Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pressed and released event to allow usage as momentary button #360

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Lovelace Button card for your entities.
## Features

- works with any toggleable entity
- 6 available actions on **tap** and/or **hold** and/or **double click**: `none`, `toggle`, `more-info`, `navigate`, `url` and `call-service`
- 6 available actions on **tap** and/or **hold** and/or **double click** and/or **press**and/or **release**: `none`, `toggle`, `more-info`, `navigate`, `url` and `call-service`
- state display (optional)
- custom color (optional), or based on light rgb value/temperature
- custom state definition with customizable color, icon and style (optional)
Expand Down Expand Up @@ -98,6 +98,8 @@ Lovelace Button card for your entities.
| `tap_action` | object | optional | See [Action](#Action) | Define the type of action on click, if undefined, toggle will be used. |
| `hold_action` | object | optional | See [Action](#Action) | Define the type of action on hold, if undefined, nothing happens. |
| `double_tap_action` | object | optional | See [Action](#Action) | Define the type of action on double click, if undefined, nothing happens. |
| `press_action` | object | optional | See [Action](#Action) | Define the type of action on press (triggers the moment your finger presses the button), if undefined, nothing happens. |
| `release_action` | object | optional | See [Action](#Action) | Define the type of action on releasing the button, if undefined, nothing happens. |
| `name` | string | optional | `Air conditioner` | Define an optional text to show below the icon. Supports templates, see [templates](#javascript-templates) |
| `state_display` | string | optional | `On` | Override the way the state is displayed. Supports templates, see [templates](#javascript-templates) |
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. Supports templates, see [templates](#javascript-templates) |
Expand Down
4 changes: 4 additions & 0 deletions src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { directive, PropertyPart } from 'lit-html';
// tslint:disable-next-line
import { Ripple } from '@material/mwc-ripple';
import { myFireEvent } from './my-fire-event';
import { ButtonCardConfig } from './types';
import { HomeAssistant, ActionConfig, fireEvent, forwardHaptic, navigate, toggleEntity } from 'custom-card-helpers';

const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;

Expand Down Expand Up @@ -95,6 +97,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
});

const start = (ev: Event): void => {
myFireEvent(element, 'action', { action: 'press' });
this.held = false;
let x;
let y;
Expand Down Expand Up @@ -129,6 +132,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
const end = (ev: Event): void => {
// Prevent mouse event if touch event
ev.preventDefault();
myFireEvent(element, 'action', { action: 'release' });
if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) {
if (this.isRepeating && this.repeatTimeout) {
clearInterval(this.repeatTimeout);
Expand Down
41 changes: 40 additions & 1 deletion src/button-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
ButtonCardEmbeddedCardsConfig,
} from './types';
import { actionHandler } from './action-handler';
import { handleActionConfig } from './handle-action';
import {
computeDomain,
computeEntity,
Expand Down Expand Up @@ -630,7 +631,15 @@ class ButtonCard extends LitElement {
const tap_action = this._getTemplateOrValue(state, this._config!.tap_action!.action);
const hold_action = this._getTemplateOrValue(state, this._config!.hold_action!.action);
const double_tap_action = this._getTemplateOrValue(state, this._config!.double_tap_action!.action);
if (tap_action != 'none' || hold_action != 'none' || double_tap_action != 'none') {
const press_action = this._getTemplateOrValue(state, this._config!.press_action!.action);
const release_action = this._getTemplateOrValue(state, this._config!.release_action!.action);
if (
tap_action != 'none' ||
hold_action != 'none' ||
double_tap_action != 'none' ||
press_action != 'none' ||
release_action != 'none'
) {
clickable = true;
} else {
clickable = false;
Expand Down Expand Up @@ -933,6 +942,8 @@ class ButtonCard extends LitElement {
this._config = {
hold_action: { action: 'none' },
double_tap_action: { action: 'none' },
press_action: { action: 'none' },
release_action: { action: 'none' },
layout: 'vertical',
size: '40%',
color_type: 'icon',
Expand Down Expand Up @@ -1049,6 +1060,12 @@ class ButtonCard extends LitElement {
private _handleAction(ev: any): void {
if (ev.detail && ev.detail.action) {
switch (ev.detail.action) {
case 'press':
this._handlePress(ev);
break;
case 'release':
this._handleRelease(ev);
break;
case 'tap':
this._handleTap(ev);
break;
Expand All @@ -1064,6 +1081,28 @@ class ButtonCard extends LitElement {
}
}

private _handlePress(ev): void {
const config = ev.target.config;
if (!config) return;
handleActionConfig(
this,
this._hass!,
this._evalActions(config, 'press_action'),
this._evalActions(config, 'press_action').press_action,
);
}

private _handleRelease(ev): void {
const config = ev.target.config;
if (!config) return;
handleActionConfig(
this,
this._hass!,
this._evalActions(config, 'release_action'),
this._evalActions(config, 'release_action').release_action,
);
}

private _handleTap(ev): void {
const config = ev.target.config;
if (!config) return;
Expand Down
92 changes: 92 additions & 0 deletions src/handle-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { HomeAssistant, ActionConfig, fireEvent, forwardHaptic, navigate, toggleEntity } from 'custom-card-helpers';

export const handleActionConfig = (
node: HTMLElement,
hass: HomeAssistant,
config: {
entity?: string;
camera_image?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
},
actionConfig: ActionConfig | undefined,
): void => {
if (!actionConfig) {
actionConfig = {
action: 'more-info',
};
}

if (
actionConfig.confirmation &&
(!actionConfig.confirmation.exemptions ||
!actionConfig.confirmation.exemptions.some(e => e.user === hass!.user!.id))
) {
forwardHaptic('warning');

if (!confirm(actionConfig.confirmation.text || `Are you sure you want to ${actionConfig.action}?`)) {
return;
}
}

switch (actionConfig.action) {
case 'more-info':
if (config.entity || config.camera_image) {
fireEvent(node, 'hass-more-info', {
entityId: config.entity ? config.entity : config.camera_image!,
});
}
break;
case 'navigate':
if (actionConfig.navigation_path) {
navigate(node, actionConfig.navigation_path);
}
break;
case 'url':
if (actionConfig.url_path) {
window.open(actionConfig.url_path);
}
break;
case 'toggle':
if (config.entity) {
toggleEntity(hass, config.entity!);
forwardHaptic('success');
}
break;
case 'call-service': {
if (!actionConfig.service) {
forwardHaptic('failure');
return;
}
const [domain, service] = actionConfig.service.split('.', 2);
hass.callService(domain, service, actionConfig.service_data);
forwardHaptic('success');
}
}
};

export const handleAction = (
node: HTMLElement,
hass: HomeAssistant,
config: {
entity?: string;
camera_image?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
double_tap_action?: ActionConfig;
},
action: string,
): void => {
let actionConfig: ActionConfig | undefined;

if (action === 'double_tap' && config.double_tap_action) {
actionConfig = config.double_tap_action;
} else if (action === 'hold' && config.hold_action) {
actionConfig = config.hold_action;
} else if (action === 'tap' && config.tap_action) {
actionConfig = config.tap_action;
}

handleActionConfig(node, hass, config, actionConfig);
};
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface ButtonCardConfig {
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
press_action?: ActionConfig;
release_action?: ActionConfig;
show_name?: boolean;
show_state?: boolean;
show_icon?: boolean;
Expand Down