From dd8c568a2cfa3e0b87355ad75c3eed591f41f72f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 19:03:43 +0100 Subject: [PATCH] Fix action directive double tab iOS issues (#4639) --- .../directives/action-handler-directive.ts | 103 +++++++++++------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/src/panels/lovelace/common/directives/action-handler-directive.ts b/src/panels/lovelace/common/directives/action-handler-directive.ts index e9f9597b9e..87c5168019 100644 --- a/src/panels/lovelace/common/directives/action-handler-directive.ts +++ b/src/panels/lovelace/common/directives/action-handler-directive.ts @@ -1,5 +1,7 @@ import { directive, PropertyPart } from "lit-html"; import "@material/mwc-ripple"; +// tslint:disable-next-line +import { Ripple } from "@material/mwc-ripple"; import { ActionHandlerOptions, ActionHandlerDetail, @@ -26,22 +28,16 @@ declare global { } class ActionHandler extends HTMLElement implements ActionHandler { - public holdTime: number; - public ripple: any; - protected timer: number | undefined; - protected held: boolean; - protected cooldownStart: boolean; - protected cooldownEnd: boolean; - private dblClickTimeout: number | undefined; + public holdTime = 500; + public ripple: Ripple; + protected timer?: number; + protected held = false; + protected touch?: boolean; + private dblClickTimeout?: number; constructor() { super(); - this.holdTime = 500; this.ripple = document.createElement("mwc-ripple"); - this.timer = undefined; - this.held = false; - this.cooldownStart = false; - this.cooldownEnd = false; } public connectedCallback() { @@ -96,10 +92,23 @@ class ActionHandler extends HTMLElement implements ActionHandler { return false; }); - const clickStart = (ev: Event) => { - if (this.cooldownStart) { + const touchStart = (ev: TouchEvent) => { + if (this.touch === false) { return; } + this.touch = true; + start(ev); + }; + + const clickStart = (ev: MouseEvent) => { + if (this.touch === true) { + return; + } + this.touch = false; + start(ev); + }; + + const start = (ev: Event) => { this.held = false; let x; let y; @@ -115,16 +124,34 @@ class ActionHandler extends HTMLElement implements ActionHandler { this.startAnimation(x, y); this.held = true; }, this.holdTime); - - this.cooldownStart = true; - window.setTimeout(() => (this.cooldownStart = false), 100); }; - const clickEnd = (ev: Event) => { + const touchEnd = (ev: TouchEvent) => { + if (this.touch === false) { + return; + } + end(ev); + }; + + const clickEnd = (ev: MouseEvent) => { + if (this.touch === true) { + return; + } + end(ev); + }; + + const handleEnter = (ev: KeyboardEvent) => { + if (this.touch === true || ev.keyCode !== 13) { + return; + } + this.touch = false; + end(ev); + }; + + const end = (ev: Event) => { if ( - this.cooldownEnd || - (["touchend", "touchcancel"].includes(ev.type) && - this.timer === undefined) + ["touchend", "touchcancel"].includes(ev.type) && + this.timer === undefined ) { return; } @@ -134,41 +161,33 @@ class ActionHandler extends HTMLElement implements ActionHandler { if (this.held) { fireEvent(element, "action", { action: "hold" }); } else if (options.hasDoubleClick) { - if ((ev as MouseEvent).detail === 1 || ev.type === "keyup") { + 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" }); } - this.cooldownEnd = true; - window.setTimeout(() => (this.cooldownEnd = false), 100); + window.setTimeout(() => (this.touch = undefined), 100); }; - const handleEnter = (ev: Event) => { - if ((ev as KeyboardEvent).keyCode === 13) { - return clickEnd(ev); - } - }; + element.addEventListener("touchstart", touchStart, { passive: true }); + element.addEventListener("touchend", touchEnd); + element.addEventListener("touchcancel", touchEnd); + + element.addEventListener("mousedown", clickStart, { passive: true }); + element.addEventListener("click", clickEnd); - element.addEventListener("touchstart", clickStart, { passive: true }); - element.addEventListener("touchend", clickEnd); - element.addEventListener("touchcancel", clickEnd); element.addEventListener("keyup", handleEnter); - - // iOS 13 sends a complete normal touchstart-touchend series of events followed by a mousedown-click series. - // That might be a bug, but until it's fixed, this should make action-handler work. - // If it's not a bug that is fixed, this might need updating with the next iOS version. - // Note that all events (both touch and mouse) must be listened for in order to work on computers with both mouse and touchscreen. - const isIOS13 = /iPhone OS 13_/.test(window.navigator.userAgent); - if (!isIOS13) { - element.addEventListener("mousedown", clickStart, { passive: true }); - element.addEventListener("click", clickEnd); - } } private startAnimation(x: number, y: number) {