mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-18 16:19:46 +00:00
282 lines
7.2 KiB
TypeScript
282 lines
7.2 KiB
TypeScript
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`<div class="time-picker-container">
|
|
<ha-numeric-arrow-input
|
|
.disabled=${this.disabled}
|
|
.required=${this.required}
|
|
.min=${this._useAmPm ? 1 : 0}
|
|
.max=${this._useAmPm ? 12 : 23}
|
|
.step=${1}
|
|
.padStart=${2}
|
|
.value=${this._hours}
|
|
@value-changed=${this._hoursChanged}
|
|
.labelUp=${
|
|
// TODO: Localize
|
|
"Increase hours"
|
|
}
|
|
.labelDown=${
|
|
// TODO: Localize
|
|
"Decrease hours"
|
|
}
|
|
></ha-numeric-arrow-input>
|
|
<span class="time-picker-separator">:</span>
|
|
<ha-numeric-arrow-input
|
|
.disabled=${this.disabled}
|
|
.required=${this.required}
|
|
.min=${0}
|
|
.max=${59}
|
|
.step=${1}
|
|
.padStart=${2}
|
|
.labelUp=${
|
|
// TODO: Localize
|
|
"Increase minutes"
|
|
}
|
|
.labelDown=${
|
|
// TODO: Localize
|
|
"Decrease minutes"
|
|
}
|
|
.value=${this._minutes}
|
|
@value-changed=${this._minutesChanged}
|
|
></ha-numeric-arrow-input>
|
|
${this.enableSeconds
|
|
? html`
|
|
<span class="time-picker-separator">:</span>
|
|
<ha-numeric-arrow-input
|
|
.disabled=${this.disabled}
|
|
.required=${this.required}
|
|
.min=${0}
|
|
.max=${59}
|
|
.step=${1}
|
|
.padStart=${2}
|
|
.labelUp=${
|
|
// TODO: Localize
|
|
"Increase seconds"
|
|
}
|
|
.labelDown=${
|
|
// TODO: Localize
|
|
"Decrease seconds"
|
|
}
|
|
.value=${this._seconds}
|
|
@value-changed=${this._secondsChanged}
|
|
></ha-numeric-arrow-input>
|
|
`
|
|
: nothing}
|
|
${this._useAmPm
|
|
? html`
|
|
<ha-button @click=${this._toggleAmPm}>
|
|
${this._isPm ? "PM" : "AM"}
|
|
</ha-button>
|
|
`
|
|
: nothing}
|
|
</div>`;
|
|
}
|
|
|
|
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<ClampedValue>) {
|
|
ev.stopPropagation?.();
|
|
this._hours = ev.detail.value;
|
|
}
|
|
|
|
private _minutesChanged(ev: CustomEvent<ClampedValue>) {
|
|
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<ClampedValue>);
|
|
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<ClampedValue>);
|
|
const hourMax = this._useAmPm ? 12 : 23;
|
|
if (this._hours < hourMax) {
|
|
this._minutes = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private _secondsChanged(ev: CustomEvent<ClampedValue>) {
|
|
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<ClampedValue>);
|
|
this._seconds = 59;
|
|
}
|
|
|
|
if (ev.detail.value === 59) {
|
|
this._minutesChanged({
|
|
detail: clampValue({ value: this._minutes + 1, min: 0, max: 59 }),
|
|
} as CustomEvent<ClampedValue>);
|
|
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;
|
|
}
|
|
}
|