Skip to content

Commit 44dbf77

Browse files
committed
customValidity didn't clear without client-side validators.
1 parent af7bc51 commit 44dbf77

File tree

6 files changed

+150
-18
lines changed

6 files changed

+150
-18
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- "SPA action mode", the `SPA` option can now take a string, corresponding to a form action, and it will post there, even without a html form on the page.
12+
- "SPA action mode", the `SPA` option can now take a string, corresponding to a form action, and it will post there, even without a html form on the page. Especially useful for debounced server checks like available usernames.
1313

1414
### Fixed
1515

1616
- Fixed types for constraints, tainted and errors when using intersections and unions in schemas.
1717
- Fixed SuperDebug collapsed height by preventing global css leak.
1818
- Redirect in SPA mode cancelled normal redirects, preventing applyAction.
1919
- Default objects sent to `superForm` returned a form with an empty id, causing collisions, it is now a random string.
20+
- `customValidity` didn't clear without client-side validators.
2021

2122
## [2.8.1] - 2024-03-07
2223

src/lib/client/superForm.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -758,14 +758,23 @@ export function superForm<
758758
setTimeout(() => Form__changeEvent(event));
759759
}
760760

761-
if (!event || !options.validators || options.validators == 'clear') return;
761+
let skipValidation = false;
762762

763763
if (!force) {
764764
if (options.validationMethod == 'onsubmit' || options.validationMethod == 'submit-only') {
765-
return;
765+
skipValidation = true;
766+
} else if (options.validationMethod == 'onblur' && event?.type == 'input')
767+
skipValidation = true;
768+
else if (options.validationMethod == 'oninput' && event?.type == 'blur')
769+
skipValidation = true;
770+
}
771+
772+
if (skipValidation || !event || !options.validators || options.validators == 'clear') {
773+
if (event?.paths) {
774+
const formElement = event?.formElement ?? EnhancedForm;
775+
if (formElement) Form__clearCustomValidity(formElement, event.paths);
766776
}
767-
if (options.validationMethod == 'onblur' && event.type == 'input') return;
768-
if (options.validationMethod == 'oninput' && event.type == 'blur') return;
777+
return;
769778
}
770779

771780
const result = await Form_validate({ adapter });
@@ -782,21 +791,13 @@ export function superForm<
782791
return result;
783792
}
784793

785-
async function Form__displayNewErrors(
786-
errors: ValidationErrors<T>,
787-
event: FullChangeEvent,
788-
force: boolean
794+
function Form__clearCustomValidity(
795+
formElement: HTMLFormElement,
796+
paths: (string | number | symbol)[][]
789797
) {
790-
const { type, immediate, multiple, paths } = event;
791-
const previous = Data.errors;
792-
793-
const output: Record<string, unknown> = {};
794798
const validity = new Map<string, { el: HTMLElement; message: string }>();
795-
796-
const formElement = event.formElement ?? EnhancedForm;
797-
798799
if (options.customValidity && formElement) {
799-
for (const path of event.paths) {
800+
for (const path of paths) {
800801
const name = CSS.escape(mergePath(path));
801802
const el = formElement.querySelector<HTMLElement>(`[name="${name}"]`);
802803
if (el) {
@@ -806,6 +807,22 @@ export function superForm<
806807
}
807808
}
808809
}
810+
return validity;
811+
}
812+
813+
async function Form__displayNewErrors(
814+
errors: ValidationErrors<T>,
815+
event: FullChangeEvent,
816+
force: boolean
817+
) {
818+
const { type, immediate, multiple, paths } = event;
819+
const previous = Data.errors;
820+
821+
const output: Record<string, unknown> = {};
822+
let validity = new Map<string, { el: HTMLElement; message: string }>();
823+
824+
const formElement = event.formElement ?? EnhancedForm;
825+
if (formElement) validity = Form__clearCustomValidity(formElement, event.paths);
809826

810827
traversePaths(errors, (error) => {
811828
if (!Array.isArray(error.value)) return;

src/routes/(v2)/v2/Navigation.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
'syncflash',
4949
'redirect-login',
5050
'debounce-username',
51-
'mixed-forms'
51+
'mixed-forms',
52+
'custom-validity'
5253
].sort();
5354
</script>
5455

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { superValidate, message } from '$lib/index.js';
2+
import { zod } from '$lib/adapters/zod.js';
3+
import { fail } from '@sveltejs/kit';
4+
import { schema } from './schema.js';
5+
6+
export const load = async () => {
7+
const form = await superValidate(zod(schema));
8+
return { form };
9+
};
10+
11+
export const actions = {
12+
default: async ({ request }) => {
13+
const form = await superValidate(request, zod(schema));
14+
console.log(form);
15+
16+
if (!form.valid) return fail(400, { form });
17+
18+
return message(form, 'Form posted successfully!');
19+
}
20+
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
import { superForm } from '$lib/index.js';
4+
import SuperDebug from '$lib/index.js';
5+
6+
export let data;
7+
8+
const { form, errors, message, enhance } = superForm(data.form, {
9+
customValidity: true
10+
});
11+
12+
// eslint-disable-next-line svelte/valid-compile
13+
$page;
14+
</script>
15+
16+
<SuperDebug data={$form} />
17+
18+
<h3>Superforms testing ground - Zod</h3>
19+
20+
{#if $message}
21+
<div class="status" class:error={$page.status >= 400} class:success={$page.status == 200}>
22+
{$message}
23+
</div>
24+
{/if}
25+
26+
<form method="POST" use:enhance>
27+
<label>
28+
Name<br />
29+
<input name="name" aria-invalid={$errors.name ? 'true' : undefined} bind:value={$form.name} />
30+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
31+
</label>
32+
33+
<label>
34+
Email<br />
35+
<input
36+
name="email"
37+
type="email"
38+
aria-invalid={$errors.email ? 'true' : undefined}
39+
bind:value={$form.email}
40+
/>
41+
{#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}
42+
</label>
43+
44+
<button>Submit</button>
45+
</form>
46+
47+
<hr />
48+
<p><a target="_blank" href="https://superforms.rocks/api">API Reference</a></p>
49+
50+
<style>
51+
.invalid {
52+
color: red;
53+
}
54+
55+
.status {
56+
color: white;
57+
padding: 4px;
58+
padding-left: 8px;
59+
border-radius: 2px;
60+
font-weight: 500;
61+
}
62+
63+
.status.success {
64+
background-color: seagreen;
65+
}
66+
67+
.status.error {
68+
background-color: #ff2a02;
69+
}
70+
71+
input {
72+
background-color: #ddd;
73+
}
74+
75+
a {
76+
text-decoration: underline;
77+
}
78+
79+
hr {
80+
margin-top: 4rem;
81+
}
82+
83+
form {
84+
padding-top: 1rem;
85+
padding-bottom: 1rem;
86+
}
87+
</style>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
name: z.string().min(1),
5+
email: z.string().email()
6+
});

0 commit comments

Comments
 (0)