1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 08:33:31 +01:00

Add date range picker time validation (#51267)

* Fix base time inputs reportValidity() function

The queryAll selector returns a NodeList not not an array. Need to spread it to an array before we can use every().

* Validate the date range picker time inputs

Enable auto validation to get the nice red underline on invalid values, and then check validity before accepting the input.

* Fix automatic 24hr value conversion in AM/PM format

When using AM/PM, entering a 24 hour value will automatically convert the first time. For example 15 will become 3. However if you then enter 15 again it will stay as 15 and not update.
To fix this, make sure we trigger an update of the input field once the current update cycle is complete.

* Validate time inputs on save not value update

In the value changed callback, the update 24->12hr input correction will not have been updated and therefore they will report invalid.
This commit is contained in:
Tom Carpenter
2026-03-30 07:13:51 +01:00
committed by Bram Kragten
parent 78a2cbb532
commit 111be984e0
3 changed files with 37 additions and 4 deletions

View File

@@ -4,7 +4,7 @@ import type { ActionDetail } from "@material/mwc-list";
import { mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, queryAll, state } from "lit/decorators";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import {
formatCallyDateRange,
@@ -29,6 +29,7 @@ import "../ha-list-item";
import "../ha-time-input";
import type { DateRangePickerRanges } from "./ha-date-range-picker";
import { datePickerStyles, dateRangePickerStyles } from "./styles";
import type { HaTimeInput } from "../ha-time-input";
@customElement("date-range-picker")
export class DateRangePicker extends LitElement {
@@ -69,6 +70,8 @@ export class DateRangePicker extends LitElement {
to: { hours: 23, minutes: 59 },
};
@queryAll("ha-time-input") private _timeInputs?: NodeListOf<HaTimeInput>;
public connectedCallback() {
super.connectedCallback();
@@ -153,6 +156,7 @@ export class DateRangePicker extends LitElement {
)}
id="from"
placeholder-labels
auto-validate
></ha-time-input>
<ha-time-input
.value=${`${this._timeValue.to.hours}:${this._timeValue.to.minutes}`}
@@ -163,6 +167,7 @@ export class DateRangePicker extends LitElement {
)}
id="to"
placeholder-labels
auto-validate
></ha-time-input>
</div>
`
@@ -200,6 +205,14 @@ export class DateRangePicker extends LitElement {
let endDate = new Date(`${dates[1]}T23:59:00`);
if (this.timePicker) {
const timeInputs = this._timeInputs;
if (
timeInputs &&
![...timeInputs].every((input) => input.reportValidity())
) {
// If we have time inputs, and they don't all report valid, don't save
return;
}
startDate.setHours(this._timeValue.from.hours);
startDate.setMinutes(this._timeValue.from.minutes);
endDate.setHours(this._timeValue.to.hours);
@@ -281,7 +294,8 @@ export class DateRangePicker extends LitElement {
private _handleChangeTime(ev: ValueChangedEvent<string>) {
ev.stopPropagation();
const time = ev.detail.value;
const type = (ev.target as HaBaseTimeInput).id;
const target = ev.target as HaBaseTimeInput;
const type = target.id;
if (time) {
if (!this._timeValue) {
this._timeValue = {

View File

@@ -137,7 +137,7 @@ export class HaBaseTimeInput extends LitElement {
@property({ attribute: "placeholder-labels", type: Boolean })
public placeholderLabels = false;
@queryAll("ha-input") private _inputs?: HaInput[];
@queryAll("ha-input") private _inputs?: NodeListOf<HaInput>;
static shadowRootOptions = {
...LitElement.shadowRootOptions,
@@ -145,7 +145,9 @@ export class HaBaseTimeInput extends LitElement {
};
public reportValidity(): boolean {
return this._inputs?.every((input) => input.reportValidity()) ?? true;
const inputs = this._inputs;
if (!inputs) return true;
return [...inputs].every((input) => input.reportValidity());
}
protected render(): TemplateResult {

View File

@@ -21,6 +21,8 @@ export class HaTimeInput extends LitElement {
@property({ type: Boolean }) public required = false;
@property({ attribute: "auto-validate", type: Boolean }) autoValidate = false;
@property({ type: Boolean, attribute: "enable-second" })
public enableSecond = false;
@@ -71,6 +73,7 @@ export class HaTimeInput extends LitElement {
.clearable=${this.clearable && this.value !== undefined}
.helper=${this.helper}
.placeholderLabels=${this.placeholderLabels}
.autoValidate=${this.autoValidate}
day-label="dd"
hour-label="hh"
min-label="mm"
@@ -86,6 +89,7 @@ export class HaTimeInput extends LitElement {
const useAMPM = useAmPm(this.locale);
let value: string | undefined;
let updateHours = 0;
// An undefined eventValue means the time selector is being cleared,
// the `value` variable will (intentionally) be left undefined.
@@ -97,6 +101,8 @@ export class HaTimeInput extends LitElement {
) {
let hours = eventValue.hours || 0;
if (eventValue && useAMPM) {
updateHours =
hours >= 12 && hours < 24 ? hours - 12 : hours === 0 ? 12 : 0;
if (eventValue.amPm === "PM" && hours < 12) {
hours += 12;
}
@@ -115,6 +121,17 @@ export class HaTimeInput extends LitElement {
}`;
}
if (updateHours) {
// If the user entered a 24hr time in a 12hr input, we need to refresh the
// input to ensure it resets back to the 12hr equivalent.
this.updateComplete.then(() => {
const input = this._input;
if (input) {
input.hours = updateHours;
}
});
}
if (value === this.value) {
return;
}