mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 06:17:20 +00:00
Convert time inputs to Lit + mwc (#11609)
This commit is contained in:
parent
5f43715dd8
commit
ed001fb10b
@ -1,5 +1,5 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||||
import { ForDict } from "../../data/automation";
|
import type { ForDict } from "../../data/automation";
|
||||||
|
|
||||||
export const createDurationData = (
|
export const createDurationData = (
|
||||||
duration: string | number | ForDict | undefined
|
duration: string | number | ForDict | undefined
|
||||||
@ -19,6 +19,9 @@ export const createDurationData = (
|
|||||||
}
|
}
|
||||||
return { seconds: duration };
|
return { seconds: duration };
|
||||||
}
|
}
|
||||||
|
if (!("days" in duration)) {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
const { days, minutes, seconds, milliseconds } = duration;
|
const { days, minutes, seconds, milliseconds } = duration;
|
||||||
let hours = duration.hours || 0;
|
let hours = duration.hours || 0;
|
||||||
hours = (hours || 0) + (days || 0) * 24;
|
hours = (hours || 0) + (days || 0) * 24;
|
||||||
|
308
src/components/ha-base-time-input.ts
Normal file
308
src/components/ha-base-time-input.ts
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import { LitElement, html, TemplateResult, css } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import "@material/mwc-select/mwc-select";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "./ha-textfield";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
|
||||||
|
export interface TimeChangedEvent {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
milliseconds: number;
|
||||||
|
amPm?: "AM" | "PM";
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-base-time-input")
|
||||||
|
export class HaBaseTimeInput extends LitElement {
|
||||||
|
/**
|
||||||
|
* Label for the input
|
||||||
|
*/
|
||||||
|
@property() label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* auto validate time inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) autoValidate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 12 or 24 hr format
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) format: 12 | 24 = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables the inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) disabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hour
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) hours = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minute
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) minutes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) seconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* milli second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) milliseconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the hour input
|
||||||
|
*/
|
||||||
|
@property() hourLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the min input
|
||||||
|
*/
|
||||||
|
@property() minLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the sec input
|
||||||
|
*/
|
||||||
|
@property() secLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the milli sec input
|
||||||
|
*/
|
||||||
|
@property() millisecLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableSecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the milli sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableMillisecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* limit hours input
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) noHoursLimit = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM or PM
|
||||||
|
*/
|
||||||
|
@property() amPm: "AM" | "PM" = "AM";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted time string
|
||||||
|
*/
|
||||||
|
@property() value?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||||
|
<div class="time-input-wrap">
|
||||||
|
<ha-textfield
|
||||||
|
id="hour"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this.hours}
|
||||||
|
.label=${this.hourLabel}
|
||||||
|
name="hours"
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
no-spinner
|
||||||
|
required
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
.max=${this._hourMax}
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
suffix=":"
|
||||||
|
class="hasSuffix"
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
id="min"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.minutes)}
|
||||||
|
.label=${this.minLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="minutes"
|
||||||
|
no-spinner
|
||||||
|
required
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableSecond ? ":" : ""}
|
||||||
|
class=${this.enableSecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this.enableSecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="sec"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.seconds)}
|
||||||
|
.label=${this.secLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="seconds"
|
||||||
|
no-spinner
|
||||||
|
required
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||||
|
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.enableMillisecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="millisec"
|
||||||
|
type="number"
|
||||||
|
.value=${this._formatValue(this.milliseconds, 3)}
|
||||||
|
.label=${this.millisecLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="milliseconds"
|
||||||
|
no-spinner
|
||||||
|
required
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="3"
|
||||||
|
max="999"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.format === 24
|
||||||
|
? ""
|
||||||
|
: html`<mwc-select
|
||||||
|
required
|
||||||
|
.value=${this.amPm}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
name="amPm"
|
||||||
|
naturalMenuWidth
|
||||||
|
fixedMenuPosition
|
||||||
|
@selected=${this._valueChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
|
</mwc-select>`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev) {
|
||||||
|
this[ev.target.name] =
|
||||||
|
ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
|
||||||
|
const value: TimeChangedEvent = {
|
||||||
|
hours: this.hours,
|
||||||
|
minutes: this.minutes,
|
||||||
|
seconds: this.seconds,
|
||||||
|
milliseconds: this.milliseconds,
|
||||||
|
};
|
||||||
|
if (this.format === 12) {
|
||||||
|
value.amPm = this.amPm;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onFocus(ev) {
|
||||||
|
ev.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time fragments
|
||||||
|
*/
|
||||||
|
private _formatValue(value: number, padding = 2) {
|
||||||
|
return value.toString().padStart(padding, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24 hour format has a max hr of 23
|
||||||
|
*/
|
||||||
|
private get _hourMax() {
|
||||||
|
if (this.noHoursLimit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.format === 12) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.time-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
--text-field-appearance: none;
|
||||||
|
--text-field-padding: 0 4px;
|
||||||
|
--text-field-suffix-padding-left: 2px;
|
||||||
|
--text-field-suffix-padding-right: 0;
|
||||||
|
--text-field-text-align: center;
|
||||||
|
}
|
||||||
|
ha-textfield.hasSuffix {
|
||||||
|
--text-field-padding: 0 0 0 4px;
|
||||||
|
}
|
||||||
|
ha-textfield:first-child {
|
||||||
|
--text-field-border-top-left-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
ha-textfield:last-child {
|
||||||
|
--text-field-border-top-right-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
mwc-select {
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family: var(
|
||||||
|
--mdc-typography-body2-font-family,
|
||||||
|
var(--mdc-typography-font-family, Roboto, sans-serif)
|
||||||
|
);
|
||||||
|
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
|
||||||
|
line-height: var(--mdc-typography-body2-line-height, 1.25rem);
|
||||||
|
font-weight: var(--mdc-typography-body2-font-weight, 400);
|
||||||
|
letter-spacing: var(
|
||||||
|
--mdc-typography-body2-letter-spacing,
|
||||||
|
0.0178571429em
|
||||||
|
);
|
||||||
|
text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
|
||||||
|
text-transform: var(--mdc-typography-body2-text-transform, inherit);
|
||||||
|
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-base-time-input": HaBaseTimeInput;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import "./paper-time-input";
|
import "./ha-base-time-input";
|
||||||
|
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||||
|
|
||||||
export interface HaDurationData {
|
export interface HaDurationData {
|
||||||
hours?: number;
|
hours?: number;
|
||||||
@ -32,110 +33,69 @@ class HaDurationInput extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-time-input
|
<ha-base-time-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.autoValidate=${this.required}
|
.autoValidate=${this.required}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
error-message="Required"
|
errorMessage="Required"
|
||||||
enable-second
|
enableSecond
|
||||||
.enableMillisecond=${this.enableMillisecond}
|
.enableMillisecond=${this.enableMillisecond}
|
||||||
format="24"
|
format="24"
|
||||||
.hour=${this._parseDuration(this._hours)}
|
.hours=${this._hours}
|
||||||
.min=${this._parseDuration(this._minutes)}
|
.minutes=${this._minutes}
|
||||||
.sec=${this._parseDuration(this._seconds)}
|
.seconds=${this._seconds}
|
||||||
.millisec=${this._parseDurationMillisec(this._milliseconds)}
|
.milliseconds=${this._milliseconds}
|
||||||
@hour-changed=${this._hourChanged}
|
@value-changed=${this._durationChanged}
|
||||||
@min-changed=${this._minChanged}
|
noHoursLimit
|
||||||
@sec-changed=${this._secChanged}
|
hourLabel="hh"
|
||||||
@millisec-changed=${this._millisecChanged}
|
minLabel="mm"
|
||||||
float-input-labels
|
secLabel="ss"
|
||||||
no-hours-limit
|
millisecLabel="ms"
|
||||||
always-float-input-labels
|
></ha-base-time-input>
|
||||||
hour-label="hh"
|
|
||||||
min-label="mm"
|
|
||||||
sec-label="ss"
|
|
||||||
millisec-label="ms"
|
|
||||||
></paper-time-input>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _hours() {
|
private get _hours() {
|
||||||
return this.data && this.data.hours ? Number(this.data.hours) : 0;
|
return this.data?.hours ? Number(this.data.hours) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _minutes() {
|
private get _minutes() {
|
||||||
return this.data && this.data.minutes ? Number(this.data.minutes) : 0;
|
return this.data?.minutes ? Number(this.data.minutes) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _seconds() {
|
private get _seconds() {
|
||||||
return this.data && this.data.seconds ? Number(this.data.seconds) : 0;
|
return this.data?.seconds ? Number(this.data.seconds) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _milliseconds() {
|
private get _milliseconds() {
|
||||||
return this.data && this.data.milliseconds
|
return this.data?.milliseconds ? Number(this.data.milliseconds) : 0;
|
||||||
? Number(this.data.milliseconds)
|
|
||||||
: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseDuration(value) {
|
private _durationChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
|
||||||
return value.toString().padStart(2, "0");
|
ev.stopPropagation();
|
||||||
}
|
const value = { ...ev.detail.value };
|
||||||
|
|
||||||
private _parseDurationMillisec(value) {
|
if (!this.enableMillisecond && !value.milliseconds) {
|
||||||
return value.toString().padStart(3, "0");
|
// @ts-ignore
|
||||||
}
|
delete value.milliseconds;
|
||||||
|
} else if (value.milliseconds > 999) {
|
||||||
private _hourChanged(ev) {
|
value.seconds += Math.floor(value.milliseconds / 1000);
|
||||||
this._durationChanged(ev, "hours");
|
value.milliseconds %= 1000;
|
||||||
}
|
|
||||||
|
|
||||||
private _minChanged(ev) {
|
|
||||||
this._durationChanged(ev, "minutes");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _secChanged(ev) {
|
|
||||||
this._durationChanged(ev, "seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _millisecChanged(ev) {
|
|
||||||
this._durationChanged(ev, "milliseconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _durationChanged(ev, unit) {
|
|
||||||
let value = Number(ev.detail.value);
|
|
||||||
|
|
||||||
if (value === this[`_${unit}`]) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let hours = this._hours;
|
if (value.seconds > 59) {
|
||||||
let minutes = this._minutes;
|
value.minutes += Math.floor(value.seconds / 60);
|
||||||
|
value.seconds %= 60;
|
||||||
if (unit === "seconds" && value > 59) {
|
|
||||||
minutes += Math.floor(value / 60);
|
|
||||||
value %= 60;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit === "minutes" && value > 59) {
|
if (value.minutes > 59) {
|
||||||
hours += Math.floor(value / 60);
|
value.hours += Math.floor(value.minutes / 60);
|
||||||
value %= 60;
|
value.minutes %= 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newValue: HaDurationData = {
|
|
||||||
hours,
|
|
||||||
minutes,
|
|
||||||
seconds: this._seconds,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.enableMillisecond || this._milliseconds) {
|
|
||||||
newValue.milliseconds = this._milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
newValue[unit] = value;
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: newValue,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ export class HaTimeSelector extends LitElement {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
hide-label
|
|
||||||
enable-second
|
enable-second
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
`;
|
`;
|
||||||
|
@ -45,6 +45,29 @@ export class HaTextField extends TextFieldBase {
|
|||||||
.mdc-text-field__input {
|
.mdc-text-field__input {
|
||||||
width: var(--ha-textfield-input-width, 100%);
|
width: var(--ha-textfield-input-width, 100%);
|
||||||
}
|
}
|
||||||
|
.mdc-text-field:not(.mdc-text-field--with-leading-icon) {
|
||||||
|
padding: var(--text-field-padding, 0px 16px);
|
||||||
|
}
|
||||||
|
.mdc-text-field__affix--suffix {
|
||||||
|
padding-left: var(--text-field-suffix-padding-left, 12px);
|
||||||
|
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: var(--text-field-text-align);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
:host([no-spinner]) input::-webkit-outer-spin-button,
|
||||||
|
:host([no-spinner]) input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
:host([no-spinner]) input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@ import { html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { useAmPm } from "../common/datetime/use_am_pm";
|
import { useAmPm } from "../common/datetime/use_am_pm";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import "./paper-time-input";
|
import "./ha-base-time-input";
|
||||||
import { FrontendLocaleData } from "../data/translation";
|
import { FrontendLocaleData } from "../data/translation";
|
||||||
|
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||||
|
|
||||||
@customElement("ha-time-input")
|
@customElement("ha-time-input")
|
||||||
export class HaTimeInput extends LitElement {
|
export class HaTimeInput extends LitElement {
|
||||||
@property() public locale!: FrontendLocaleData;
|
@property({ attribute: false }) public locale!: FrontendLocaleData;
|
||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@ -15,9 +16,6 @@ export class HaTimeInput extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "hide-label" }) public hideLabel =
|
|
||||||
false;
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "enable-second" })
|
@property({ type: Boolean, attribute: "enable-second" })
|
||||||
public enableSecond = false;
|
public enableSecond = false;
|
||||||
|
|
||||||
@ -35,40 +33,44 @@ export class HaTimeInput extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-time-input
|
<ha-base-time-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.hour=${hours}
|
.hours=${Number(hours)}
|
||||||
.min=${parts[1]}
|
.minutes=${Number(parts[1])}
|
||||||
.sec=${parts[2]}
|
.seconds=${Number(parts[2])}
|
||||||
.format=${useAMPM ? 12 : 24}
|
.format=${useAMPM ? 12 : 24}
|
||||||
.amPm=${useAMPM && (numberHours >= 12 ? "PM" : "AM")}
|
.amPm=${useAMPM && (numberHours >= 12 ? "PM" : "AM")}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@change=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
@am-pm-changed=${this._timeChanged}
|
|
||||||
.hideLabel=${this.hideLabel}
|
|
||||||
.enableSecond=${this.enableSecond}
|
.enableSecond=${this.enableSecond}
|
||||||
></paper-time-input>
|
></ha-base-time-input>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _timeChanged(ev) {
|
private _timeChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
|
||||||
let value = ev.target.value;
|
ev.stopPropagation();
|
||||||
|
const eventValue = ev.detail.value;
|
||||||
|
|
||||||
const useAMPM = useAmPm(this.locale);
|
const useAMPM = useAmPm(this.locale);
|
||||||
let hours = Number(ev.target.hour || 0);
|
let hours = eventValue.hours || 0;
|
||||||
if (value && useAMPM) {
|
if (eventValue && useAMPM) {
|
||||||
if (ev.target.amPm === "PM" && hours < 12) {
|
if (eventValue.amPm === "PM" && hours < 12) {
|
||||||
hours += 12;
|
hours += 12;
|
||||||
}
|
}
|
||||||
if (ev.target.amPm === "AM" && hours === 12) {
|
if (eventValue.amPm === "AM" && hours === 12) {
|
||||||
hours = 0;
|
hours = 0;
|
||||||
}
|
}
|
||||||
value = `${hours.toString().padStart(2, "0")}:${ev.target.min || "00"}:${
|
|
||||||
ev.target.sec || "00"
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
const value = `${hours.toString().padStart(2, "0")}:${
|
||||||
|
eventValue.minutes ? eventValue.minutes.toString().padStart(2, "0") : "00"
|
||||||
|
}:${
|
||||||
|
eventValue.seconds ? eventValue.seconds.toString().padStart(2, "0") : "00"
|
||||||
|
}`;
|
||||||
|
|
||||||
if (value === this.value) {
|
if (value === this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
|
@ -1,497 +0,0 @@
|
|||||||
/**
|
|
||||||
Adapted from paper-time-input from
|
|
||||||
https://github.com/ryanburns23/paper-time-input
|
|
||||||
MIT Licensed. Copyright (c) 2017 Ryan Burns
|
|
||||||
|
|
||||||
`<paper-time-input>` Polymer element to accept a time with paper-input & paper-dropdown-menu
|
|
||||||
Inspired by the time input in google forms
|
|
||||||
|
|
||||||
### Styling
|
|
||||||
|
|
||||||
`<paper-time-input>` provides the following custom properties and mixins for styling:
|
|
||||||
|
|
||||||
Custom property | Description | Default
|
|
||||||
----------------|-------------|----------
|
|
||||||
`--paper-time-input-dropdown-ripple-color` | dropdown ripple color | `--primary-color`
|
|
||||||
`--paper-time-input-cotnainer` | Mixin applied to the inputs | `{}`
|
|
||||||
`--paper-time-dropdown-input-cotnainer` | Mixin applied to the dropdown input | `{}`
|
|
||||||
*/
|
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
export class PaperTimeInput extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
@apply --paper-font-common-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-input {
|
|
||||||
width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
--paper-input-container-input: {
|
|
||||||
/* Damn you firefox
|
|
||||||
* Needed to hide spin num in firefox
|
|
||||||
* http://stackoverflow.com/questions/3790935/can-i-hide-the-html5-number-input-s-spin-box
|
|
||||||
*/
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
@apply --paper-time-input-cotnainer;
|
|
||||||
}
|
|
||||||
--paper-input-container-input-webkit-spinner: {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
--paper-input-container-shared-input-style_-_-webkit-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-dropdown-menu {
|
|
||||||
width: 55px;
|
|
||||||
padding: 0;
|
|
||||||
/* Force ripple to use the whole container */
|
|
||||||
--paper-dropdown-menu-ripple: {
|
|
||||||
color: var(
|
|
||||||
--paper-time-input-dropdown-ripple-color,
|
|
||||||
var(--primary-color)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
--paper-input-container-input: {
|
|
||||||
@apply --paper-font-button;
|
|
||||||
text-align: center;
|
|
||||||
padding-left: 5px;
|
|
||||||
@apply --paper-time-dropdown-input-cotnainer;
|
|
||||||
}
|
|
||||||
--paper-input-container-underline: {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
--paper-input-container-underline-focus: {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-listbox {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
@apply --paper-font-caption;
|
|
||||||
color: var(
|
|
||||||
--paper-input-container-color,
|
|
||||||
var(--secondary-text-color)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-input-wrap {
|
|
||||||
@apply --layout-horizontal;
|
|
||||||
@apply --layout-no-wrap;
|
|
||||||
justify-content: var(--paper-time-input-justify-content, normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#millisec {
|
|
||||||
width: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-suffix {
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<label hidden$="[[hideLabel]]">[[label]]</label>
|
|
||||||
<div class="time-input-wrap">
|
|
||||||
<!-- Hour Input -->
|
|
||||||
<paper-input
|
|
||||||
id="hour"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
value="{{hour}}"
|
|
||||||
label="[[hourLabel]]"
|
|
||||||
on-change="_shouldFormatHour"
|
|
||||||
on-focus="_onFocus"
|
|
||||||
required
|
|
||||||
prevent-invalid-input
|
|
||||||
auto-validate="[[autoValidate]]"
|
|
||||||
maxlength="2"
|
|
||||||
max="[[_computeHourMax(format)]]"
|
|
||||||
min="0"
|
|
||||||
no-label-float$="[[!floatInputLabels]]"
|
|
||||||
always-float-label$="[[alwaysFloatInputLabels]]"
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
>
|
|
||||||
<span suffix slot="suffix">:</span>
|
|
||||||
</paper-input>
|
|
||||||
|
|
||||||
<!-- Min Input -->
|
|
||||||
<paper-input
|
|
||||||
class$="[[_computeClassNames(enableSecond)]]"
|
|
||||||
id="min"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
value="{{min}}"
|
|
||||||
label="[[minLabel]]"
|
|
||||||
on-change="_formatMin"
|
|
||||||
on-focus="_onFocus"
|
|
||||||
required
|
|
||||||
auto-validate="[[autoValidate]]"
|
|
||||||
prevent-invalid-input
|
|
||||||
maxlength="2"
|
|
||||||
max="59"
|
|
||||||
min="0"
|
|
||||||
no-label-float$="[[!floatInputLabels]]"
|
|
||||||
always-float-label$="[[alwaysFloatInputLabels]]"
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
>
|
|
||||||
<span hidden$="[[!enableSecond]]" suffix slot="suffix">:</span>
|
|
||||||
</paper-input>
|
|
||||||
|
|
||||||
<!-- Sec Input -->
|
|
||||||
<paper-input
|
|
||||||
class$="[[_computeClassNames(enableMillisecond)]]"
|
|
||||||
id="sec"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
value="{{sec}}"
|
|
||||||
label="[[secLabel]]"
|
|
||||||
on-change="_formatSec"
|
|
||||||
on-focus="_onFocus"
|
|
||||||
required
|
|
||||||
auto-validate="[[autoValidate]]"
|
|
||||||
prevent-invalid-input
|
|
||||||
maxlength="2"
|
|
||||||
max="59"
|
|
||||||
min="0"
|
|
||||||
no-label-float$="[[!floatInputLabels]]"
|
|
||||||
always-float-label$="[[alwaysFloatInputLabels]]"
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
hidden$="[[!enableSecond]]"
|
|
||||||
>
|
|
||||||
<span hidden$="[[!enableMillisecond]]" suffix slot="suffix">:</span>
|
|
||||||
</paper-input>
|
|
||||||
|
|
||||||
<!-- Millisec Input -->
|
|
||||||
<paper-input
|
|
||||||
id="millisec"
|
|
||||||
type="number"
|
|
||||||
value="{{millisec}}"
|
|
||||||
label="[[millisecLabel]]"
|
|
||||||
on-change="_formatMillisec"
|
|
||||||
on-focus="_onFocus"
|
|
||||||
required
|
|
||||||
auto-validate="[[autoValidate]]"
|
|
||||||
prevent-invalid-input
|
|
||||||
maxlength="3"
|
|
||||||
max="999"
|
|
||||||
min="0"
|
|
||||||
no-label-float$="[[!floatInputLabels]]"
|
|
||||||
always-float-label$="[[alwaysFloatInputLabels]]"
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
hidden$="[[!enableMillisecond]]"
|
|
||||||
>
|
|
||||||
</paper-input>
|
|
||||||
|
|
||||||
<!-- Dropdown Menu -->
|
|
||||||
<paper-dropdown-menu
|
|
||||||
id="dropdown"
|
|
||||||
required=""
|
|
||||||
hidden$="[[_equal(format, 24)]]"
|
|
||||||
no-label-float=""
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
attr-for-selected="name"
|
|
||||||
selected="{{amPm}}"
|
|
||||||
slot="dropdown-content"
|
|
||||||
>
|
|
||||||
<paper-item name="AM">AM</paper-item>
|
|
||||||
<paper-item name="PM">PM</paper-item>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Label for the input
|
|
||||||
*/
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
value: "Time",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* auto validate time inputs
|
|
||||||
*/
|
|
||||||
autoValidate: {
|
|
||||||
type: Boolean,
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* hides the label
|
|
||||||
*/
|
|
||||||
hideLabel: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* float the input labels
|
|
||||||
*/
|
|
||||||
floatInputLabels: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* always float the input labels
|
|
||||||
*/
|
|
||||||
alwaysFloatInputLabels: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 12 or 24 hr format
|
|
||||||
*/
|
|
||||||
format: {
|
|
||||||
type: Number,
|
|
||||||
value: 12,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* disables the inputs
|
|
||||||
*/
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* hour
|
|
||||||
*/
|
|
||||||
hour: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* minute
|
|
||||||
*/
|
|
||||||
min: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* second
|
|
||||||
*/
|
|
||||||
sec: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* milli second
|
|
||||||
*/
|
|
||||||
millisec: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Label for the hour input
|
|
||||||
*/
|
|
||||||
hourLabel: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Label for the min input
|
|
||||||
*/
|
|
||||||
minLabel: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Label for the sec input
|
|
||||||
*/
|
|
||||||
secLabel: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Label for the milli sec input
|
|
||||||
*/
|
|
||||||
millisecLabel: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* show the sec field
|
|
||||||
*/
|
|
||||||
enableSecond: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* show the milli sec field
|
|
||||||
*/
|
|
||||||
enableMillisecond: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* limit hours input
|
|
||||||
*/
|
|
||||||
noHoursLimit: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* AM or PM
|
|
||||||
*/
|
|
||||||
amPm: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
value: "AM",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Formatted time string
|
|
||||||
*/
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
readOnly: true,
|
|
||||||
computed: "_computeTime(min, hour, sec, millisec, amPm)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the inputs
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
validate() {
|
|
||||||
let valid = true;
|
|
||||||
// Validate hour & min fields
|
|
||||||
if (!this.$.hour.validate() || !this.$.min.validate()) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
// Validate second field
|
|
||||||
if (this.enableSecond && !this.$.sec.validate()) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
// Validate milli second field
|
|
||||||
if (this.enableMillisecond && !this.$.millisec.validate()) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
// Validate AM PM if 12 hour time
|
|
||||||
if (this.format === 12 && !this.$.dropdown.validate()) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create time string
|
|
||||||
*/
|
|
||||||
_computeTime(min, hour, sec, millisec, amPm) {
|
|
||||||
let str;
|
|
||||||
if (
|
|
||||||
hour ||
|
|
||||||
min ||
|
|
||||||
(sec && this.enableSecond) ||
|
|
||||||
(millisec && this.enableMillisecond)
|
|
||||||
) {
|
|
||||||
hour = hour || "00";
|
|
||||||
min = min || "00";
|
|
||||||
sec = sec || "00";
|
|
||||||
millisec = millisec || "000";
|
|
||||||
str = hour + ":" + min;
|
|
||||||
// add sec field
|
|
||||||
if (this.enableSecond && sec) {
|
|
||||||
str = str + ":" + sec;
|
|
||||||
}
|
|
||||||
// add milli sec field
|
|
||||||
if (this.enableMillisecond && millisec) {
|
|
||||||
str = str + ":" + millisec;
|
|
||||||
}
|
|
||||||
// No ampm on 24 hr time
|
|
||||||
if (this.format === 12) {
|
|
||||||
str = str + " " + amPm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFocus(ev) {
|
|
||||||
ev.target.inputElement.inputElement.select();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format milli sec
|
|
||||||
*/
|
|
||||||
_formatMillisec() {
|
|
||||||
if (this.millisec.toString().length === 1) {
|
|
||||||
this.millisec = this.millisec.toString().padStart(3, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format sec
|
|
||||||
*/
|
|
||||||
_formatSec() {
|
|
||||||
if (this.sec.toString().length === 1) {
|
|
||||||
this.sec = this.sec.toString().padStart(2, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format min
|
|
||||||
*/
|
|
||||||
_formatMin() {
|
|
||||||
if (this.min.toString().length === 1) {
|
|
||||||
this.min = this.min.toString().padStart(2, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format hour
|
|
||||||
*/
|
|
||||||
_shouldFormatHour() {
|
|
||||||
if (this.format === 24 && this.hour.toString().length === 1) {
|
|
||||||
this.hour = this.hour.toString().padStart(2, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 24 hour format has a max hr of 23
|
|
||||||
*/
|
|
||||||
_computeHourMax(format) {
|
|
||||||
if (this.noHoursLimit) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (format === 12) {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
return 23;
|
|
||||||
}
|
|
||||||
|
|
||||||
_equal(n1, n2) {
|
|
||||||
return n1 === n2;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeClassNames(hasSuffix) {
|
|
||||||
return hasSuffix ? " " : "no-suffix";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("paper-time-input", PaperTimeInput);
|
|
@ -43,7 +43,6 @@ class MoreInfoInputDatetime extends LitElement {
|
|||||||
: this.stateObj.state}
|
: this.stateObj.state}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
||||||
hide-label
|
|
||||||
@value-changed=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
@click=${this._stopEventPropagation}
|
@click=${this._stopEventPropagation}
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
|
@ -72,7 +72,6 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
|||||||
: stateObj.state}
|
: stateObj.state}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||||
hide-label
|
|
||||||
@value-changed=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
@click=${this._stopEventPropagation}
|
@click=${this._stopEventPropagation}
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user