UNPKG

meteoalarm-card

Version:

Meteoalarm card for Home Assistant Lovelace UI

203 lines (172 loc) 5.44 kB
import { fireEvent } from 'custom-card-helpers'; import { ActionHandlerDetail, ActionHandlerOptions } from 'custom-card-helpers/dist/types'; import { noChange } from 'lit'; import { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive'; const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0; interface ActionHandler extends HTMLElement { holdTime: number; bind(element: Element, options): void; } interface ActionHandlerElement extends HTMLElement { actionHandler?: boolean; } declare global { interface HASSDomEvents { action: ActionHandlerDetail; } } class ActionHandler extends HTMLElement implements ActionHandler { public holdTime = 500; // eslint-disable-next-line @typescript-eslint/no-explicit-any public ripple: any; protected timer?: number; protected held = false; private dblClickTimeout?: number; constructor() { super(); this.ripple = document.createElement('mwc-ripple'); } public connectedCallback(): void { Object.assign(this.style, { position: 'absolute', width: isTouch ? '100px' : '50px', height: isTouch ? '100px' : '50px', transform: 'translate(-50%, -50%)', pointerEvents: 'none', zIndex: '999', }); this.appendChild(this.ripple); this.ripple.primary = true; ['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach( (ev) => { document.addEventListener( ev, () => { clearTimeout(this.timer); this.stopAnimation(); this.timer = undefined; }, { passive: true }, ); }, ); } public bind(element: ActionHandlerElement, options): void { if (element.actionHandler) { return; } element.actionHandler = true; element.addEventListener('contextmenu', (ev: Event) => { const e = ev || window.event; if (e.preventDefault) { e.preventDefault(); } if (e.stopPropagation) { e.stopPropagation(); } e.cancelBubble = true; e.returnValue = false; return false; }); const start = (ev: Event): void => { this.held = false; let x; let y; if ((ev as TouchEvent).touches) { x = (ev as TouchEvent).touches[0].pageX; y = (ev as TouchEvent).touches[0].pageY; } else { x = (ev as MouseEvent).pageX; y = (ev as MouseEvent).pageY; } this.timer = window.setTimeout(() => { this.startAnimation(x, y); this.held = true; }, this.holdTime); }; const end = (ev: Event): void => { // Prevent mouse event if touch event ev.preventDefault(); if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) { return; } clearTimeout(this.timer); this.stopAnimation(); this.timer = undefined; if (this.held) { fireEvent(element, 'action', { action: 'hold' }); } else if (options.hasDoubleClick) { if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) { this.dblClickTimeout = window.setTimeout(() => { this.dblClickTimeout = undefined; fireEvent(element, 'action', { action: 'tap' }); }, 250); } else { clearTimeout(this.dblClickTimeout); this.dblClickTimeout = undefined; fireEvent(element, 'action', { action: 'double_tap' }); } } else { fireEvent(element, 'action', { action: 'tap' }); } }; const handleEnter = (ev: KeyboardEvent): void => { if (ev.keyCode !== 13) { return; } end(ev); }; element.addEventListener('touchstart', start, { passive: true }); element.addEventListener('touchend', end); element.addEventListener('touchcancel', end); element.addEventListener('mousedown', start, { passive: true }); element.addEventListener('click', end); element.addEventListener('keyup', handleEnter); } private startAnimation(x: number, y: number): void { Object.assign(this.style, { left: `${x}px`, top: `${y}px`, display: null, }); this.ripple.disabled = false; this.ripple.active = true; this.ripple.unbounded = true; } private stopAnimation(): void { this.ripple.active = false; this.ripple.disabled = true; this.style.display = 'none'; } } customElements.define('action-handler-meteoalarm', ActionHandler); const getActionHandler = (): ActionHandler => { const body = document.body; if (body.querySelector('action-handler-meteoalarm')) { return body.querySelector('action-handler-meteoalarm') as ActionHandler; } const actionhandler = document.createElement('action-handler-meteoalarm'); body.appendChild(actionhandler); return actionhandler as ActionHandler; }; export const actionHandlerBind = ( element: ActionHandlerElement, options?: ActionHandlerOptions, ): void => { const actionhandler: ActionHandler = getActionHandler(); if (!actionhandler) { return; } actionhandler.bind(element, options); }; export const actionHandler = directive( class extends Directive { update(part: AttributePart, [options]: DirectiveParameters<this>) { actionHandlerBind(part.element as ActionHandlerElement, options); return noChange; } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars render(_options?: ActionHandlerOptions) {} }, );