@void/svelte 1.2.1 → 1.3.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/plugin.mjs CHANGED
@@ -95,7 +95,10 @@ function islandsPlugin(pagesDir, options) {
95
95
  },
96
96
  async buildStart() {
97
97
  if (existsSync(pagesDir)) {
98
- pageScan = await scanPages(root, { pagesDir });
98
+ pageScan = await scanPages(root, {
99
+ pagesDir,
100
+ environment: isDev ? "dev" : "prod"
101
+ });
99
102
  const filesToScan = [
100
103
  ...pageScan.pages.map((page) => ({
101
104
  filePath: page.componentPath,
@@ -360,7 +363,10 @@ function pagesPlugin(pagesDir, options) {
360
363
  const normalizedPagesDir = normalizePath(pagesDir);
361
364
  let pendingRescan = null;
362
365
  const rescanPages = async () => {
363
- pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
366
+ pageScan = existsSync(pagesDir) ? await scanPages(root, {
367
+ pagesDir,
368
+ environment: isDev ? "dev" : "prod"
369
+ }) : null;
364
370
  };
365
371
  const runRescan = () => {
366
372
  pendingRescan ??= rescanPages().finally(() => {
@@ -424,14 +430,12 @@ function pagesPlugin(pagesDir, options) {
424
430
  viteConfig = config;
425
431
  },
426
432
  config(_, env) {
433
+ isDev = env.command === "serve";
427
434
  if (!existsSync(pagesDir)) return;
428
- if (env.command === "serve") {
429
- isDev = true;
430
- return {
431
- optimizeDeps: SVELTE_OPTIMIZE_DEPS,
432
- environments: { void_worker: { optimizeDeps: { exclude: ["@void/svelte"] } } }
433
- };
434
- }
435
+ if (isDev) return {
436
+ optimizeDeps: SVELTE_OPTIMIZE_DEPS,
437
+ environments: { void_worker: { optimizeDeps: { exclude: ["@void/svelte"] } } }
438
+ };
435
439
  if (!hasNonIslandPages(pagesDir)) return;
436
440
  return { build: {
437
441
  manifest: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/svelte",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Svelte pages, server rendering, forms, and navigation for Void.",
5
5
  "homepage": "https://void.cloud",
6
6
  "bugs": {
@@ -43,12 +43,12 @@
43
43
  "svelte": "^5.57.2",
44
44
  "vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
45
45
  "vite-plus": "1.3.0",
46
- "void": "1.2.1"
46
+ "void": "1.3.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "svelte": "^5.0.0",
50
50
  "vite": "^8.0.0",
51
- "void": "1.2.1"
51
+ "void": "1.3.0"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "vp pack",
@@ -1,11 +1,14 @@
1
- import { getContext } from 'svelte';
1
+ import { getContext, untrack } from 'svelte';
2
2
  import {
3
3
  VoidActionError,
4
+ cloneFormValue,
4
5
  getValidationErrors,
5
6
  interpolateRouteParams,
6
7
  isAbortError,
7
8
  isEqualFormValue,
9
+ parseFormResetArgs,
8
10
  submitAction,
11
+ type FormResetOptions,
9
12
  type VisitOptions,
10
13
  type VoidRouter,
11
14
  } from 'void/pages-client';
@@ -26,7 +29,9 @@ type UntypedForm = {
26
29
  put(opts?: FormSubmitOptions): Promise<void>;
27
30
  patch(opts?: FormSubmitOptions): Promise<void>;
28
31
  delete(opts?: FormSubmitOptions): Promise<void>;
32
+ reset(options: FormResetOptions, ...fields: ReadonlyArray<string>): void;
29
33
  reset(...fields: ReadonlyArray<string>): void;
34
+ setError(field: string, message: string): void;
30
35
  clearErrors(...fields: ReadonlyArray<string>): void;
31
36
  clearError(): void;
32
37
  };
@@ -48,7 +53,12 @@ export function useForm<U extends ActionUrl & string>(
48
53
  put(opts?: FormSubmitOptions): Promise<void>;
49
54
  patch(opts?: FormSubmitOptions): Promise<void>;
50
55
  delete(opts?: FormSubmitOptions): Promise<void>;
56
+ reset(
57
+ options: FormResetOptions,
58
+ ...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>
59
+ ): void;
51
60
  reset(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
61
+ setError(field: keyof ResolveActionBody<U> & string, message: string): void;
52
62
  clearErrors(...fields: ReadonlyArray<keyof ResolveActionBody<U> & string>): void;
53
63
  clearError(): void;
54
64
  };
@@ -56,32 +66,50 @@ export function useForm<U extends ActionUrl & string>(
56
66
  export function useForm(
57
67
  url: string,
58
68
  defaults: Record<string, unknown>,
59
- options?: { params?: Record<string, string> },
69
+ options?: { params?: Record<string, string>; resetOnSuccess?: boolean },
60
70
  ): UntypedForm {
61
71
  const router = getContext<VoidRouter>('__void_router');
62
72
 
63
- let data = $state({ ...defaults });
73
+ const initialDefaults = cloneFormValue(defaults);
74
+ let data = $state(cloneFormValue(initialDefaults));
64
75
  let errors: Record<string, string> = $state({});
65
76
  let error: VoidActionError | null = $state(null);
66
77
  let pending = $state(false);
67
78
  let wasSuccessful = $state(false);
68
79
  let recentlySuccessful = $state(false);
69
80
 
70
- let defaults_ = { ...defaults };
81
+ let defaults_ = $state.raw(initialDefaults);
71
82
  let successTimeout: ReturnType<typeof setTimeout> | null = null;
83
+ let dataSnapshot = cloneFormValue(initialDefaults);
84
+
85
+ $effect(() => {
86
+ // Read nested values even after an earlier edit, without subscribing to errors.
87
+ const current = cloneFormValue(data);
88
+ untrack(() => {
89
+ for (const key of new Set([...Object.keys(dataSnapshot), ...Object.keys(current)])) {
90
+ if (!isEqualFormValue(current[key], dataSnapshot[key])) {
91
+ delete errors[key];
92
+ }
93
+ }
94
+ dataSnapshot = current;
95
+ });
96
+ });
72
97
 
73
98
  const hasChanges = $derived(
74
99
  Object.keys(defaults_).some((key) => !isEqualFormValue(data[key], defaults_[key])),
75
100
  );
76
101
 
77
- function reset(...fields: ReadonlyArray<string>) {
102
+ function reset(...args: ReadonlyArray<string | FormResetOptions>) {
103
+ const { initial, fields } = parseFormResetArgs(args);
104
+ const source = initial ? initialDefaults : defaults_;
78
105
  if (fields.length === 0) {
79
- data = { ...defaults_ };
106
+ data = cloneFormValue(source);
80
107
  } else {
81
108
  for (const field of fields) {
82
- data[field] = defaults_[field];
109
+ data[field] = cloneFormValue(source[field]);
83
110
  }
84
111
  }
112
+ dataSnapshot = cloneFormValue(data);
85
113
  }
86
114
 
87
115
  function clearErrors(...fields: ReadonlyArray<string>) {
@@ -94,6 +122,12 @@ export function useForm(
94
122
  }
95
123
  }
96
124
 
125
+ function setError(field: string, message: string) {
126
+ // An input handler may validate its new value before the change effect runs.
127
+ dataSnapshot = { ...dataSnapshot, [field]: cloneFormValue(data[field]) };
128
+ errors = { ...errors, [field]: message };
129
+ }
130
+
97
131
  function clearError() {
98
132
  error = null;
99
133
  }
@@ -120,7 +154,9 @@ export function useForm(
120
154
  if (!result.ok) {
121
155
  const validationErrors = getValidationErrors(result.error.body);
122
156
  if (validationErrors) {
123
- Object.assign(errors, validationErrors);
157
+ for (const [field, message] of Object.entries(validationErrors)) {
158
+ setError(field, message);
159
+ }
124
160
  } else {
125
161
  error = result.error;
126
162
  }
@@ -129,7 +165,12 @@ export function useForm(
129
165
 
130
166
  wasSuccessful = true;
131
167
  recentlySuccessful = true;
132
- defaults_ = { ...data };
168
+ if (options?.resetOnSuccess) {
169
+ data = cloneFormValue(initialDefaults);
170
+ defaults_ = cloneFormValue(initialDefaults);
171
+ } else {
172
+ defaults_ = cloneFormValue(data);
173
+ }
133
174
  successTimeout = setTimeout(() => {
134
175
  recentlySuccessful = false;
135
176
  }, 2000);
@@ -166,6 +207,7 @@ export function useForm(
166
207
  return recentlySuccessful;
167
208
  },
168
209
  reset,
210
+ setError,
169
211
  clearErrors,
170
212
  clearError,
171
213
  post: (opts?: FormSubmitOptions) => submit('POST', opts),