mirror of
https://github.com/GeorgeSG/lovelace-time-picker-card.git
synced 2026-11-03 11:48:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
526a52f630 | ||
|
|
72d82d7c0a | ||
|
|
21f66a9066 | ||
|
|
fe93d385e9 | ||
|
|
05e0952624 | ||
|
|
3c10a9ab44 | ||
|
|
9a2a57af5c |
@@ -15,7 +15,19 @@ Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_da
|
||||
|
||||
## Installation
|
||||
|
||||
Install using [HACS](https://hacs.xyz) or follow this [guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins)
|
||||
### HACS
|
||||
|
||||
Install using [HACS](https://hacs.xyz) and add the following to your config:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
- url: /hacsfiles/lovelace-time-picker-card/time-picker-card.js
|
||||
type: module
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Download time-picker-card.js from the [latest realease](https://github.com/GeorgeSG/lovelace-time-picker-card/releases/latest) and place it in your `config/www` folder. Add the following to your config:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
@@ -91,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` |
|
||||
@@ -116,13 +129,14 @@ layout:
|
||||
Time Picker Card will automatically pick up colors from your lovelace theme, but if you want to customize some of them,
|
||||
you can use the following variables in your theme's config file:
|
||||
|
||||
| Name | Default | Description |
|
||||
| ------------------------------------- | ---------------------------- | -------------------------------------- |
|
||||
| time-picker-elements-background-color | `var(--primary-color)` | Background color for header and inputs |
|
||||
| time-picker-icon-color | `var(--primary-text-color)` | Arrow color |
|
||||
| time-picker-text-color | `white` | Text color |
|
||||
| time-picker-accent-color | `var(--primary-color)` | AM / PM active color |
|
||||
| time-picker-off-color | `var(--disabled-text-color)` | AM / PM inactive color |
|
||||
| Name | Default | Description |
|
||||
| ------------------------------------- | ------------------------------ | -------------------------------------- |
|
||||
| time-picker-elements-background-color | `var(--primary-color)` | Background color for header and inputs |
|
||||
| time-picker-icon-color | `var(--primary-text-color)` | Arrow color |
|
||||
| time-picker-text-color | `white` | Text color |
|
||||
| time-picker-accent-color | `var(--primary-color)` | AM / PM active color |
|
||||
| time-picker-off-color | `var(--disabled-text-color)` | AM / PM inactive color |
|
||||
| time-picker-border-radius | `var(--ha-card-border-radius)` | Border radius of the card |
|
||||
|
||||
## Meta
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "time-picker-card",
|
||||
"version": "0.1.1",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -2546,9 +2546,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.get": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "time-picker-card",
|
||||
"version": "0.1.1",
|
||||
"version": "1.0.1",
|
||||
"author": "Georgi Gardev <georgi@gardev.com>",
|
||||
"description": "Lovelace time-picker-card",
|
||||
"keywords": [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+39
-20
@@ -61,14 +61,15 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
||||
@value-changed=${this.onValueChange}
|
||||
></paper-input>
|
||||
<div class="side-by-side">
|
||||
<ha-switch
|
||||
style="margin-left: 10px"
|
||||
.checked="${!Boolean(this.config.hide?.name)}"
|
||||
.configValue="${'hide_name'}"
|
||||
@change="${this.onHideNameChange}"
|
||||
>
|
||||
<div>
|
||||
<ha-switch
|
||||
style="margin-left: 10px"
|
||||
.checked="${!Boolean(this.config.hide?.name)}"
|
||||
.configValue="${'hide_name'}"
|
||||
@change="${this.onHideNameChange}"
|
||||
></ha-switch>
|
||||
Show name?
|
||||
</ha-switch>
|
||||
</div>
|
||||
<paper-dropdown-menu
|
||||
style="width: 100%"
|
||||
label="Name Position (Optional)"
|
||||
@@ -101,20 +102,24 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<ha-switch
|
||||
.checked="${this.config.hour_mode === 12}"
|
||||
.configValue="${'hour_mode'}"
|
||||
@change="${this.onHourModeChange}"
|
||||
>
|
||||
<div>
|
||||
<ha-switch
|
||||
.checked="${this.config.hour_mode === 12}"
|
||||
.configValue="${'hour_mode'}"
|
||||
@change="${this.onHourModeChange}"
|
||||
></ha-switch>
|
||||
12-Hour mode
|
||||
</ha-switch>
|
||||
<ha-switch
|
||||
.checked="${this.config.layout?.hour_mode === 'single'}"
|
||||
.configValue="${'hour_mode_layout'}"
|
||||
@change="${this.onHourModeLayoutChange}"
|
||||
>
|
||||
"Single" hour mode layout (in 12-Hour mode only)
|
||||
</ha-switch>
|
||||
</div>
|
||||
${this.config.hour_mode === 12
|
||||
? html`<div>
|
||||
<ha-switch
|
||||
.checked="${this.config.layout?.hour_mode === 'single'}"
|
||||
.configValue="${'hour_mode_layout'}"
|
||||
@change="${this.onHourModeLayoutChange}"
|
||||
></ha-switch>
|
||||
"Single" hour mode layout
|
||||
</div>`
|
||||
: ''}
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-dropdown-menu
|
||||
@@ -131,6 +136,14 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
||||
${Object.values(Layout.AlignControls).map((a) => html`<paper-item>${a}</paper-item>`)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<div>
|
||||
<ha-switch
|
||||
.checked="${this.config.link_values}"
|
||||
.configValue="${'link_values'}"
|
||||
@change="${this.onLinkValuesChange}"
|
||||
></ha-switch>
|
||||
Link Values
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
+32
-11
@@ -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`
|
||||
<ha-card>
|
||||
@@ -121,9 +122,17 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
${this.hasNameInside ? Partial.nestedName(this.name!, this.entity) : ''}
|
||||
|
||||
<div class=${classMap(this.contentClass)}>
|
||||
<time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit>
|
||||
<time-unit
|
||||
.unit=${this.time.hour}
|
||||
@stepChange=${this.onHourStepChange}
|
||||
@update=${this.callHassService}
|
||||
></time-unit>
|
||||
<div class="time-separator">:</div>
|
||||
<time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit>
|
||||
<time-unit
|
||||
.unit=${this.time.minute}
|
||||
@stepChange=${this.onMinuteStepChange}
|
||||
@update=${this.callHassService}
|
||||
></time-unit>
|
||||
|
||||
${this.shouldShowPeriod
|
||||
? html`<time-period
|
||||
@@ -159,7 +168,17 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private onPeriodToggle(): void {
|
||||
this.hour.togglePeriod();
|
||||
this.time.hour.togglePeriod();
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
private onHourStepChange(event: CustomEvent): void {
|
||||
this.time.hourStep(event.detail.direction);
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
private onMinuteStepChange(event: CustomEvent): void {
|
||||
this.time.minuteStep(event.detail.direction);
|
||||
this.callHassService();
|
||||
}
|
||||
|
||||
@@ -168,11 +187,9 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
throw new Error('Unable to update datetime');
|
||||
}
|
||||
|
||||
const time = `${this.hour.value}:${this.minute.value}:00`;
|
||||
|
||||
return this.hass.callService(ENTITY_DOMAIN, 'set_datetime', {
|
||||
entity_id: this.entity!.entity_id,
|
||||
time,
|
||||
time: this.time.value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,12 +205,16 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
||||
--tpc-text-color: var(--time-picker-text-color, #fff);
|
||||
--tpc-accent-color: var(--time-picker-accent-color, var(--primary-color));
|
||||
--tpc-off-color: var(--time-picker-off-color, var(--disabled-text-color));
|
||||
|
||||
--tpc-border-radius: var(--time-picker-border-radius, var(--ha-card-border-radius, 4px));
|
||||
}
|
||||
|
||||
.time-picker-header {
|
||||
padding: 16px;
|
||||
color: var(--tpc-text-color);
|
||||
background-color: var(--tpc-elements-background-color);
|
||||
border-top-left-radius: var(--tpc-border-radius);
|
||||
border-top-right-radius: var(--tpc-border-radius);
|
||||
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;
|
||||
link_values?: boolean;
|
||||
hour_mode?: HourMode;
|
||||
hour_step?: number;
|
||||
minute_step?: number;
|
||||
|
||||
@@ -99,4 +99,21 @@ describe('Minute', () => {
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user