diff --git a/README.md b/README.md index 3585766..7410c4b 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) | diff --git a/src/action-handler.ts b/src/action-handler.ts index 0e4a8f3..146f80f 100644 --- a/src/action-handler.ts +++ b/src/action-handler.ts @@ -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; @@ -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; @@ -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); diff --git a/src/button-card.ts b/src/button-card.ts index a0525fe..f760f01 100644 --- a/src/button-card.ts +++ b/src/button-card.ts @@ -40,6 +40,7 @@ import { ButtonCardEmbeddedCardsConfig, } from './types'; import { actionHandler } from './action-handler'; +import { handleActionConfig } from './handle-action'; import { computeDomain, computeEntity, @@ -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; @@ -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', @@ -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; @@ -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; diff --git a/src/handle-action.ts b/src/handle-action.ts new file mode 100644 index 0000000..60f59c6 --- /dev/null +++ b/src/handle-action.ts @@ -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); +}; diff --git a/src/types.ts b/src/types.ts index 2b2ccc0..15d5a22 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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;