diff --git a/README.md b/README.md
index dfdef51..5714eae 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/src/const.ts b/src/const.ts
index 4e0ee49..57db70d 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -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;
diff --git a/src/models/second.ts b/src/models/second.ts
new file mode 100644
index 0000000..8ac7d4b
--- /dev/null
+++ b/src/models/second.ts
@@ -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;
+ }
+}
diff --git a/src/models/time.ts b/src/models/time.ts
index afe91f9..1e99582 100644
--- a/src/models/time.ts
+++ b/src/models/time.ts
@@ -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}`;
}
}
diff --git a/src/partials.ts b/src/partials.ts
index 572ba3a..e0dc1ed 100644
--- a/src/partials.ts
+++ b/src/partials.ts
@@ -16,11 +16,7 @@ export class Partial {
}
static headerName(title: string): TemplateResult {
- return html`
-
- `;
+ return html``;
}
static nestedName(name: string, entity: HassEntity): TemplateResult {
diff --git a/src/time-picker-card.ts b/src/time-picker-card.ts
index 6aedf6a..85f0a66 100644
--- a/src/time-picker-card.ts
+++ b/src/time-picker-card.ts
@@ -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}
>
-
+ ${this.config.hide?.seconds === false
+ ? html`:
+ `
+ : ''}
${this.shouldShowPeriod
? html` {
if (!this.hass) {
throw new Error('Unable to update datetime');
diff --git a/src/types.ts b/src/types.ts
index 41e91f9..6ddef6c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -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 {
diff --git a/test/models/second.test.ts b/test/models/second.test.ts
new file mode 100644
index 0000000..7691210
--- /dev/null
+++ b/test/models/second.test.ts
@@ -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;
+ });
+ });
+});