feat: add dirty() method to form fields, improve validation skipping logic - #16208
Merged
Conversation
🦋 Changeset detectedLatest commit: 6302071 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| touched[key] = true; | ||
|
|
||
| if (element) { | ||
| // TODO only if the value actually changed |
Contributor
There was a problem hiding this comment.
shouldn't this actually be implemented as part of this PR?
|
Install the latest version of pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/6302071cee945fdca1630c15e3e175913a5f3af0Open in |
elliott-with-the-longest-name-on-github
approved these changes
Jul 1, 2026
Contributor
|
/autofix |
… never treated as pristine after a reset and validation surfaces issues for all fields.
This commit fixes the issue reported at packages/kit/src/runtime/client/remote-functions/form.svelte.js:537
## Bug
In `packages/kit/src/runtime/client/remote-functions/form.svelte.js`, `handle_reset` (lines ~528-538) clears validation state but omits `submitted`:
```js
const handle_reset = async () => {
await tick();
input = convert_formdata(new FormData(form));
raw_issues = [];
touched = {};
dirty = {};
can_validate = {};
// submitted was never reset
};
```
`submitted` is a `$state(false)` (line 114) set to `true` on submit (lines 427, 590) and never reset back anywhere. The `validate()` method gates the `can_validate` filter on it:
```js
if (!all && !submitted) {
array = array.filter((issue) => can_validate[issue.name]);
}
```
Once `submitted` becomes `true`, this filter is permanently skipped.
## Failure mode
1. User submits the form → `submitted = true`.
2. User resets the form (native reset / reset event) → `handle_reset` clears `touched`/`dirty`/`can_validate` but leaves `submitted = true`.
3. User edits a single field and `validate()` runs → because `submitted` is still `true`, the `can_validate` filter is skipped and validation issues for **all** fields (including untouched, un-blurred ones) are surfaced.
This contradicts the intended pristine-after-reset behavior ("all fields are always subject to validation... unless the form is reset, at which point it is treated as pristine").
## Fix
Add `submitted = false;` to `handle_reset` alongside the other resets, so a reset form returns to the pristine validation state.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
Rich-Harris
pushed a commit
that referenced
this pull request
Jul 2, 2026
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to version-3, this PR will be updated.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ `version-3` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run `changeset pre exit` on `version-3`.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ # Releases ## @sveltejs/adapter-node@6.0.0-next.2 ### Major Changes - breaking: add `kit.paths.origin` config option, remove `kit.prerender.origin` and the `adapter-node` `ORIGIN` environment variable ([#16161](#16161)) ### Patch Changes - Updated dependencies [[`3c434fb`](3c434fb), [`a9284e8`](a9284e8), [`3726a7a`](3726a7a), [`f9d2240`](f9d2240), [`a9284e8`](a9284e8), [`7c040ba`](7c040ba), [`223eaad`](223eaad), [`223eaad`](223eaad), [`3b907d4`](3b907d4), [`fd628a5`](fd628a5), [`223eaad`](223eaad), [`6c1d035`](6c1d035), [`178eac0`](178eac0), [`c6562a9`](c6562a9), [`8eca2ab`](8eca2ab), [`61cf188`](61cf188)]: - @sveltejs/kit@3.0.0-next.6 ## @sveltejs/kit@3.0.0-next.6 ### Major Changes - breaking: return no content for 204 responses ([#16200](#16200)) - breaking: form action responses now use the HTTP status code returned from `fail` ([#16200](#16200)) - breaking: nested server-only directories ([#15685](#15685)) - breaking: add `kit.paths.origin` config option, remove `kit.prerender.origin` and the `adapter-node` `ORIGIN` environment variable ([#16161](#16161)) - breaking: don't abort navigation when calling `invalidate(All)` during navigation ([#16188](#16188)) - breaking: allow `handleError` to influence status code ([#16162](#16162)) - breaking: forbid external redirects by default ([#16198](#16198)) ### Minor Changes - feat: use `type: 'module'` for service worker registrations ([#16169](#16169)) - feat: add `dirty()` property to form fields ([#16208](#16208)) - feat: add `cookies.parse` method ([#16203](#16203)) ### Patch Changes - fix: drain unconsumed request bodies so keep-alive connections don't hang ([#16170](#16170)) - fix: properly handle Date objects in form.fields.set ([#16168](#16168)) - fix: skip clean fields when programmatically validating forms ([#16208](#16208)) - breaking: experimental remote form `validate({ includeUntouched })` option is now `all` ([#16208](#16208)) - fix: return `undefined` from `fields.branch.issues()` when only `fields.branch.leaf` has issues ([#16187](#16187)) - feat: add field.touched() helper to remote form fields ([#14692](#14692)) ## @sveltejs/adapter-cloudflare@8.0.0-next.1 ### Patch Changes - fix: avoid overriding user's existing `_headers` rules ([#16183](#16183)) - Updated dependencies [[`3c434fb`](3c434fb), [`a9284e8`](a9284e8), [`3726a7a`](3726a7a), [`f9d2240`](f9d2240), [`a9284e8`](a9284e8), [`7c040ba`](7c040ba), [`223eaad`](223eaad), [`223eaad`](223eaad), [`3b907d4`](3b907d4), [`fd628a5`](fd628a5), [`223eaad`](223eaad), [`6c1d035`](6c1d035), [`178eac0`](178eac0), [`c6562a9`](c6562a9), [`8eca2ab`](8eca2ab), [`61cf188`](61cf188)]: - @sveltejs/kit@3.0.0-next.6 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
elliott-with-the-longest-name-on-github
added a commit
that referenced
this pull request
Aug 27, 2026
…ault only until edited (#16939) Since #16331 moved coercion into `coerce_form_value`, `handle_input` has stored the raw string of every typed value: `fields.value()` reported `'20'` while typing and `200` after a reset, and an emptied number field held `''` instead of `undefined`. Typed values are coerced again, the same way the submitter path already is. That re-exposes #15937: the default passed to `.as(type, default)` was re-applied whenever the field's value became `undefined` or `null`, so an emptied number input, or one cleared with `set()`, snapped back to its default. The default now only applies until the field is dirty. `defaultValue` and `defaultChecked` are unchanged, so reset still restores it. `dirty` is the modified signal from #16208; `touched` would drop the default on a tab-through. It is client-only, so SSR and a failed no-JS submission still render defaults. #16237 keys the same rule on key presence in the model instead. Fixes #15937.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #14692. This exposes a
dirty()method on form fields, which is separate fromtouched()— it only becomestrueonce a value is actually edited (whether via user input or programmatically, after the form has mounted).It also improves the logic around
myform.validate()— rather than validating all fields that have been touched by default, it now only validates fields that are touched and dirty, meaning the user has edited a field and then blurred it (or it was set programmatically), which matches accessibility guidelines.To validate every field regardless of dirty state, do
myform.validate({ all: true }).Draft because I guess we should add a test
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm testand lint the project withpnpm lintandpnpm checkChangesets
pnpm changesetand following the prompts. Changesets that add features should beminorand those that fix bugs should bepatch. Please prefix changeset messages withfeat:,fix:, orchore:.Edits