diff --git a/README.md b/README.md index 8abad2e..dfdef51 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ layout: | type | string | **Required** | `custom:time-picker-card` | | | entity | string | **Required** | [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | | | name | string | **Optional** | Card name | Entity's `friendly_name` | +| link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` | | hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` | | hour_step | number | **Optional** | Hour change when clicking arrows | `1` | | minute_step | number | **Optional** | Minute change when clicking arrows | `5` | diff --git a/src/components/time-unit.component.ts b/src/components/time-unit.component.ts index e867c05..fb40003 100644 --- a/src/components/time-unit.component.ts +++ b/src/components/time-unit.component.ts @@ -16,6 +16,7 @@ import { Direction } from '../types'; @customElement('time-unit') export class TimeUnitComponent extends LitElement { static readonly EVENT_UPDATE = 'update'; + static readonly EVENT_STEP_CHANGE = 'stepChange'; @property() private unit!: TimeUnit; @@ -39,16 +40,12 @@ export class TimeUnitComponent extends LitElement { onInputChange({ target: { value } }: { target: HTMLInputElement }): void { this.unit.setStringValue(value); - this.emitUpdate(); + const event = new CustomEvent(TimeUnitComponent.EVENT_UPDATE); + this.dispatchEvent(event); } onStepChangerClick(direction: Direction): void { - this.unit.stepUpdate(direction); - this.emitUpdate(); - } - - private emitUpdate(): void { - const event = new CustomEvent(TimeUnitComponent.EVENT_UPDATE); + const event = new CustomEvent(TimeUnitComponent.EVENT_STEP_CHANGE, { detail: { direction } }); this.dispatchEvent(event); } diff --git a/src/editor.ts b/src/editor.ts index 7e5a74b..bcbc13f 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -61,14 +61,15 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit @value-changed=${this.onValueChange} >
- +
+ Show name? - +
- +
+ 12-Hour mode - - - "Single" hour mode layout (in 12-Hour mode only) - +
+ ${this.config.hour_mode === 12 + ? html`
+ + "Single" hour mode layout +
` + : ''}
html`${a}`)} +
+ + Link Values +
`; } @@ -173,6 +186,11 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit this.dispatch(newConfig); } + private onLinkValuesChange({ target: { checked } }): void { + const newConfig = { ...this.config, link_values: checked }; + this.dispatch(newConfig); + } + private onValueChange(e: CustomEvent): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any const property = (e.target as any).configValue; @@ -209,6 +227,7 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit return css` ha-switch { padding: 16px 0; + margin-right: 16px; } .side-by-side { display: flex; diff --git a/src/models/minute.ts b/src/models/minute.ts index d850087..dc9f4b4 100644 --- a/src/models/minute.ts +++ b/src/models/minute.ts @@ -1,5 +1,6 @@ import { DEFAULT_MINUTE_STEP } from '../const'; import { TimeUnit } from './time-unit'; +import { Direction } from '../types'; /** * * Represents the minute value of a datetime. @@ -13,4 +14,13 @@ export class Minute extends TimeUnit { constructor(value: number, step = DEFAULT_MINUTE_STEP) { super(value, step, Minute.VALUE_LIMIT); } + + /** + * Returns true if the minutes 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-unit.ts b/src/models/time-unit.ts index e6e5591..ba5a001 100644 --- a/src/models/time-unit.ts +++ b/src/models/time-unit.ts @@ -39,23 +39,27 @@ export abstract class TimeUnit { * Updates the value in {@param direction} by the step size specified in the constructoor. * @param direction */ - stepUpdate(direction: Direction): void { - direction === Direction.UP ? this.increment() : this.decrement(); + stepUpdate(direction: Direction, step: number = this._step): void { + direction === Direction.UP ? this.increment(step) : this.decrement(step); } toString(): string { return this.value < 10 ? `0${this.value}` : this.value.toString(); } - private increment(): void { - this.setValue(this.value + this._step); + private increment(step: number = this._step): void { + this.setValue(this.value + step); } - private decrement(): void { - this.setValue(this.value - this._step); + private decrement(step: number = this._step): void { + this.setValue(this.value - step); } protected setValue(newValue: number): void { + if (isNaN(newValue)) { + return; + } + if (newValue >= this._limit || newValue < 0) { newValue = (newValue + this._limit) % this._limit; } diff --git a/src/models/time.ts b/src/models/time.ts new file mode 100644 index 0000000..afe91f9 --- /dev/null +++ b/src/models/time.ts @@ -0,0 +1,23 @@ +import { Hour } from './hour'; +import { Minute } from './minute'; +import { Direction } from '../types'; + +export class Time { + constructor(public hour: Hour, public minute: Minute, private _linkValues: boolean = false) {} + + hourStep(direction: Direction): void { + this.hour.stepUpdate(direction); + } + + minuteStep(direction: Direction): void { + if (this._linkValues && this.minute.willOverflow(direction)) { + this.hour.stepUpdate(direction, 1); + } + + this.minute.stepUpdate(direction); + } + + get value(): string { + return `${this.hour.value}:${this.minute.value}`; + } +} diff --git a/src/time-picker-card.ts b/src/time-picker-card.ts index af11c4b..6aedf6a 100644 --- a/src/time-picker-card.ts +++ b/src/time-picker-card.ts @@ -19,6 +19,7 @@ import { } from './const'; import { Hour } from './models/hour'; import { Minute } from './models/minute'; +import { Time } from './models/time'; import { Partial } from './partials'; import { Period, TimePickerCardConfig, Layout } from './types'; @@ -43,8 +44,7 @@ window.customCards.push({ export class TimePickerCard extends LitElement implements LovelaceCard { @property({ type: Object }) hass!: HomeAssistant; @property() private config!: TimePickerCardConfig; - @property() private hour!: Hour; - @property() private minute!: Minute; + @property() private time!: Time; @property() private period!: Period; private get entity(): HassEntity | undefined { @@ -110,9 +110,10 @@ export class TimePickerCard extends LitElement implements LovelaceCard { } const { hour, minute } = this.entity!.attributes; - 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; + 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); + this.period = hourInstance.value >= 12 ? Period.PM : Period.AM; return html` @@ -121,9 +122,17 @@ export class TimePickerCard extends LitElement implements LovelaceCard { ${this.hasNameInside ? Partial.nestedName(this.name!, this.entity) : ''}
- +
:
- + ${this.shouldShowPeriod ? html` { expect(minute.value).to.equal(47); }); }); + + describe('will overflow', () => { + it('returns true if the minutes will overflow an hour up', () => { + minute.setStringValue('59'); + expect(minute.willOverflow(Direction.UP)).to.be.true; + }); + + it('returns true if the minutes will overflow an hour down', () => { + minute.setStringValue('0'); + expect(minute.willOverflow(Direction.DOWN)).to.be.true; + }); + + it('returns true if the minutes will not overflow', () => { + minute.setStringValue('55'); + expect(minute.willOverflow(Direction.DOWN)).to.be.false; + }); + }); });