mirror of
https://github.com/GeorgeSG/lovelace-time-picker-card.git
synced 2026-11-05 12:18:45 +00:00
Add optional seconds picker
This commit is contained in:
@@ -120,9 +120,10 @@ layout:
|
||||
|
||||
### Hide Object
|
||||
|
||||
| Name | Type | Requirement | Description | Default |
|
||||
| ---- | ------- | ------------ | ------------------- | ------- |
|
||||
| name | boolean | **Optional** | Hides the card name | `false` |
|
||||
| Name | Type | Requirement | Description | Default |
|
||||
| ---- | ------- | ------------ | ------------------- | ------- |
|
||||
| name | boolean | **Optional** | Hides the card name | `false` |
|
||||
| seconds | boolean | **Optional** | Hides seconds input | `true` |
|
||||
|
||||
### Theme Variables
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export const ENTITY_DOMAIN = 'input_datetime';
|
||||
// Config defaults
|
||||
export const DEFAULT_HOUR_STEP = 1;
|
||||
export const DEFAULT_MINUTE_STEP = 5;
|
||||
export const DEFAULT_SECOND_STEP = 5;
|
||||
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
|
||||
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
|
||||
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DEFAULT_SECOND_STEP } from '../const';
|
||||
import { TimeUnit } from './time-unit';
|
||||
import { Direction } from '../types';
|
||||
|
||||
/**
|
||||
* * Represents the second value of a datetime.
|
||||
*/
|
||||
export class Second extends TimeUnit {
|
||||
private static readonly VALUE_LIMIT = 60;
|
||||
|
||||
minValue = 0;
|
||||
maxValue = Second.VALUE_LIMIT - 1;
|
||||
|
||||
constructor(value: number, step = DEFAULT_SECOND_STEP) {
|
||||
super(value, step, Second.VALUE_LIMIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the seconds will overflow to a different hour when changed in {@param direction}.
|
||||
* @param direction
|
||||
*/
|
||||
willOverflow(direction: Direction): boolean {
|
||||
const newValue = direction === Direction.UP ? this.value + this._step : this.value - this._step;
|
||||
return newValue >= this._limit || newValue < 0;
|
||||
}
|
||||
}
|
||||
+17
-3
@@ -1,9 +1,15 @@
|
||||
import { Direction } from '../types';
|
||||
import { Hour } from './hour';
|
||||
import { Minute } from './minute';
|
||||
import { Direction } from '../types';
|
||||
import { Second } from './second';
|
||||
|
||||
export class Time {
|
||||
constructor(public hour: Hour, public minute: Minute, private _linkValues: boolean = false) {}
|
||||
constructor(
|
||||
public hour: Hour,
|
||||
public minute: Minute,
|
||||
public second: Second,
|
||||
private _linkValues: boolean = false
|
||||
) {}
|
||||
|
||||
hourStep(direction: Direction): void {
|
||||
this.hour.stepUpdate(direction);
|
||||
@@ -17,7 +23,15 @@ export class Time {
|
||||
this.minute.stepUpdate(direction);
|
||||
}
|
||||
|
||||
secondStep(direction: Direction): void {
|
||||
if (this._linkValues && this.second.willOverflow(direction)) {
|
||||
this.minute.stepUpdate(direction, 1);
|
||||
}
|
||||
|
||||
this.second.stepUpdate(direction);
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return `${this.hour.value}:${this.minute.value}`;
|
||||
return `${this.hour.value}:${this.minute.value}:${this.second.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -16,11 +16,7 @@ export class Partial {
|
||||
}
|
||||
|
||||
static headerName(title: string): TemplateResult {
|
||||
return html`
|
||||
<div class="time-picker-header">
|
||||
${title}
|
||||
</div>
|
||||
`;
|
||||
return html`<div class="time-picker-header">${title}</div>`;
|
||||
}
|
||||
|
||||
static nestedName(name: string, entity: HassEntity): TemplateResult {
|
||||
|
||||
+22
-9
@@ -10,22 +10,22 @@ import {
|
||||
TemplateResult,
|
||||
} from 'lit-element';
|
||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
||||
import './components/time-period.component';
|
||||
import './components/time-unit.component';
|
||||
import {
|
||||
CARD_SIZE,
|
||||
CARD_VERSION,
|
||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||
DEFAULT_LAYOUT_HOUR_MODE,
|
||||
ENTITY_DOMAIN,
|
||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||
} from './const';
|
||||
import './editor';
|
||||
import { Hour } from './models/hour';
|
||||
import { Minute } from './models/minute';
|
||||
import { Second } from './models/second';
|
||||
import { Time } from './models/time';
|
||||
import { Partial } from './partials';
|
||||
import { Period, TimePickerCardConfig, Layout } from './types';
|
||||
|
||||
import './components/time-period.component';
|
||||
import './components/time-unit.component';
|
||||
import './editor';
|
||||
import { Layout, Period, TimePickerCardConfig } from './types';
|
||||
|
||||
console.info(
|
||||
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
||||
@@ -109,10 +109,11 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
);
|
||||
}
|
||||
|
||||
const { hour, minute } = this.entity!.attributes;
|
||||
const { hour, minute, second } = this.entity!.attributes;
|
||||
const hourInstance = new Hour(hour, this.config.hour_step, this.config.hour_mode);
|
||||
const minuteInstance = new Minute(minute, this.config.minute_step);
|
||||
this.time = new Time(hourInstance, minuteInstance, this.config.link_values);
|
||||
const secondInstance = new Second(second, this.config.second_step);
|
||||
this.time = new Time(hourInstance, minuteInstance, secondInstance, this.config.link_values);
|
||||
this.period = hourInstance.value >= 12 ? Period.PM : Period.AM;
|
||||
|
||||
return html`
|
||||
@@ -133,7 +134,14 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
@stepChange=${this.onMinuteStepChange}
|
||||
@update=${this.callHassService}
|
||||
></time-unit>
|
||||
|
||||
${this.config.hide?.seconds === false
|
||||
? html`<div class="time-separator">:</div>
|
||||
<time-unit
|
||||
.unit=${this.time.second}
|
||||
@stepChange=${this.onSecondStepChange}
|
||||
@update=${this.callHassService}
|
||||
></time-unit>`
|
||||
: ''}
|
||||
${this.shouldShowPeriod
|
||||
? html`<time-period
|
||||
.period=${this.period}
|
||||
@@ -182,6 +190,11 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
private onSecondStepChange(event: CustomEvent): void {
|
||||
this.time.secondStep(event.detail.direction);
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
private callHassService(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
throw new Error('Unable to update datetime');
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface TimePickerCardConfig extends LovelaceCardConfig {
|
||||
hour_mode?: HourMode;
|
||||
hour_step?: number;
|
||||
minute_step?: number;
|
||||
second_step?: number;
|
||||
layout?: TimePickerLayoutConfig;
|
||||
hide?: TimePickerHideConfig;
|
||||
}
|
||||
@@ -36,6 +37,7 @@ export namespace Layout {
|
||||
|
||||
export interface TimePickerHideConfig {
|
||||
name?: boolean;
|
||||
seconds?: boolean;
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Second } from '../../src/models/Second';
|
||||
import { Direction } from '../../src/types';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('Second', () => {
|
||||
let second: Second;
|
||||
|
||||
context('with default config', () => {
|
||||
beforeEach(() => {
|
||||
second = new Second(2);
|
||||
});
|
||||
|
||||
it('returns value', () => {
|
||||
expect(second.value).to.equal(2);
|
||||
});
|
||||
|
||||
it('has correct max value', () => {
|
||||
expect(second.maxValue).to.equal(59);
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('renders values correctly', () => {
|
||||
second = new Second(0);
|
||||
expect(second.toString()).to.equal('00');
|
||||
|
||||
second = new Second(5);
|
||||
expect(second.toString()).to.equal('05');
|
||||
|
||||
second = new Second(59);
|
||||
expect(second.toString()).to.equal('59');
|
||||
});
|
||||
});
|
||||
|
||||
describe('stepUpdate', () => {
|
||||
it('updates up', () => {
|
||||
second.stepUpdate(Direction.UP);
|
||||
expect(second.value).to.equal(7);
|
||||
});
|
||||
|
||||
it('updates down', () => {
|
||||
second.stepUpdate(Direction.DOWN);
|
||||
expect(second.value).to.equal(57);
|
||||
});
|
||||
|
||||
it('goes up from 59 to 4', () => {
|
||||
second = new Second(59);
|
||||
second.stepUpdate(Direction.UP);
|
||||
expect(second.value).to.equal(4);
|
||||
});
|
||||
|
||||
it('goes down from 0 to 55', () => {
|
||||
second = new Second(0);
|
||||
second.stepUpdate(Direction.DOWN);
|
||||
expect(second.value).to.equal(55);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setStringValue', () => {
|
||||
it('sets value from valid string', () => {
|
||||
second.setStringValue('3');
|
||||
expect(second.value).to.equal(3);
|
||||
});
|
||||
|
||||
it("doesn't set value from invalid string", () => {
|
||||
second.setStringValue('test');
|
||||
expect(second.value).to.equal(2);
|
||||
});
|
||||
|
||||
it("doesn't set value from outside of scope", () => {
|
||||
second.setStringValue('60');
|
||||
expect(second.value).to.equal(2);
|
||||
|
||||
second.setStringValue('65');
|
||||
expect(second.value).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with a different step size', () => {
|
||||
beforeEach(() => {
|
||||
second = new Second(2, 15);
|
||||
});
|
||||
|
||||
it('goes up correctly', () => {
|
||||
second.stepUpdate(Direction.UP);
|
||||
expect(second.value).to.equal(17);
|
||||
|
||||
second.stepUpdate(Direction.UP);
|
||||
second.stepUpdate(Direction.UP);
|
||||
second.stepUpdate(Direction.UP);
|
||||
expect(second.value).to.equal(2);
|
||||
|
||||
second.stepUpdate(Direction.UP);
|
||||
expect(second.value).to.equal(17);
|
||||
});
|
||||
|
||||
it('goes down correclty', () => {
|
||||
second.stepUpdate(Direction.DOWN);
|
||||
expect(second.value).to.equal(47);
|
||||
});
|
||||
});
|
||||
|
||||
describe('will overflow', () => {
|
||||
it('returns true if the Seconds will overflow an hour up', () => {
|
||||
second.setStringValue('59');
|
||||
expect(second.willOverflow(Direction.UP)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns true if the Seconds will overflow an hour down', () => {
|
||||
second.setStringValue('0');
|
||||
expect(second.willOverflow(Direction.DOWN)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns true if the Seconds will not overflow', () => {
|
||||
second.setStringValue('55');
|
||||
expect(second.willOverflow(Direction.DOWN)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user