mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47: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 { ForDict } from "../../data/automation";
|
||||
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||
import type { ForDict } from "../../data/automation";
|
||||
|
||||
export const createDurationData = (
|
||||
duration: string | number | ForDict | undefined
|
||||
@ -19,6 +19,9 @@ export const createDurationData = (
|
||||
}
|
||||
return { seconds: duration };
|
||||
}
|
||||
if (!("days" in duration)) {
|
||||
return duration;
|
||||
}
|
||||
const { days, minutes, seconds, milliseconds } = duration;
|
||||
let hours = duration.hours || 0;
|
||||
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 { customElement, property, query } from "lit/decorators";
|
||||
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 {
|
||||
hours?: number;
|
||||
@ -32,110 +33,69 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-time-input
|
||||
<ha-base-time-input
|
||||
.label=${this.label}
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
error-message="Required"
|
||||
enable-second
|
||||
errorMessage="Required"
|
||||
enableSecond
|
||||
.enableMillisecond=${this.enableMillisecond}
|
||||
format="24"
|
||||
.hour=${this._parseDuration(this._hours)}
|
||||
.min=${this._parseDuration(this._minutes)}
|
||||
.sec=${this._parseDuration(this._seconds)}
|
||||
.millisec=${this._parseDurationMillisec(this._milliseconds)}
|
||||
@hour-changed=${this._hourChanged}
|
||||
@min-changed=${this._minChanged}
|
||||
@sec-changed=${this._secChanged}
|
||||
@millisec-changed=${this._millisecChanged}
|
||||
float-input-labels
|
||||
no-hours-limit
|
||||
always-float-input-labels
|
||||
hour-label="hh"
|
||||
min-label="mm"
|
||||
sec-label="ss"
|
||||
millisec-label="ms"
|
||||
></paper-time-input>
|
||||
.hours=${this._hours}
|
||||
.minutes=${this._minutes}
|
||||
.seconds=${this._seconds}
|
||||
.milliseconds=${this._milliseconds}
|
||||
@value-changed=${this._durationChanged}
|
||||
noHoursLimit
|
||||
hourLabel="hh"
|
||||
minLabel="mm"
|
||||
secLabel="ss"
|
||||
millisecLabel="ms"
|
||||
></ha-base-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.data && this.data.minutes ? Number(this.data.minutes) : 0;
|
||||
return this.data?.minutes ? Number(this.data.minutes) : 0;
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.data && this.data.milliseconds
|
||||
? Number(this.data.milliseconds)
|
||||
: 0;
|
||||
return this.data?.milliseconds ? Number(this.data.milliseconds) : 0;
|
||||
}
|
||||
|
||||
private _parseDuration(value) {
|
||||
return value.toString().padStart(2, "0");
|
||||
}
|
||||
private _durationChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
private _parseDurationMillisec(value) {
|
||||
return value.toString().padStart(3, "0");
|
||||
}
|
||||
|
||||
private _hourChanged(ev) {
|
||||
this._durationChanged(ev, "hours");
|
||||
}
|
||||
|
||||
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;
|
||||
if (!this.enableMillisecond && !value.milliseconds) {
|
||||
// @ts-ignore
|
||||
delete value.milliseconds;
|
||||
} else if (value.milliseconds > 999) {
|
||||
value.seconds += Math.floor(value.milliseconds / 1000);
|
||||
value.milliseconds %= 1000;
|
||||
}
|
||||
|
||||
let hours = this._hours;
|
||||
let minutes = this._minutes;
|
||||
|
||||
if (unit === "seconds" && value > 59) {
|
||||
minutes += Math.floor(value / 60);
|
||||
value %= 60;
|
||||
if (value.seconds > 59) {
|
||||
value.minutes += Math.floor(value.seconds / 60);
|
||||
value.seconds %= 60;
|
||||
}
|
||||
|
||||
if (unit === "minutes" && value > 59) {
|
||||
hours += Math.floor(value / 60);
|
||||
value %= 60;
|
||||
if (value.minutes > 59) {
|
||||
value.hours += Math.floor(value.minutes / 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", {
|
||||
value: newValue,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ export class HaTimeSelector extends LitElement {
|
||||
.value=${this.value}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
hide-label
|
||||
enable-second
|
||||
></ha-time-input>
|
||||
`;
|
||||
|
@ -45,6 +45,29 @@ export class HaTextField extends TextFieldBase {
|
||||
.mdc-text-field__input {
|
||||
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 { useAmPm } from "../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./paper-time-input";
|
||||
import "./ha-base-time-input";
|
||||
import { FrontendLocaleData } from "../data/translation";
|
||||
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||
|
||||
@customElement("ha-time-input")
|
||||
export class HaTimeInput extends LitElement {
|
||||
@property() public locale!: FrontendLocaleData;
|
||||
@property({ attribute: false }) public locale!: FrontendLocaleData;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@ -15,9 +16,6 @@ export class HaTimeInput extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-label" }) public hideLabel =
|
||||
false;
|
||||
|
||||
@property({ type: Boolean, attribute: "enable-second" })
|
||||
public enableSecond = false;
|
||||
|
||||
@ -35,40 +33,44 @@ export class HaTimeInput extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<paper-time-input
|
||||
<ha-base-time-input
|
||||
.label=${this.label}
|
||||
.hour=${hours}
|
||||
.min=${parts[1]}
|
||||
.sec=${parts[2]}
|
||||
.hours=${Number(hours)}
|
||||
.minutes=${Number(parts[1])}
|
||||
.seconds=${Number(parts[2])}
|
||||
.format=${useAMPM ? 12 : 24}
|
||||
.amPm=${useAMPM && (numberHours >= 12 ? "PM" : "AM")}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._timeChanged}
|
||||
@am-pm-changed=${this._timeChanged}
|
||||
.hideLabel=${this.hideLabel}
|
||||
@value-changed=${this._timeChanged}
|
||||
.enableSecond=${this.enableSecond}
|
||||
></paper-time-input>
|
||||
></ha-base-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _timeChanged(ev) {
|
||||
let value = ev.target.value;
|
||||
private _timeChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
|
||||
ev.stopPropagation();
|
||||
const eventValue = ev.detail.value;
|
||||
|
||||
const useAMPM = useAmPm(this.locale);
|
||||
let hours = Number(ev.target.hour || 0);
|
||||
if (value && useAMPM) {
|
||||
if (ev.target.amPm === "PM" && hours < 12) {
|
||||
let hours = eventValue.hours || 0;
|
||||
if (eventValue && useAMPM) {
|
||||
if (eventValue.amPm === "PM" && hours < 12) {
|
||||
hours += 12;
|
||||
}
|
||||
if (ev.target.amPm === "AM" && hours === 12) {
|
||||
if (eventValue.amPm === "AM" && hours === 12) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
fireEvent(this, "change");
|
||||
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}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
||||
hide-label
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
|
@ -72,7 +72,6 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
: stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
hide-label
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
|
Loading…
x
Reference in New Issue
Block a user