@@ -42,14 +42,8 @@ export function nativeControlCreate(
4242) : ( ) => void {
4343 let updateMode = false ;
4444 const input = parent . nativeFormElement ;
45- // The input's value as the DOM itself holds it, recorded after each write we make.
46- //
47- // Comparing model values can't tell a user edit from the browser reshaping what we wrote: an
48- // unparseable string or a `Date` the input can't represent is rejected and leaves the value
49- // empty, and a `Date` carrying more precision than the input keeps is truncated. In each case the
50- // value read back differs from the model through no action of the user, which would dirty every
51- // date-like field on load (#69632). The DOM's own string is the one representation both sides
52- // agree on.
45+ // The DOM's own string, recorded after each write. The browser rejects values it can't parse and
46+ // truncates ones it can't represent, so comparing model values reads those as edits (#69632).
5347 let lastWrittenDomValue = input . value ;
5448
5549 // TODO: (perf) ok to always create this?
@@ -64,9 +58,7 @@ export function nativeControlCreate(
6458 ) ;
6559
6660 parseErrorsSource . set ( parser . errors ) ;
67- // Writes to the DOM and records what the DOM ended up holding, which is not necessarily what we
68- // asked for: the browser rejects values it can't parse and truncates ones carrying more precision
69- // than the input type keeps.
61+ // Records what the DOM kept, which may differ from what we asked it to hold.
7062 const writeNativeControlValue = ( value : unknown ) => {
7163 setNativeControlValue ( input , value ) ;
7264 lastWrittenDomValue = input . value ;
@@ -80,9 +72,8 @@ export function nativeControlCreate(
8072 } ;
8173 // Pass undefined as the raw value since the parse function doesn't care about it.
8274 host . listenToDom ( 'input' , ( ) => {
83- // An `input` event is the definitive signal of user interaction, so dirty the field up front.
84- // Parsing may fail — typing `e` into a number input, or an incomplete date — in which case no
85- // value reaches `controlValue` and nothing else would mark the field as edited.
75+ // An `input` event means the user edited, even if parsing then fails and no value reaches
76+ // `controlValue`.
8677 parent . state ( ) . markAsDirty ( ) ;
8778 parser . setRawValue ( undefined ) ;
8879 } ) ;
@@ -91,13 +82,9 @@ export function nativeControlCreate(
9182 // TODO: move extraction to first update pass?
9283 if ( isInput ( input ) && inputRequiresValidityTracking ( input ) ) {
9384 validityMonitor . watchValidity ( parent . destroyRef , input , ( ) => {
94- // The browser runs the `:valid` / `:invalid` animation as soon as the input renders, which is
95- // not a user edit. If the DOM still holds exactly what we last wrote then nothing changed and
96- // there is nothing to sync.
97- //
98- // Two states are exempt, because in both the user did edit the input while its `value` stayed
99- // empty: entering text the input can't convert (`badInput`, which reports an empty `value`),
100- // and clearing that text again, which resolves the parse error the previous sync recorded.
85+ // The `:valid` / `:invalid` animation fires on render, which is not an edit: if the DOM still
86+ // holds what we wrote, nothing changed. `badInput` and a pending parse error are exempt —
87+ // both mean the user edited while `value` stayed empty.
10188 if (
10289 untracked ( parser . errors ) . length === 0 &&
10390 ! validityMonitor . isBadInput ( input ) &&
0 commit comments