Skip to content

Commit 1807006

Browse files
committed
fixup! fix(forms): prevent date inputs from being marked dirty on init
1 parent 6bfdd4b commit 1807006

1 file changed

Lines changed: 8 additions & 21 deletions

File tree

‎packages/forms/signals/src/directive/control_native.ts‎

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)