Skip to content

Commit 149c3b6

Browse files
committed
WIP: Schema transform works but should only update on submit.
1 parent b153c1d commit 149c3b6

File tree

6 files changed

+190
-15
lines changed

6 files changed

+190
-15
lines changed

src/lib/client/clientValidation.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ export async function validateObjectErrors<T extends AnyZodObject, M>(
282282
}
283283
}
284284

285+
type ValidationResult<T extends ZodValidation<AnyZodObject>> = {
286+
validated: boolean | 'all';
287+
errors: string[] | undefined;
288+
data: SuperForm<T, unknown>['form'] | undefined;
289+
};
290+
285291
/**
286292
* Validate a specific form field.
287293
* @DCI-context
@@ -296,7 +302,7 @@ export async function validateField<
296302
Errors: SuperForm<T, M>['errors'],
297303
Tainted: SuperForm<T, M>['tainted'],
298304
options: ValidateOptions<unknown, UnwrapEffects<T>> = {}
299-
): Promise<string[] | undefined> {
305+
): Promise<ValidationResult<T>> {
300306
function Errors_clear() {
301307
clearErrors(Errors, { undefinePath: path, clearFormLevelErrors: true });
302308
}
@@ -352,16 +358,18 @@ export async function validateField<
352358
// the tainted+error check in oninput.
353359
Errors_clear();
354360
} else {
355-
return Errors_update(errors.errors);
361+
errors.errors = Errors_update(errors.errors);
362+
return errors;
356363
}
357364
} else if (
358365
errors.validated === false &&
359366
formOptions.defaultValidator == 'clear'
360367
) {
361-
return Errors_update(undefined);
368+
errors.errors = Errors_update(errors.errors);
369+
return errors;
362370
}
363371

364-
return errors.errors;
372+
return errors;
365373
}
366374

367375
// @DCI-context
@@ -372,7 +380,7 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
372380
Errors: SuperForm<T, M>['errors'],
373381
Tainted: SuperForm<T, M>['tainted'],
374382
options: ValidateOptions<unknown, UnwrapEffects<T>> = {}
375-
): Promise<{ validated: boolean | 'all'; errors: string[] | undefined }> {
383+
): Promise<ValidationResult<T>> {
376384
if (options.update === undefined) options.update = true;
377385
if (options.taint === undefined) options.taint = false;
378386
if (typeof options.errors == 'string') options.errors = [options.errors];
@@ -386,7 +394,7 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
386394
};
387395

388396
async function defaultValidate() {
389-
return { validated: false, errors: undefined } as const;
397+
return { validated: false, errors: undefined, data: undefined } as const;
390398
}
391399

392400
///// Roles ///////////////////////////////////////////////////////
@@ -516,11 +524,16 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
516524

517525
return {
518526
validated: true,
519-
errors: options.errors ?? current?.value
527+
errors: options.errors ?? current?.value,
528+
data: undefined
520529
};
521530
} else {
522531
Errors_clearAll();
523-
return { validated: true, errors: undefined };
532+
return {
533+
validated: true,
534+
errors: undefined,
535+
data: result.data // For a successful Zod result, return the possibly transformed data.
536+
};
524537
}
525538
} else {
526539
// SuperForms validator
@@ -539,7 +552,8 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
539552

540553
return {
541554
validated: true,
542-
errors: result ? options.errors ?? result : result
555+
errors: result ? options.errors ?? result : result,
556+
data: undefined // No transformation for Superforms validators
543557
};
544558
}
545559
}

src/lib/client/formEnhance.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export function formEnhance<T extends AnyZodObject, M>(
153153
else validityEl = null;
154154
}
155155

156-
const newErrors = await validateField(
156+
const result = await validateField(
157157
change,
158158
options,
159159
data,
@@ -162,7 +162,11 @@ export function formEnhance<T extends AnyZodObject, M>(
162162
);
163163

164164
if (validityEl) {
165-
setCustomValidity(validityEl as any, newErrors);
165+
setCustomValidity(validityEl as HTMLInputElement, result.errors);
166+
}
167+
168+
if (result.data) {
169+
data.set(result.data);
166170
}
167171
}
168172

@@ -226,6 +230,7 @@ export function formEnhance<T extends AnyZodObject, M>(
226230
);
227231
if (!validityEl) continue;
228232

233+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
229234
const hadErrors = traversePath(get(errors), change as any);
230235
if (hadErrors && hadErrors.key in hadErrors.parent) {
231236
// Problem - store hasn't updated here with new value yet.

src/lib/client/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ const defaultFormOptions = {
191191
validateMethod: 'auto'
192192
};
193193

194-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
195194
type SuperFormSnapshot<
196195
T extends AnyZodObject,
196+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
197197
M = Superforms.Message extends never ? any : Superforms.Message
198198
> = SuperValidated<T, M> & { tainted: TaintedFields<T> | undefined };
199199

@@ -203,9 +203,9 @@ export type TaintOption<T extends AnyZodObject = AnyZodObject> =
203203
| 'untaint-all'
204204
| { fields: FormPathLeaves<z.infer<T>> | FormPathLeaves<z.infer<T>>[] };
205205

206-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
207206
export type SuperForm<
208207
T extends ZodValidation<AnyZodObject>,
208+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
209209
M = Superforms.Message extends never ? any : Superforms.Message
210210
> = {
211211
form: {
@@ -909,7 +909,9 @@ export function superForm<
909909
})
910910
) as unknown as FormFields<UnwrappedT>;
911911

912-
function validate<Path extends FormPathLeaves<z.infer<UnwrapEffects<T>>>>(
912+
async function validate<
913+
Path extends FormPathLeaves<z.infer<UnwrapEffects<T>>>
914+
>(
913915
path?: Path,
914916
opts?: ValidateOptions<
915917
FormPathType<z.infer<UnwrapEffects<T>>, Path>,
@@ -925,14 +927,15 @@ export function superForm<
925927
false
926928
);
927929
}
928-
return validateField<UnwrapEffects<T>, M>(
930+
const result = await validateField<UnwrapEffects<T>, M>(
929931
splitPath(path) as string[],
930932
options,
931933
Form,
932934
Errors,
933935
Tainted,
934936
opts
935937
);
938+
return result.errors;
936939
}
937940

938941
return {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { superValidate, message } from '$lib/server';
2+
import { fail } from '@sveltejs/kit';
3+
import { schema } from './schema';
4+
5+
import type { Actions, PageServerLoad } from './$types';
6+
7+
///// Load function /////
8+
9+
export const load: PageServerLoad = async () => {
10+
const form = await superValidate(schema);
11+
return { form };
12+
};
13+
14+
///// Form actions /////
15+
16+
export const actions: Actions = {
17+
default: async ({ request }) => {
18+
const form = await superValidate(request, schema);
19+
20+
console.log('POST', form);
21+
22+
if (!form.valid) return fail(400, { form });
23+
24+
form.data.email = '[email protected]';
25+
26+
return message(form, 'Form posted successfully!');
27+
}
28+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
import { superForm } from '$lib/client';
4+
import SuperDebug from '$lib/client/SuperDebug.svelte';
5+
import { schema } from './schema';
6+
7+
export let data;
8+
9+
let SPA = $page.url.searchParams.has('SPA') || undefined;
10+
11+
const { form, errors, message, enhance, validate } = superForm(data.form, {
12+
SPA,
13+
taintedMessage: null,
14+
validators: schema,
15+
onUpdate({ form }) {
16+
if (SPA && form.valid) form.message = 'SPA form posted OK';
17+
}
18+
});
19+
20+
let customCheck = '';
21+
22+
async function validateCheck() {
23+
const result = (await validate()).data;
24+
customCheck = `${result.name} - ${result.email} - ${await validate(
25+
'email'
26+
)}`;
27+
}
28+
</script>
29+
30+
<SuperDebug data={$form} />
31+
32+
<h3>Superforms client-side validation</h3>
33+
34+
{#if $message}
35+
<div
36+
class="status"
37+
class:error={$page.status >= 400}
38+
class:success={$page.status == 200}
39+
>
40+
{$message}
41+
</div>
42+
{/if}
43+
44+
<p>{customCheck}</p>
45+
46+
<form method="POST" use:enhance>
47+
<label>
48+
Name<br />
49+
<input
50+
name="name"
51+
aria-invalid={$errors.name ? 'true' : undefined}
52+
bind:value={$form.name}
53+
/>
54+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
55+
</label>
56+
57+
<label>
58+
Email<br />
59+
<input
60+
name="email"
61+
aria-invalid={$errors.email ? 'true' : undefined}
62+
bind:value={$form.email}
63+
/>
64+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
65+
</label>
66+
67+
<button>Submit</button>
68+
<button type="button" on:click={validateCheck}>Manual validate</button>
69+
</form>
70+
71+
<hr />
72+
<p>
73+
<a target="_blank" href="https://superforms.rocks/api">API Reference</a>
74+
</p>
75+
76+
<style>
77+
.invalid {
78+
color: red;
79+
}
80+
81+
.status {
82+
color: white;
83+
padding: 4px;
84+
padding-left: 8px;
85+
border-radius: 2px;
86+
font-weight: 500;
87+
}
88+
89+
.status.success {
90+
background-color: seagreen;
91+
}
92+
93+
.status.error {
94+
background-color: #ff2a02;
95+
}
96+
97+
input {
98+
background-color: #ddd;
99+
}
100+
101+
a {
102+
text-decoration: underline;
103+
}
104+
105+
hr {
106+
margin-top: 4rem;
107+
}
108+
109+
form {
110+
padding-top: 1rem;
111+
padding-bottom: 1rem;
112+
}
113+
</style>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
name: z.string().min(1),
5+
email: z
6+
.string()
7+
.email()
8+
.transform((email) => {
9+
console.log('Transforming email:', email);
10+
11+
})
12+
});

0 commit comments

Comments
 (0)