import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { clampValue } from "../data/number"; import { useAmPm } from "../common/datetime/use_am_pm"; import { fireEvent } from "../common/dom/fire_event"; import type { FrontendLocaleData } from "../data/translation"; import type { HomeAssistant } from "../types"; import type { ClampedValue } from "../data/number"; import "./ha-base-time-input"; import "./ha-button"; import "./ha-numeric-arrow-input"; @customElement("ha-time-picker") export class HaTimePicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public locale!: FrontendLocaleData; @property({ attribute: false }) public value?: string; @property({ attribute: false }) public disabled = false; @property({ attribute: false }) public required = false; @property({ attribute: false }) public enableSeconds = false; @state() private _hours = 0; @state() private _minutes = 0; @state() private _seconds = 0; @state() private _useAmPm = false; @state() private _isPm = false; protected firstUpdated(changedProperties: PropertyValues) { super.firstUpdated(changedProperties); this._useAmPm = useAmPm(this.locale); let hours = NaN; let minutes = NaN; let seconds = NaN; let isPm = false; if (this.value) { const parts = this.value?.split(":") || []; minutes = parts[1] ? Number(parts[1]) : 0; seconds = parts[2] ? Number(parts[2]) : 0; const hour24 = parts[0] ? Number(parts[0]) : 0; if (this._useAmPm) { if (hour24 === 0) { hours = 12; isPm = false; } else if (hour24 < 12) { hours = hour24; isPm = false; } else if (hour24 === 12) { hours = 12; isPm = true; } else { hours = hour24 - 12; isPm = true; } } else { hours = hour24; } } this._hours = hours; this._minutes = minutes; this._seconds = seconds; this._isPm = isPm; } protected render() { return html`
: ${this.enableSeconds ? html` : ` : nothing} ${this._useAmPm ? html` ${this._isPm ? "PM" : "AM"} ` : nothing}
`; } protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (changedProperties.has("_hours")) { this._timeUpdated(); } if (changedProperties.has("_minutes")) { this._timeUpdated(); } if (changedProperties.has("_seconds")) { this._timeUpdated(); } if (changedProperties.has("_useAmPm")) { this._timeUpdated(); } if (changedProperties.has("_isPm")) { this._timeUpdated(); } } private _hoursChanged(ev: CustomEvent) { ev.stopPropagation?.(); this._hours = ev.detail.value; } private _minutesChanged(ev: CustomEvent) { ev.stopPropagation?.(); this._minutes = ev.detail.value; if (ev.detail.clamped) { if (ev.detail.value === 0) { this._hoursChanged({ detail: clampValue({ value: this._hours - 1, min: this._useAmPm ? 1 : 0, max: this._useAmPm ? 12 : 23, }), } as CustomEvent); this._minutes = 59; } if (ev.detail.value === 59) { this._hoursChanged({ detail: clampValue({ value: this._hours + 1, min: this._useAmPm ? 1 : 0, max: this._useAmPm ? 12 : 23, }), } as CustomEvent); const hourMax = this._useAmPm ? 12 : 23; if (this._hours < hourMax) { this._minutes = 0; } } } } private _secondsChanged(ev: CustomEvent) { ev.stopPropagation?.(); this._seconds = ev.detail.value; if (ev.detail.clamped) { if (ev.detail.value === 0) { this._minutesChanged({ detail: clampValue({ value: this._minutes - 1, min: 0, max: 59 }), } as CustomEvent); this._seconds = 59; } if (ev.detail.value === 59) { this._minutesChanged({ detail: clampValue({ value: this._minutes + 1, min: 0, max: 59 }), } as CustomEvent); const hourMax = this._useAmPm ? 12 : 23; if (!(this._hours === hourMax && this._minutes === 59)) { this._seconds = 0; } } } } private _toggleAmPm() { this._isPm = !this._isPm; } private _timeUpdated() { let hour24 = this._hours; if (this._useAmPm) { if (this._hours === 12) { hour24 = this._isPm ? 12 : 0; } else { hour24 = this._isPm ? this._hours + 12 : this._hours; } } const timeParts = [ hour24.toString().padStart(2, "0"), this._minutes.toString().padStart(2, "0"), this._seconds.toString().padStart(2, "0"), ]; const time = timeParts.join(":"); if (time === this.value) { return; } this.value = time; fireEvent(this, "change"); fireEvent(this, "value-changed", { value: time }); } static styles = css` .time-picker-container { display: flex; flex-direction: row; align-items: center; gap: 4px; } .time-picker-separator { color: var(--primary-text-color); } `; } declare global { interface HTMLElementTagNameMap { "ha-time-picker": HaTimePicker; } }