Skip to content

Commit

Permalink
Convert time inputs to Lit + mwc (#11609)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten committed Feb 9, 2022
1 parent 5f43715 commit ed001fb
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 600 deletions.
7 changes: 5 additions & 2 deletions src/common/datetime/create_duration_data.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down
308 changes: 308 additions & 0 deletions src/components/ha-base-time-input.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit ed001fb

Please sign in to comment.