Repository navigation
Validation modes you can write yourself - #103
KamilSztandur wants to merge 12 commits into
Conversation
|
Docs preview: https://advanced-forms-k85euu9u2-leancode.vercel.app Built from 4caeb4a; the landing page is at |
|
@mhnowak Here it is :) |
|
IMO instead of introducing yet another enum value for the validation mode, the |
A common ask: no error while the user types an email for the first time, validate when they leave the field, and from then on validate every keystroke so the error disappears as soon as the value is fixed. Neither onUnfocus nor onUserInteraction does this on its own. ValidationMode.afterFirstUnfocus is the onUnfocus row of the gate until the field is first left after an edit, and the onUserInteraction row from then on. The field remembers that first unfocus in one bit, set by the event and cleared only by reset(). The gate still reads no status. validate() from a submit button does not count as the first unfocus, and leaving a field the user never edited does not either. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With four modes the segmented button no longer fits a phone, and
Flutter broke the labels mid-word ("afterFir / stUnfoc / us"). It
already did that to onUserInteraction with three. The row now keeps
its full width where it fits and scrolls horizontally where it does
not, in both copies of the demo.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
df3d958 to
acc6e4b
Compare
The four modes become constants of a final class instead of enum members, and ValidationMode.custom(name, fn) adds one the package does not ship. The function sees a ValidationContext: the event, and whether the user has ever left the field after editing it. It never sees the status or the errors, because every edit clears the errors and a debounced round sets the status to pending, so a mode reading either would disarm itself as the user started fixing the value. The untouched-field rule stays in the field, in front of every mode, so a custom mode cannot reach a field the user has not edited. Modes are compared by name, so one built in build() is not a mode change; a built-in name throws. ValidationMode.values is gone, because with custom modes there is no finite list, and an exhaustive switch over the modes needs a default case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
acc6e4b to
222a6d6
Compare
What the rule sees and never sees, what still holds, the name, three examples and how to test a rule without a form. The modes page points there, the README gets a section, and the skill reference carries the same rules in short form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A Try it island on the custom-modes page: Password on the form's default mode, Repeat password switching between followsDependency, quietDependencies and onUnfocusOnly, each a one-line rule in the source. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The page with the four shipped modes is titled Available modes, so the sidebar reads: Available modes, Configuring the mode, Your own validation mode. The quietDependencies example gives way to onOwnEdits, a username with an async availability check that must not re-run because the country changed, on the page, in the island and in the skill. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The segmented button clipped its last label on narrow screens even with the horizontal scroll. Both mode demos and the custom-modes demo now use one ChoiceChip per mode in a Wrap, so they flow onto a second row instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DocsModePicker carries the "Select your validation mode" title in the accent colour and renders the chosen mode as a solid accent pill with ink text, like the primary button. All three mode demos use it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The const list was committed for the docs copy only, so the example manifest no longer matched the landing copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| if (_builtIn.any((mode) => mode.name == name)) { | ||
| throw ArgumentError.value(name, 'name', 'is a built-in mode'); | ||
| } |
There was a problem hiding this comment.
I'm not a fan of such a solution. I'd implement different strategies as subclasses (and a custom strategy would be, e.g., extends CustomValidationMode). Then, to detect a change in the mode, you just compare types
There was a problem hiding this comment.
It wouldn't be able to tell difference between different custom modes, if we only compare by types, I think. Comparing by == instead means every custom mode writes its own equality (so it's easy to make a bug for devs using it).
I believe a name is the smallest explicit identity. Let me know if you disagree and have strong opinion on it
There was a problem hiding this comment.
We would be able — we would have two different types
There was a problem hiding this comment.
I feel like relying on runtime checks for forbidden names might be super inconvenient. With types, no such restriction would exist
There was a problem hiding this comment.
Yeah, +1 on strategy pattern, I think it fits here perfectly.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| `afterFirstUnfocus` brings none of that back: the mode never changes by itself, and the bit it reads is set by an | ||
| unfocus, not derived from a verdict. |
There was a problem hiding this comment.
Maybe I'm missing the point of this document, but I still don't see a reason to mention "bringing anything back", especially when singling out one validation mode 🤔
Problem
The form decides when a field validates itself with one of three modes: on submit, on every keystroke, or when the field is left. Real forms keep asking for rules that are not on that list. The one that started this PR: an email field should stay quiet while it is typed for the first time, validate when the user leaves it, and from then on re-check every keystroke so the error disappears the moment the address is right. Today the only way to get that, or any other rule the package does not ship, is to switch modes by hand from listeners, which is fragile and easy to get wrong.
Solution
ValidationModeis now a class instead of an enum, andValidationMode.custom(name, fn)makes a mode from your own rule. The four shipped modes are constants of the same class, written with the same two inputs your rule gets, so a custom mode is not a second-class citizen: set it on the form and it reaches every field and subform, set it on one field and that field keeps it.The rule gets a
ValidationContextwith two fields: which event is happening (valueChanged,unfocus,dependencyChanged) and whether the user has ever left the field after editing it. That is all a rule needs, and all it gets.The email case ships as a fourth built-in mode,
ValidationMode.afterFirstUnfocus: likeonUnfocusuntil the field is first left after an edit, likeonUserInteractionfrom then on.How it is nicer now
A rule you can say in one sentence is one function, with the package's guarantees still around it: an untouched field validates nothing whatever the rule says,
validate()ignores the mode, a round runs sync before async, the mode reaches the whole tree. Nothing to switch by hand, nothing to keep in sync. The docs have a page for it, with examples and how to test a rule without a form.Design decisions
pending, so a rule that asked "is there an error" would stop validating at the exact moment the user started fixing the value. That is the trap the earlier five-mode design fell into, and the context is shaped so nobody can fall into it again.custommodes with the same name are the same mode, so one built inbuild()is not a mode change and leaves a round in flight alone. A name must therefore be unique; a built-in name throws.ValidationMode.valuesis gone. With custom modes there is no finite list, so the getter would lie.afterFirstUnfocusflips to live only on an unfocus.validate()from a submit button does not count,reset()flips it back, and leaving a field the user never edited does not count.Breaking changes
For an app that uses only the shipped modes: an exhaustive
switchoverValidationModeneeds a default case, andValidationMode.valuesno longer exists. Everything else compiles unchanged. This is a 0.3.0.Demo
The "Try it" island on the Validation modes page, in
afterFirstUnfocus: two letters typed with no error, the error appears on leaving the field, a third letter clears it at once, backspace brings it back at once.Where it shows
The package:
ValidationMode,ValidationContextandValidationEventinvalidation_mode.dart, one bit in the field controller. Docs: a new page "Your own validation mode", the mode table and gate table on the modes page, README section, FAQ, internals, the skill and its validation reference, the mode-switching demo. Tests cover the custom modes (what they see, that they cannot reach an untouched field, equality by name, broadcast to subforms) and both halves ofafterFirstUnfocus.🤖 Generated with Claude Code