mirror of
https://github.com/GeorgeSG/lovelace-time-picker-card.git
synced 2026-09-06 02:18:44 +00:00
Add hour formats. Fix and refactor style variables
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from 'lit-element';
|
||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
||||
import { Period } from '../types';
|
||||
|
||||
@customElement('time-period')
|
||||
export class TimePeriodComponent extends LitElement {
|
||||
static readonly EVENT_TOGGLE = 'toggle';
|
||||
|
||||
@property() private period!: Period;
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <div class="time-period-selector">
|
||||
<div class=${classMap(this.amClass)} @click=${this.onTimePeriodChange}>
|
||||
AM<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
<div class=${classMap(this.pmClass)} @click=${this.onTimePeriodChange}>
|
||||
PM<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
onTimePeriodChange(): void {
|
||||
const event = new CustomEvent(TimePeriodComponent.EVENT_TOGGLE);
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private get amClass(): ClassInfo {
|
||||
return { 'time-period': true, active: this.period === Period.AM };
|
||||
}
|
||||
|
||||
private get pmClass(): ClassInfo {
|
||||
return { 'time-period': true, active: this.period === Period.PM };
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.time-period-selector {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.time-period {
|
||||
width: 30px;
|
||||
padding: 8px;
|
||||
background: var(--tpc-elements-background-color);
|
||||
color: var(--tpc-text-color, #fff);
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time-period.active {
|
||||
background: var(--tpc-accent-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import { Direction } from '../types';
|
||||
|
||||
@customElement('time-unit')
|
||||
export class TimeUnitComponent extends LitElement {
|
||||
static readonly EVENT_UPDATE = 'update';
|
||||
|
||||
@property() private unit!: TimeUnit;
|
||||
|
||||
render(): TemplateResult {
|
||||
@@ -23,7 +25,7 @@ export class TimeUnitComponent extends LitElement {
|
||||
type="number"
|
||||
placeholder="MM"
|
||||
min="0"
|
||||
max="60"
|
||||
max=${this.unit.maxValue}
|
||||
.value="${this.unit.toString()}"
|
||||
@change=${this.onInputChange}
|
||||
/>
|
||||
@@ -43,7 +45,7 @@ export class TimeUnitComponent extends LitElement {
|
||||
}
|
||||
|
||||
private emitUpdate(): void {
|
||||
const event = new CustomEvent('update');
|
||||
const event = new CustomEvent(TimeUnitComponent.EVENT_UPDATE);
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
@@ -70,15 +72,16 @@ export class TimeUnitComponent extends LitElement {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: var(--tpc-icon-color);
|
||||
}
|
||||
|
||||
.time-input {
|
||||
width: 30px;
|
||||
padding: 8px 8px 6px;
|
||||
background: var(--time-picker-card-background-color);
|
||||
background: var(--tpc-elements-background-color);
|
||||
border: 0;
|
||||
border-bottom: 2px solid var(--time-picker-card-background-color);
|
||||
color: var(--text-color, #fff);
|
||||
border-bottom: 2px solid var(--tpc-elements-background-color);
|
||||
color: var(--tpc-text-color, #fff);
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
-moz-appearance: textfield;
|
||||
|
||||
@@ -2,7 +2,3 @@ import * as pkg from '../package.json';
|
||||
|
||||
export const CARD_VERSION = pkg.version;
|
||||
export const CARD_SIZE = 3;
|
||||
|
||||
export const STYLE_VARIABLES = {
|
||||
'--time-picker-card-background-color': 'rgb(37, 47, 68)',
|
||||
};
|
||||
|
||||
+23
-1
@@ -1,10 +1,32 @@
|
||||
import { TimeUnit } from './time-unit';
|
||||
import { HourMode } from '../types';
|
||||
|
||||
export class Hour extends TimeUnit {
|
||||
private static readonly DEFAULT_STEP = 1;
|
||||
private static readonly MAX = 24;
|
||||
|
||||
constructor(value: number, step = Hour.DEFAULT_STEP) {
|
||||
constructor(value: number, step = Hour.DEFAULT_STEP, private hourMode: HourMode) {
|
||||
super(value, step, Hour.MAX);
|
||||
}
|
||||
|
||||
get maxValue(): number {
|
||||
return this.hourMode || Hour.MAX;
|
||||
}
|
||||
|
||||
togglePeriod(): void {
|
||||
this.setValue(this.value + 12);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const value = this.hourMode === 12 ? (this.value + 12) % 12 : this.value;
|
||||
|
||||
return value < 10 ? `0${value}` : value.toString();
|
||||
}
|
||||
|
||||
protected isValidString(valueStr: string): boolean {
|
||||
const value = parseInt(valueStr);
|
||||
const limit = this.hourMode || this._limit;
|
||||
|
||||
return !isNaN(value) && value >= 0 && value <= limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@ import { TimeUnit } from './time-unit';
|
||||
|
||||
export class Minute extends TimeUnit {
|
||||
private static readonly DEFAULT_STEP = 5;
|
||||
private static readonly MAX = 50;
|
||||
private static readonly MAX = 60;
|
||||
|
||||
maxValue = Minute.MAX;
|
||||
|
||||
constructor(value: number, step = Minute.DEFAULT_STEP) {
|
||||
super(value, step, Minute.MAX);
|
||||
}
|
||||
|
||||
protected isValidString(valueStr: string): boolean {
|
||||
const value = parseInt(valueStr);
|
||||
return !isNaN(value) && value >= 0 && value <= this._limit;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-7
@@ -1,7 +1,23 @@
|
||||
import { Direction } from '../types';
|
||||
|
||||
export abstract class TimeUnit {
|
||||
constructor(private _value: number, private _step: number, private _limit: number) {}
|
||||
/**
|
||||
* Return true if the valueStr can be set as a value of this instance.
|
||||
*/
|
||||
protected abstract isValidString(valueStr: string): boolean;
|
||||
|
||||
/**
|
||||
* The max allowed value for this instance. Used for UI validation.
|
||||
*/
|
||||
abstract maxValue: number;
|
||||
|
||||
/**
|
||||
* Create a new instance of a TimeUnit
|
||||
* @param _value current value
|
||||
* @param _step how much to increase / decrease the value when step-changing
|
||||
* @param _limit value upper limit
|
||||
*/
|
||||
constructor(private _value: number, protected _step: number, protected _limit: number) {}
|
||||
|
||||
get value(): number {
|
||||
return this._value;
|
||||
@@ -29,12 +45,7 @@ export abstract class TimeUnit {
|
||||
return this.value < 10 ? `0${this.value}` : this.value.toString();
|
||||
}
|
||||
|
||||
private isValidString(valueStr: string): boolean {
|
||||
const value = parseInt(valueStr);
|
||||
return !isNaN(value) && value >= 0 && value <= this._limit;
|
||||
}
|
||||
|
||||
private setValue(newValue: number): void {
|
||||
protected setValue(newValue: number): void {
|
||||
if (newValue >= this._limit || newValue < 0) {
|
||||
newValue = (newValue + this._limit) % this._limit;
|
||||
}
|
||||
|
||||
+38
-14
@@ -9,12 +9,13 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from 'lit-element';
|
||||
import './components/time-period.component';
|
||||
import './components/time-unit.component';
|
||||
import { CARD_SIZE, CARD_VERSION, STYLE_VARIABLES } from './const';
|
||||
import { CARD_SIZE, CARD_VERSION } from './const';
|
||||
import { Hour } from './models/hour';
|
||||
import { Minute } from './models/minute';
|
||||
import { Partial } from './partials';
|
||||
import { TimePickerCardConfig } from './types';
|
||||
import { Period, TimePickerCardConfig } from './types';
|
||||
|
||||
console.info(
|
||||
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
||||
@@ -28,14 +29,7 @@ export class TimePickerCard extends LitElement {
|
||||
@property() private config!: TimePickerCardConfig;
|
||||
@property() private hour!: Hour;
|
||||
@property() private minute!: Minute;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
Object.entries(STYLE_VARIABLES).forEach(([variable, value]) =>
|
||||
this.style.setProperty(variable, value)
|
||||
);
|
||||
}
|
||||
@property() private period!: Period;
|
||||
|
||||
private get entity(): HassEntity | undefined {
|
||||
return this.hass.states[this.config.entity];
|
||||
@@ -49,6 +43,10 @@ export class TimePickerCard extends LitElement {
|
||||
return this.config.name || this.entity?.attributes.friendly_name;
|
||||
}
|
||||
|
||||
private get shouldShowPeriod(): boolean {
|
||||
return this.config.hour_mode === 12;
|
||||
}
|
||||
|
||||
render(): TemplateResult | null {
|
||||
if (!this.entity) {
|
||||
return Partial.error('Entity not found', this.config);
|
||||
@@ -66,16 +64,24 @@ export class TimePickerCard extends LitElement {
|
||||
}
|
||||
|
||||
const { hour, minute } = this.entity!.attributes;
|
||||
this.hour = new Hour(hour, this.config.hour_step);
|
||||
this.hour = new Hour(hour, this.config.hour_step, this.config.hour_mode);
|
||||
this.minute = new Minute(minute, this.config.minute_step);
|
||||
this.period = this.hour.value >= 12 ? Period.PM : Period.AM;
|
||||
|
||||
return html`
|
||||
<ha-card class="time-picker-ha-card">
|
||||
<ha-card>
|
||||
${this.shouldShowName ? Partial.header(this.name!) : ''}
|
||||
<div class="time-picker-content">
|
||||
<time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit>
|
||||
<div class="time-separator">:</div>
|
||||
<time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit>
|
||||
|
||||
${this.shouldShowPeriod
|
||||
? html`<time-period
|
||||
.period=${this.period}
|
||||
@toggle=${this.onPeriodToggle}
|
||||
></time-period>`
|
||||
: ''}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -90,6 +96,10 @@ export class TimePickerCard extends LitElement {
|
||||
throw new Error('You must set an entity');
|
||||
}
|
||||
|
||||
if (config.hour_mode && config.hour_mode !== 12 && config.hour_mode !== 24) {
|
||||
throw new Error('Invalid hour_mode: select either 12 or 24');
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@@ -97,6 +107,11 @@ export class TimePickerCard extends LitElement {
|
||||
return CARD_SIZE;
|
||||
}
|
||||
|
||||
private onPeriodToggle(): void {
|
||||
this.hour.togglePeriod();
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
private callHassService(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
throw new Error('Unable to update datetime');
|
||||
@@ -112,12 +127,21 @@ export class TimePickerCard extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.time-picker-ha-card {
|
||||
:host {
|
||||
--tpc-elements-background-color: var(
|
||||
--time-picker-elements-background-color,
|
||||
var(--dark-primary-color)
|
||||
);
|
||||
|
||||
--tpc-icon-color: var(--time-picker-icon-color, var(--primary-text-color));
|
||||
--tpc-text-color: var(--time-picker-text-color, #fff);
|
||||
--tpc-accent-color: var(--time-picker-accent-color, var(--accent-color));
|
||||
}
|
||||
|
||||
.time-picker-header {
|
||||
padding: 16px;
|
||||
background-color: var(--time-picker-card-background-color);
|
||||
color: var(--tpc-text-color, #fff);
|
||||
background-color: var(--tpc-elements-background-color);
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LovelaceCardConfig } from 'custom-card-helpers';
|
||||
export interface TimePickerCardConfig extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
hour_mode?: HourMode;
|
||||
hour_step?: number;
|
||||
minute_step?: number;
|
||||
hide?: TimePickerHideConfig;
|
||||
@@ -16,3 +17,10 @@ export enum Direction {
|
||||
UP = 'up',
|
||||
DOWN = 'down',
|
||||
}
|
||||
|
||||
export enum Period {
|
||||
AM = 'am',
|
||||
PM = 'pm',
|
||||
}
|
||||
|
||||
export type HourMode = 12 | 24 | undefined;
|
||||
|
||||
Reference in New Issue
Block a user