mirror of
https://github.com/GeorgeSG/lovelace-time-picker-card.git
synced 2026-11-05 20:28:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d87e9afb | ||
|
|
e56dc41dcc | ||
|
|
4b5fc79630 | ||
|
|
6113ec9415 | ||
|
|
f306d95748 | ||
|
|
fe91b79320 | ||
|
|
0b1b87ed6e | ||
|
|
81a0f1a95a | ||
|
|
526a52f630 | ||
|
|
72d82d7c0a | ||
|
|
21f66a9066 |
@@ -117,12 +117,14 @@ layout:
|
|||||||
| hour_mode | `single`, `double` | **Optional** | Whether to show both AM/PM or just the current mode. In `single` mode, tap the value to change it. | `double` |
|
| hour_mode | `single`, `double` | **Optional** | Whether to show both AM/PM or just the current mode. In `single` mode, tap the value to change it. | `double` |
|
||||||
| align_controls | `left`, `center`, `right` | **Optional** | Horizontal alignment of the controls | `center` |
|
| align_controls | `left`, `center`, `right` | **Optional** | Horizontal alignment of the controls | `center` |
|
||||||
| name | `header`, `inside` | **Optional** | Whether to show the name as a header or inside the card | `header` |
|
| name | `header`, `inside` | **Optional** | Whether to show the name as a header or inside the card | `header` |
|
||||||
|
| embedded | boolean | **Optional** | Render with embedded style - disables padding, box shadow, and card header | `false` |
|
||||||
|
|
||||||
### Hide Object
|
### Hide Object
|
||||||
|
|
||||||
| Name | Type | Requirement | Description | Default |
|
| Name | Type | Requirement | Description | Default |
|
||||||
| ---- | ------- | ------------ | ------------------- | ------- |
|
| ---- | ------- | ------------ | ------------------- | ------- |
|
||||||
| name | boolean | **Optional** | Hides the card name | `false` |
|
| name | boolean | **Optional** | Hides the card name | `false` |
|
||||||
|
| seconds | boolean | **Optional** | Hides seconds input | `true` |
|
||||||
|
|
||||||
### Theme Variables
|
### Theme Variables
|
||||||
|
|
||||||
|
|||||||
Generated
+950
-573
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "time-picker-card",
|
"name": "time-picker-card",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"author": "Georgi Gardev <georgi@gardev.com>",
|
"author": "Georgi Gardev <georgi@gardev.com>",
|
||||||
"description": "Lovelace time-picker-card",
|
"description": "Lovelace time-picker-card",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -25,36 +25,36 @@
|
|||||||
"release-major": "npm version major && git push origin master --tags"
|
"release-major": "npm version major && git push origin master --tags"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"custom-card-helpers": "^1.6.4",
|
"custom-card-helpers": "^1.6.6",
|
||||||
"home-assistant-js-websocket": "^5.1.0",
|
"home-assistant-js-websocket": "^5.7.0",
|
||||||
"lit-element": "^2.3.1",
|
"lit-element": "^2.4.0",
|
||||||
"lit-html": "^1.2.1"
|
"lit-html": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.12.7",
|
||||||
"@open-wc/testing": "^2.5.16",
|
"@open-wc/testing": "^2.5.32",
|
||||||
"@rollup/plugin-commonjs": "^11.1.0",
|
"@rollup/plugin-commonjs": "^11.1.0",
|
||||||
"@rollup/plugin-json": "^4.0.3",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^7.1.1",
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||||
"@rollup/plugin-typescript": "^4.0.0",
|
"@rollup/plugin-typescript": "^4.1.2",
|
||||||
"@types/chai": "^4.2.11",
|
"@types/chai": "^4.2.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.30.0",
|
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
||||||
"@typescript-eslint/parser": "^2.30.0",
|
"@typescript-eslint/parser": "^2.34.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.15.0",
|
||||||
"eslint-plugin-prettier": "^3.1.3",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"karma": "^5.0.5",
|
"karma": "^5.2.3",
|
||||||
"karma-chai": "^0.1.0",
|
"karma-chai": "^0.1.0",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"karma-chrome-launcher": "^3.1.0",
|
||||||
"karma-mocha": "^2.0.1",
|
"karma-mocha": "^2.0.1",
|
||||||
"karma-rollup-preprocessor": "^7.0.5",
|
"karma-rollup-preprocessor": "^7.0.5",
|
||||||
"mocha": "^7.1.2",
|
"mocha": "^7.2.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.2.0",
|
||||||
"rollup": "^1.32.1",
|
"rollup": "^1.32.1",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-serve": "^1.0.1",
|
"rollup-plugin-serve": "^1.1.0",
|
||||||
"rollup-plugin-terser": "^5.3.0",
|
"rollup-plugin-terser": "^5.3.1",
|
||||||
"sinon": "^9.0.2",
|
"sinon": "^9.2.1",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.9.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const ENTITY_DOMAIN = 'input_datetime';
|
|||||||
// Config defaults
|
// Config defaults
|
||||||
export const DEFAULT_HOUR_STEP = 1;
|
export const DEFAULT_HOUR_STEP = 1;
|
||||||
export const DEFAULT_MINUTE_STEP = 5;
|
export const DEFAULT_MINUTE_STEP = 5;
|
||||||
|
export const DEFAULT_SECOND_STEP = 5;
|
||||||
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
|
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
|
||||||
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
|
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
|
||||||
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
|
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
|
||||||
|
|||||||
+43
-5
@@ -15,12 +15,18 @@ import {
|
|||||||
ENTITY_DOMAIN,
|
ENTITY_DOMAIN,
|
||||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||||
DEFAULT_LAYOUT_NAME,
|
DEFAULT_LAYOUT_NAME,
|
||||||
|
DEFAULT_SECOND_STEP,
|
||||||
} from './const';
|
} from './const';
|
||||||
import { TimePickerCardConfig, Layout, HourMode } from './types';
|
import { TimePickerCardConfig, Layout, HourMode } from './types';
|
||||||
|
|
||||||
@customElement('time-picker-card-editor')
|
@customElement('time-picker-card-editor')
|
||||||
export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor {
|
export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor {
|
||||||
private static readonly NUMBER_PROPERTIES = ['hour_step', 'minute_step', 'hour_mode'];
|
private static readonly NUMBER_PROPERTIES = [
|
||||||
|
'hour_step',
|
||||||
|
'minute_step',
|
||||||
|
'second_step',
|
||||||
|
'hour_mode',
|
||||||
|
];
|
||||||
private static readonly CONFIG_CHANGED_EVENT = 'config-changed';
|
private static readonly CONFIG_CHANGED_EVENT = 'config-changed';
|
||||||
|
|
||||||
@property({ type: Object }) hass!: HomeAssistant;
|
@property({ type: Object }) hass!: HomeAssistant;
|
||||||
@@ -65,7 +71,6 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
<ha-switch
|
<ha-switch
|
||||||
style="margin-left: 10px"
|
style="margin-left: 10px"
|
||||||
.checked="${!Boolean(this.config.hide?.name)}"
|
.checked="${!Boolean(this.config.hide?.name)}"
|
||||||
.configValue="${'hide_name'}"
|
|
||||||
@change="${this.onHideNameChange}"
|
@change="${this.onHideNameChange}"
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
Show name?
|
Show name?
|
||||||
@@ -101,11 +106,27 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
@value-changed=${this.onValueChange}
|
@value-changed=${this.onValueChange}
|
||||||
></paper-input>
|
></paper-input>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="side-by-side">
|
||||||
|
<div>
|
||||||
|
<ha-switch
|
||||||
|
style="margin-left: 10px"
|
||||||
|
.checked="${this.config.hide?.seconds === false}"
|
||||||
|
@change="${this.onHideSecondsChange}"
|
||||||
|
></ha-switch>
|
||||||
|
Show seconds?
|
||||||
|
</div>
|
||||||
|
<paper-input
|
||||||
|
type="number"
|
||||||
|
label="Second Step (Optional)"
|
||||||
|
.configValue=${'second_step'}
|
||||||
|
.value=${this.config.second_step || DEFAULT_SECOND_STEP}
|
||||||
|
@value-changed=${this.onValueChange}
|
||||||
|
></paper-input>
|
||||||
|
</div>
|
||||||
<div class="side-by-side">
|
<div class="side-by-side">
|
||||||
<div>
|
<div>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked="${this.config.hour_mode === 12}"
|
.checked="${this.config.hour_mode === 12}"
|
||||||
.configValue="${'hour_mode'}"
|
|
||||||
@change="${this.onHourModeChange}"
|
@change="${this.onHourModeChange}"
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
12-Hour mode
|
12-Hour mode
|
||||||
@@ -114,7 +135,6 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
? html`<div>
|
? html`<div>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked="${this.config.layout?.hour_mode === 'single'}"
|
.checked="${this.config.layout?.hour_mode === 'single'}"
|
||||||
.configValue="${'hour_mode_layout'}"
|
|
||||||
@change="${this.onHourModeLayoutChange}"
|
@change="${this.onHourModeLayoutChange}"
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
"Single" hour mode layout
|
"Single" hour mode layout
|
||||||
@@ -139,12 +159,20 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
<div>
|
<div>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked="${this.config.link_values}"
|
.checked="${this.config.link_values}"
|
||||||
.configValue="${'link_values'}"
|
|
||||||
@change="${this.onLinkValuesChange}"
|
@change="${this.onLinkValuesChange}"
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
Link Values
|
Link Values
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="side-by-side">
|
||||||
|
<div>
|
||||||
|
<ha-switch
|
||||||
|
.checked="${this.config.layout?.embedded}"
|
||||||
|
@change="${this.onEmbeddedChange}"
|
||||||
|
></ha-switch>
|
||||||
|
Embedded
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +192,11 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
this.dispatch(newConfig);
|
this.dispatch(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onHideSecondsChange({ target: { checked } }): void {
|
||||||
|
const newConfig = { ...this.config, hide: { seconds: !checked } };
|
||||||
|
this.dispatch(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
private onLayoutAlignChange({ detail: { value } }: CustomEvent): void {
|
private onLayoutAlignChange({ detail: { value } }: CustomEvent): void {
|
||||||
const layout = { ...this.config.layout, align_controls: value };
|
const layout = { ...this.config.layout, align_controls: value };
|
||||||
const newConfig = { ...this.config, layout };
|
const newConfig = { ...this.config, layout };
|
||||||
@@ -191,6 +224,11 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
this.dispatch(newConfig);
|
this.dispatch(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onEmbeddedChange({ target: { checked } }): void {
|
||||||
|
const newConfig = { ...this.config, layout: { embedded: checked } };
|
||||||
|
this.dispatch(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
private onValueChange(e: CustomEvent): void {
|
private onValueChange(e: CustomEvent): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const property = (e.target as any).configValue;
|
const property = (e.target as any).configValue;
|
||||||
|
|||||||
@@ -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 { Hour } from './hour';
|
||||||
import { Minute } from './minute';
|
import { Minute } from './minute';
|
||||||
import { Direction } from '../types';
|
import { Second } from './second';
|
||||||
|
|
||||||
export class Time {
|
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 {
|
hourStep(direction: Direction): void {
|
||||||
this.hour.stepUpdate(direction);
|
this.hour.stepUpdate(direction);
|
||||||
@@ -17,7 +23,15 @@ export class Time {
|
|||||||
this.minute.stepUpdate(direction);
|
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 {
|
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 {
|
static headerName(title: string): TemplateResult {
|
||||||
return html`
|
return html`<div class="time-picker-header">${title}</div>`;
|
||||||
<div class="time-picker-header">
|
|
||||||
${title}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static nestedName(name: string, entity: HassEntity): TemplateResult {
|
static nestedName(name: string, entity: HassEntity): TemplateResult {
|
||||||
|
|||||||
+45
-12
@@ -10,22 +10,22 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from 'lit-element';
|
} from 'lit-element';
|
||||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
||||||
|
import './components/time-period.component';
|
||||||
|
import './components/time-unit.component';
|
||||||
import {
|
import {
|
||||||
CARD_SIZE,
|
CARD_SIZE,
|
||||||
CARD_VERSION,
|
CARD_VERSION,
|
||||||
|
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||||
DEFAULT_LAYOUT_HOUR_MODE,
|
DEFAULT_LAYOUT_HOUR_MODE,
|
||||||
ENTITY_DOMAIN,
|
ENTITY_DOMAIN,
|
||||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
|
||||||
} from './const';
|
} from './const';
|
||||||
|
import './editor';
|
||||||
import { Hour } from './models/hour';
|
import { Hour } from './models/hour';
|
||||||
import { Minute } from './models/minute';
|
import { Minute } from './models/minute';
|
||||||
|
import { Second } from './models/second';
|
||||||
import { Time } from './models/time';
|
import { Time } from './models/time';
|
||||||
import { Partial } from './partials';
|
import { Partial } from './partials';
|
||||||
import { Period, TimePickerCardConfig, Layout } from './types';
|
import { Layout, Period, TimePickerCardConfig } from './types';
|
||||||
|
|
||||||
import './components/time-period.component';
|
|
||||||
import './components/time-unit.component';
|
|
||||||
import './editor';
|
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
||||||
@@ -51,11 +51,16 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
return this.hass.states[this.config.entity];
|
return this.hass.states[this.config.entity];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get isEmbedded(): boolean {
|
||||||
|
return this.config.layout?.embedded === true;
|
||||||
|
}
|
||||||
|
|
||||||
private get hasNameInHeader(): boolean {
|
private get hasNameInHeader(): boolean {
|
||||||
return (
|
return (
|
||||||
Boolean(this.name) &&
|
Boolean(this.name) &&
|
||||||
Boolean(this.config.hide?.name) === false &&
|
Boolean(this.config.hide?.name) === false &&
|
||||||
this.config.layout?.name !== Layout.Name.INSIDE
|
this.config.layout?.name !== Layout.Name.INSIDE &&
|
||||||
|
Boolean(this.config.layout?.embedded) === false // embedded layout disables name in header
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
return (
|
return (
|
||||||
Boolean(this.name) &&
|
Boolean(this.name) &&
|
||||||
Boolean(this.config.hide?.name) === false &&
|
Boolean(this.config.hide?.name) === false &&
|
||||||
this.config.layout?.name === Layout.Name.INSIDE
|
(this.config.layout?.name === Layout.Name.INSIDE || Boolean(this.config.layout?.embedded))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,10 +84,17 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
return this.config.layout?.align_controls ?? DEFAULT_LAYOUT_ALIGN_CONTROLS;
|
return this.config.layout?.align_controls ?? DEFAULT_LAYOUT_ALIGN_CONTROLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get haCardClass(): ClassInfo {
|
||||||
|
return {
|
||||||
|
embedded: this.isEmbedded,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private get rowClass(): ClassInfo {
|
private get rowClass(): ClassInfo {
|
||||||
return {
|
return {
|
||||||
'time-picker-row': true,
|
'time-picker-row': true,
|
||||||
'with-header-name': this.hasNameInHeader,
|
'with-header-name': this.hasNameInHeader,
|
||||||
|
embedded: this.isEmbedded,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +121,15 @@ 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 hourInstance = new Hour(hour, this.config.hour_step, this.config.hour_mode);
|
||||||
const minuteInstance = new Minute(minute, this.config.minute_step);
|
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;
|
this.period = hourInstance.value >= 12 ? Period.PM : Period.AM;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card class=${classMap(this.haCardClass)}>
|
||||||
${this.hasNameInHeader ? Partial.headerName(this.name!) : ''}
|
${this.hasNameInHeader ? Partial.headerName(this.name!) : ''}
|
||||||
<div class=${classMap(this.rowClass)}>
|
<div class=${classMap(this.rowClass)}>
|
||||||
${this.hasNameInside ? Partial.nestedName(this.name!, this.entity) : ''}
|
${this.hasNameInside ? Partial.nestedName(this.name!, this.entity) : ''}
|
||||||
@@ -133,7 +146,14 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
@stepChange=${this.onMinuteStepChange}
|
@stepChange=${this.onMinuteStepChange}
|
||||||
@update=${this.callHassService}
|
@update=${this.callHassService}
|
||||||
></time-unit>
|
></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
|
${this.shouldShowPeriod
|
||||||
? html`<time-period
|
? html`<time-period
|
||||||
.period=${this.period}
|
.period=${this.period}
|
||||||
@@ -182,6 +202,11 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
this.callHassService();
|
this.callHassService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onSecondStepChange(event: CustomEvent): void {
|
||||||
|
this.time.secondStep(event.detail.direction);
|
||||||
|
this.callHassService();
|
||||||
|
}
|
||||||
|
|
||||||
private callHassService(): Promise<void> {
|
private callHassService(): Promise<void> {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
throw new Error('Unable to update datetime');
|
throw new Error('Unable to update datetime');
|
||||||
@@ -209,6 +234,10 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
--tpc-border-radius: var(--time-picker-border-radius, var(--ha-card-border-radius, 4px));
|
--tpc-border-radius: var(--time-picker-border-radius, var(--ha-card-border-radius, 4px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-card.embedded {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.time-picker-header {
|
.time-picker-header {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
color: var(--tpc-text-color);
|
color: var(--tpc-text-color);
|
||||||
@@ -226,6 +255,10 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-picker-row.embedded {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.time-picker-row.with-header-name {
|
.time-picker-row.with-header-name {
|
||||||
padding: 8px 16px 16px;
|
padding: 8px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface TimePickerCardConfig extends LovelaceCardConfig {
|
|||||||
hour_mode?: HourMode;
|
hour_mode?: HourMode;
|
||||||
hour_step?: number;
|
hour_step?: number;
|
||||||
minute_step?: number;
|
minute_step?: number;
|
||||||
|
second_step?: number;
|
||||||
layout?: TimePickerLayoutConfig;
|
layout?: TimePickerLayoutConfig;
|
||||||
hide?: TimePickerHideConfig;
|
hide?: TimePickerHideConfig;
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,7 @@ export interface TimePickerLayoutConfig {
|
|||||||
align_controls?: Layout.AlignControls;
|
align_controls?: Layout.AlignControls;
|
||||||
name?: Layout.Name;
|
name?: Layout.Name;
|
||||||
hour_mode?: Layout.HourMode;
|
hour_mode?: Layout.HourMode;
|
||||||
|
embedded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Layout {
|
export namespace Layout {
|
||||||
@@ -36,6 +38,7 @@ export namespace Layout {
|
|||||||
|
|
||||||
export interface TimePickerHideConfig {
|
export interface TimePickerHideConfig {
|
||||||
name?: boolean;
|
name?: boolean;
|
||||||
|
seconds?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Direction {
|
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