Skip to content
Merged
Changes from all commits
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
101 changes: 60 additions & 41 deletions src/panels/lovelace/common/directives/action-handler-directive.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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() {
Expand Down Expand Up @@ -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;
Expand All @@ -115,16 +124,34 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.startAnimation(x, y);
this.held = true;
}, this.holdTime);
};

const touchEnd = (ev: TouchEvent) => {
if (this.touch === false) {
return;
}
end(ev);
};

this.cooldownStart = true;
window.setTimeout(() => (this.cooldownStart = false), 100);
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 clickEnd = (ev: Event) => {
const end = (ev: Event) => {
if (
this.cooldownEnd ||
(["touchend", "touchcancel"].includes(ev.type) &&
this.timer === undefined)
["touchend", "touchcancel"].includes(ev.type) &&
this.timer === undefined
) {
return;
}
Expand All @@ -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("touchstart", clickStart, { passive: true });
element.addEventListener("touchend", clickEnd);
element.addEventListener("touchcancel", clickEnd);
element.addEventListener("keyup", handleEnter);
element.addEventListener("mousedown", clickStart, { passive: true });
element.addEventListener("click", clickEnd);

// 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);
}
element.addEventListener("keyup", handleEnter);
}

private startAnimation(x: number, y: number) {
Expand Down