Skip to content

Commit

Permalink
add action option
Browse files Browse the repository at this point in the history
  • Loading branch information
iantrich committed Oct 21, 2019
1 parent f8b5e4c commit 621f467
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 25 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ In your `resources` section add
| condition | map | **Optional** | Conditional object to make lock active. See [Condition Options](#condition-options). |
| row | boolean | **Optional** | Set to true to give a default `margin:left: 24px` |
| duration | number | **Optional** | Duration of unlock in seconds. Default is `5` |
| action | string | **Optional** | Action type to trigger the unlock. Options are `tap`, `double_tap`, or `hold`. Default is `tap` |

## Restrictions Options

Expand Down
42 changes: 23 additions & 19 deletions dist/restriction-card.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"author": "BoilerPlate <[email protected]>",
"license": "MIT",
"dependencies": {
"custom-card-helpers": "^1.2.4",
"custom-card-helpers": "^1.2.7",
"home-assistant-js-websocket": "^4.3.1",
"lit-element": "^2.2.1"
},
Expand Down
202 changes: 202 additions & 0 deletions src/long-press-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { directive, PropertyPart } from "lit-html";
import { LongPressOptions } from "./types";

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

interface LongPress extends HTMLElement {
holdTime: number;
bind(element: Element, options): void;
}
interface LongPressElement extends Element {
longPress?: boolean;
}

class LongPress extends HTMLElement implements LongPress {
public holdTime: number;
public ripple: any;
protected timer: number | undefined;
protected held: boolean;
protected cooldownStart: boolean;
protected cooldownEnd: boolean;
private dblClickTimeout: number | undefined;

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() {
Object.assign(this.style, {
position: "absolute",
width: isTouch ? "100px" : "50px",
height: isTouch ? "100px" : "50px",
transform: "translate(-50%, -50%)",
pointerEvents: "none",
});

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: LongPressElement, options) {
if (element.longPress) {
return;
}
element.longPress = 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 clickStart = (ev: Event) => {
if (this.cooldownStart) {
return;
}
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);

this.cooldownStart = true;
window.setTimeout(() => (this.cooldownStart = false), 100);
};

const clickEnd = (ev: Event) => {
if (
this.cooldownEnd ||
(["touchend", "touchcancel"].includes(ev.type) &&
this.timer === undefined)
) {
return;
}
clearTimeout(this.timer);
this.stopAnimation();
this.timer = undefined;
if (this.held) {
element.dispatchEvent(new Event("ha-hold"));
} else if (options.hasDoubleClick) {
if ((ev as MouseEvent).detail === 1) {
this.dblClickTimeout = window.setTimeout(() => {
element.dispatchEvent(new Event("ha-click"));
}, 250);
} else {
clearTimeout(this.dblClickTimeout);
element.dispatchEvent(new Event("ha-dblclick"));
}
} else {
element.dispatchEvent(new Event("ha-click"));
}
this.cooldownEnd = true;
window.setTimeout(() => (this.cooldownEnd = false), 100);
};

element.addEventListener("touchstart", clickStart, { passive: true });
element.addEventListener("touchend", clickEnd);
element.addEventListener("touchcancel", 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 long-press 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 = window.navigator.userAgent.match(/iPhone OS 13_/);
if (!isIOS13) {
element.addEventListener("mousedown", clickStart, { passive: true });
element.addEventListener("click", clickEnd);
}
}

private startAnimation(x: number, y: number) {
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() {
this.ripple.active = false;
this.ripple.disabled = true;
this.style.display = "none";
}
}

customElements.define("long-press-restriction", LongPress);

const getLongPress = (): LongPress => {
const body = document.body;
if (body.querySelector("long-press-restriction")) {
return body.querySelector("long-press-restriction") as LongPress;
}

const longpress = document.createElement("long-press-restriction");
body.appendChild(longpress);

return longpress as LongPress;
};

export const longPressBind = (
element: LongPressElement,
options: LongPressOptions
) => {
const longpress: LongPress = getLongPress();
if (!longpress) {
return;
}
longpress.bind(element, options);
};

export const longPress = directive(
(options: LongPressOptions = {}) => (part: PropertyPart) => {
longPressBind(part.committer.element, options);
}
);
27 changes: 26 additions & 1 deletion src/restriction-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
LovelaceCardConfig,
evaluateFilter
} from "custom-card-helpers";
import { longPress } from "./long-press-directive";

@customElement("restriction-card")
class RestrictionCard extends LitElement implements LovelaceCard {
Expand Down Expand Up @@ -56,7 +57,7 @@ class RestrictionCard extends LitElement implements LovelaceCard {
throw new Error("A pin code is required for pin restrictions");
}

this._config = { duration: 5, ...config };
this._config = { duration: 5, action: "tap", ...config };
}

protected shouldUpdate(changedProps: PropertyValues): boolean {
Expand Down Expand Up @@ -104,6 +105,12 @@ class RestrictionCard extends LitElement implements LovelaceCard {
? ""
: html`
<div
@ha-click=${this._handleClick}
@ha-hold=${this._handleHold}
@ha-dblclick=${this._handleDblClick}
.longPress=${longPress({
hasDoubleClick: this._config!.action === "double_tap",
})}
@click=${this._handleClick}
id="overlay"
class="${classMap({
Expand Down Expand Up @@ -153,6 +160,24 @@ class RestrictionCard extends LitElement implements LovelaceCard {
}

private _handleClick(): void {
if (this._config!.action === "tap") {
this._handleRestriction();
}
}

private _handleDblClick(): void {
if (this._config!.action === "double_tap") {
this._handleRestriction();
}
}

private _handleHold() : void {
if (this._config!.action === "hold") {
this._handleRestriction();
}
}

private _handleRestriction(): void {
const lock = this.shadowRoot!.getElementById("lock") as LitElement;

if (this._config!.restrictions) {
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface RestrictionCardConfig extends LovelaceCardConfig {
card?: LovelaceCardConfig;
row?: boolean;
delay?: number;
action: string;
}

export interface RestrictionsConfig {
Expand Down Expand Up @@ -50,3 +51,7 @@ export interface ConditionConfig {
entity: string;
attribute?: string;
}

export interface LongPressOptions {
hasDoubleClick?: boolean;
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,10 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"

custom-card-helpers@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/custom-card-helpers/-/custom-card-helpers-1.2.4.tgz#b95c65ca38596dd072989c8120b2e1b5bf82d931"
integrity sha512-mGSsgRcOXsKUl01b6e1pisbAyZUM8o720L9hQo9meAIzBZgLVoB0XA/M/1XxQg/UEoQDsc8YlLpfOQgSwpT9Pg==
custom-card-helpers@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/custom-card-helpers/-/custom-card-helpers-1.2.7.tgz#83525cccca4cac7e6eb83d962832245e011773e5"
integrity sha512-dhLwyQlvaLCFaqXveA36STjID7yKKcZqPhpw7eupn5R3y4sOUnPjx8IaTm5URLM0dEKprnzF3hdb743z62vBGw==
dependencies:
fecha "^3.0.3"
home-assistant-js-websocket "^4.1.2"
Expand Down

0 comments on commit 621f467

Please sign in to comment.