From b32fd403ec29dc6acc3bebd199c4bb3b35bc252e Mon Sep 17 00:00:00 2001 From: pk Date: Wed, 10 Jun 2026 22:42:27 +0200 Subject: [PATCH] fix(tp-date-input): validate correctly when change event hasn't fired yet validate() relied on this.value, which is only set by _inputChanged on the change event. Submitting via Enter while focus is still in an input field skips that event, leaving this.value undefined and causing valid dates to fail validation. Introduce _valueFromInputs() as the single parsing source: it reads the three sub-inputs directly and returns a UTC-midnight DateTime. validate() now falls back to it when this.value is not set. _inputChanged is refactored to call _valueFromInputs() as well, removing the duplicated format-mapping logic. --- package.json | 2 +- tp-date-input.js | 58 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 7f9a0df..6854424 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tp/tp-date-input", - "version": "2.1.1", + "version": "2.1.2", "description": "", "main": "tp-date-input.js", "scripts": { diff --git a/tp-date-input.js b/tp-date-input.js index 289c79e..15b11f9 100644 --- a/tp-date-input.js +++ b/tp-date-input.js @@ -212,8 +212,20 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) { const maxDate = this._getMaxDate(this.maxDate); - if ((this.inputs[0].invalid || this.inputs[1].invalid || this.inputs[2].invalid) || - !this.dateValid(this.value, maxDate)) { + if (this.inputs[0].invalid || this.inputs[1].invalid || this.inputs[2].invalid) { + this.invalid = true; + return false; + } + + // this.value is only set by _inputChanged which fires on the change event. + // When validate() is triggered before the change event fires (e.g. submitting + // via Enter while focus is still in the year field), this.value can be + // undefined/null even though the inputs contain a fully valid date. + // Fall back to computing the ISO value directly from the inputs in that case. + const utcDt = this._valueFromInputs(); + const value = this.value || (utcDt ? utcDt.toISO() : null); + + if (!this.dateValid(value, maxDate)) { this.invalid = true; return false; } @@ -222,6 +234,31 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) { return true; } + // Parses the three input fields into a UTC-midnight DateTime. + // Returns the DateTime on success, or null if any field is empty or the date is invalid. + _valueFromInputs() { + const i0 = this.inputs[0]?.value; + const i1 = this.inputs[1]?.value; + const i2 = this.inputs[2]?.value; + + if (!i0 || !i1 || !i2) return null; + + const format = this._inputAssign.map(part => { + switch (part) { + case 'MM': return 'LL'; + case 'dd': return 'dd'; + case 'y': return 'yyyy'; + default: return part; + } + }).join('-'); + + const dt = DateTime.fromFormat(`${i0}-${i1}-${i2}`, format); + + if (!dt.isValid) return null; + + return DateTime.utc(dt.year, dt.month, dt.day); + } + focus() { this.inputs[0].select(); } @@ -291,26 +328,15 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) { return; } - // Convert luxon format to match input assignment - const format = this._inputAssign.map(part => { - switch (part) { - case 'MM': return 'LL'; - case 'dd': return 'dd'; - case 'y': return 'yyyy'; - default: return part; - } - }).join('-'); - // Parse the entered values as a plain calendar date, then store as UTC midnight. // Date-only fields are timezone-agnostic — April 7 means April 7 for everyone. - const dt = DateTime.fromFormat(i0 + '-' + i1 + '-' + i2, format); + const utcMidnight = this._valueFromInputs(); - if (dt.isValid) { + if (utcMidnight) { this.inputs[0].invalid = false; this.inputs[1].invalid = false; this.inputs[2].invalid = false; - const utcMidnight = DateTime.utc(dt.year, dt.month, dt.day); this.date = utcMidnight.toJSDate(); this.value = utcMidnight.toISO(); this.invalid = false; @@ -450,6 +476,8 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) { // Reset invalid state if value was changed. // This clears up old invalid states if the value was changed programmatically. _onValueChanged() { + console.log(this.value); + // We skip if the user was just inputting values. if (this._skipOnValueChanged) { return;