-
-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* HACS badge * hacs.json * New action handling with double tap support * Cleanup
- Loading branch information
Showing
10 changed files
with
695 additions
and
1,244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "Boilerplate Card", | ||
"render_readme": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "boilerplate-card", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"description": "Lovelace boilerplate-card", | ||
"keywords": [ | ||
"home-assistant", | ||
|
@@ -12,30 +12,30 @@ | |
], | ||
"module": "boilerplate-card.js", | ||
"repository": "[email protected]:custom_cards/boilerplate-card.git", | ||
"author": "BoilerPlate <boilerplate@email.com>", | ||
"author": "Ian Richardson <iantrich@gmail.com>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"custom-card-helpers": "^1.0.8", | ||
"home-assistant-js-websocket": "^3.4.0", | ||
"lit-element": "^2.0.1" | ||
"custom-card-helpers": "^1.3.5", | ||
"home-assistant-js-websocket": "^4.4.0", | ||
"lit-element": "^2.2.1" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.4.3", | ||
"@babel/plugin-proposal-class-properties": "^7.4.0", | ||
"@babel/core": "^7.5.5", | ||
"@babel/plugin-proposal-class-properties": "^7.5.5", | ||
"@babel/plugin-proposal-decorators": "^7.4.0", | ||
"@typescript-eslint/eslint-plugin": "^1.4.2", | ||
"@typescript-eslint/parser": "^1.4.1", | ||
"eslint": "^5.14.1", | ||
"eslint-config-airbnb-base": "^13.1.0", | ||
"eslint-plugin-import": "^2.16.0", | ||
"prettier": "^1.16.4", | ||
"rollup": "^1.2.3", | ||
"rollup-plugin-babel": "^4.3.2", | ||
"rollup-plugin-node-resolve": "^4.0.1", | ||
"rollup-plugin-terser": "^4.0.4", | ||
"rollup-plugin-typescript2": "^0.19.2", | ||
"@typescript-eslint/eslint-plugin": "^2.0.0", | ||
"@typescript-eslint/parser": "^2.0.0", | ||
"eslint": "^6.2.2", | ||
"eslint-config-airbnb-base": "^14.0.0", | ||
"eslint-plugin-import": "^2.18.2", | ||
"prettier": "^1.18.2", | ||
"rollup": "^1.20.2", | ||
"rollup-plugin-babel": "^4.3.3", | ||
"rollup-plugin-node-resolve": "^5.2.0", | ||
"rollup-plugin-terser": "^5.1.1", | ||
"rollup-plugin-typescript2": "^0.23.0", | ||
"rollup-plugin-uglify": "^6.0.2", | ||
"typescript": "^3.3.3333" | ||
"typescript": "^3.5.3" | ||
}, | ||
"scripts": { | ||
"start": "rollup -c --watch", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import { directive, PropertyPart } from "lit-html"; | ||
import { fireEvent, ActionHandlerOptions } from "custom-card-helpers"; | ||
|
||
const isTouch = | ||
"ontouchstart" in window || | ||
navigator.maxTouchPoints > 0 || | ||
navigator.msMaxTouchPoints > 0; | ||
|
||
interface ActionHandler extends HTMLElement { | ||
holdTime: number; | ||
bind(element: Element, options): void; | ||
} | ||
interface ActionHandlerElement extends Element { | ||
actionHandler?: boolean; | ||
} | ||
|
||
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; | ||
|
||
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: ActionHandlerElement, options) { | ||
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 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; | ||
} | ||
|
||
if (options.hasHold) { | ||
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) { | ||
fireEvent(element as HTMLElement, "action", { action: "hold" }); | ||
} else if (options.hasDoubleTap) { | ||
if ((ev as MouseEvent).detail === 1) { | ||
this.dblClickTimeout = window.setTimeout(() => { | ||
fireEvent(element as HTMLElement, "action", { action: "tap" }); | ||
}, 250); | ||
} else { | ||
clearTimeout(this.dblClickTimeout); | ||
fireEvent(element as HTMLElement, "action", { action: "double_tap" }); | ||
} | ||
} else { | ||
fireEvent(element as HTMLElement, "action", { action: "tap" }); | ||
} | ||
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 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 = 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"; | ||
} | ||
} | ||
|
||
// TODO You need to replace all instances of "action-handler-boilerplate" with "action-handler-<your card name>" | ||
customElements.define("action-handler-boilerplate", ActionHandler); | ||
|
||
const geActionHandler = (): ActionHandler => { | ||
const body = document.body; | ||
if (body.querySelector("action-handler-boilerplate")) { | ||
return body.querySelector("action-handler-boilerplate") as ActionHandler; | ||
} | ||
|
||
const actionhandler = document.createElement("action-handler-boilerplate"); | ||
body.appendChild(actionhandler); | ||
|
||
return actionhandler as ActionHandler; | ||
}; | ||
|
||
export const actionHandlerBind = ( | ||
element: ActionHandlerElement, | ||
options: ActionHandlerOptions | ||
) => { | ||
const actionhandler: ActionHandler = geActionHandler(); | ||
if (!actionhandler) { | ||
return; | ||
} | ||
actionhandler.bind(element, options); | ||
}; | ||
|
||
export const actionHandler = directive( | ||
(options: ActionHandlerOptions = {}) => (part: PropertyPart) => { | ||
actionHandlerBind(part.committer.element, options); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.