11import { directive , PropertyPart } from 'lit-html' ;
2- import { fireEvent , ActionHandlerOptions } from 'custom-card-helpers' ;
2+
3+ import { fireEvent , ActionHandlerDetail , ActionHandlerOptions } from 'custom-card-helpers' ;
34
45const isTouch = 'ontouchstart' in window || navigator . maxTouchPoints > 0 || navigator . msMaxTouchPoints > 0 ;
56
67interface ActionHandler extends HTMLElement {
78 holdTime : number ;
89 bind ( element : Element , options ) : void ;
910}
10- interface ActionHandlerElement extends Element {
11+ interface ActionHandlerElement extends HTMLElement {
1112 actionHandler ?: boolean ;
1213}
1314
15+ declare global {
16+ interface HASSDomEvents {
17+ action : ActionHandlerDetail ;
18+ }
19+ }
20+
1421class ActionHandler extends HTMLElement implements ActionHandler {
15- public holdTime : number ;
16- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
22+ public holdTime = 500 ;
23+
1724 public ripple : any ;
18- protected timer : number | undefined ;
19- protected held : boolean ;
20- protected cooldownStart : boolean ;
21- protected cooldownEnd : boolean ;
22- private dblClickTimeout : number | undefined ;
25+
26+ protected timer ?: number ;
27+
28+ protected held = false ;
29+
30+ private dblClickTimeout ?: number ;
2331
2432 constructor ( ) {
2533 super ( ) ;
26- this . holdTime = 500 ;
2734 this . ripple = document . createElement ( 'mwc-ripple' ) ;
28- this . timer = undefined ;
29- this . held = false ;
30- this . cooldownStart = false ;
31- this . cooldownEnd = false ;
3235 }
3336
3437 public connectedCallback ( ) : void {
@@ -38,6 +41,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
3841 height : isTouch ? '100px' : '50px' ,
3942 transform : 'translate(-50%, -50%)' ,
4043 pointerEvents : 'none' ,
44+ zIndex : '999' ,
4145 } ) ;
4246
4347 this . appendChild ( this . ripple ) ;
@@ -72,13 +76,10 @@ class ActionHandler extends HTMLElement implements ActionHandler {
7276 }
7377 e . cancelBubble = true ;
7478 e . returnValue = false ;
75- return ;
79+ return false ;
7680 } ) ;
7781
78- const clickStart = ( ev : Event ) : void => {
79- if ( this . cooldownStart ) {
80- return ;
81- }
82+ const start = ( ev : Event ) : void => {
8283 this . held = false ;
8384 let x ;
8485 let y ;
@@ -94,56 +95,50 @@ class ActionHandler extends HTMLElement implements ActionHandler {
9495 this . startAnimation ( x , y ) ;
9596 this . held = true ;
9697 } , this . holdTime ) ;
97-
98- this . cooldownStart = true ;
99- window . setTimeout ( ( ) => ( this . cooldownStart = false ) , 100 ) ;
10098 } ;
10199
102- const clickEnd = ( ev : Event ) : void => {
103- if ( this . cooldownEnd || ( [ 'touchend' , 'touchcancel' ] . includes ( ev . type ) && this . timer === undefined ) ) {
100+ const end = ( ev : Event ) : void => {
101+ // Prevent mouse event if touch event
102+ ev . preventDefault ( ) ;
103+ if ( [ 'touchend' , 'touchcancel' ] . includes ( ev . type ) && this . timer === undefined ) {
104104 return ;
105105 }
106106 clearTimeout ( this . timer ) ;
107107 this . stopAnimation ( ) ;
108108 this . timer = undefined ;
109109 if ( this . held ) {
110- fireEvent ( element as HTMLElement , 'action' , { action : 'hold' } ) ;
111- } else if ( options . hasDoubleTap ) {
112- if ( ( ev as MouseEvent ) . detail === 1 || ev . type === 'keyup' ) {
110+ fireEvent ( element , 'action' , { action : 'hold' } ) ;
111+ } else if ( options . hasDoubleClick ) {
112+ if ( ( ev . type === 'click' && ( ev as MouseEvent ) . detail < 2 ) || ! this . dblClickTimeout ) {
113113 this . dblClickTimeout = window . setTimeout ( ( ) => {
114- fireEvent ( element as HTMLElement , 'action' , { action : 'tap' } ) ;
114+ this . dblClickTimeout = undefined ;
115+ fireEvent ( element , 'action' , { action : 'tap' } ) ;
115116 } , 250 ) ;
116117 } else {
117118 clearTimeout ( this . dblClickTimeout ) ;
118- fireEvent ( element as HTMLElement , 'action' , { action : 'double_tap' } ) ;
119+ this . dblClickTimeout = undefined ;
120+ fireEvent ( element , 'action' , { action : 'double_tap' } ) ;
119121 }
120122 } else {
121- fireEvent ( element as HTMLElement , 'action' , { action : 'tap' } ) ;
123+ fireEvent ( element , 'action' , { action : 'tap' } ) ;
122124 }
123- this . cooldownEnd = true ;
124- window . setTimeout ( ( ) => ( this . cooldownEnd = false ) , 100 ) ;
125125 } ;
126126
127- const handleEnter = ( ev : Event ) : void => {
128- if ( ( ev as KeyboardEvent ) . keyCode = == 13 ) {
129- return clickEnd ( ev ) ;
127+ const handleEnter = ( ev : KeyboardEvent ) : void => {
128+ if ( ev . keyCode ! == 13 ) {
129+ return ;
130130 }
131+ end ( ev ) ;
131132 } ;
132133
133- element . addEventListener ( 'touchstart' , clickStart , { passive : true } ) ;
134- element . addEventListener ( 'touchend' , clickEnd ) ;
135- element . addEventListener ( 'touchcancel' , clickEnd ) ;
136- element . addEventListener ( 'keyup' , handleEnter ) ;
134+ element . addEventListener ( 'touchstart' , start , { passive : true } ) ;
135+ element . addEventListener ( 'touchend' , end ) ;
136+ element . addEventListener ( 'touchcancel' , end ) ;
137137
138- // iOS 13 sends a complete normal touchstart-touchend series of events followed by a mousedown-click series.
139- // That might be a bug, but until it's fixed, this should make action-handler work.
140- // If it's not a bug that is fixed, this might need updating with the next iOS version.
141- // Note that all events (both touch and mouse) must be listened for in order to work on computers with both mouse and touchscreen.
142- const isIOS13 = / i P h o n e O S 1 3 _ / . test ( window . navigator . userAgent ) ;
143- if ( ! isIOS13 ) {
144- element . addEventListener ( 'mousedown' , clickStart , { passive : true } ) ;
145- element . addEventListener ( 'click' , clickEnd ) ;
146- }
138+ element . addEventListener ( 'mousedown' , start , { passive : true } ) ;
139+ element . addEventListener ( 'click' , end ) ;
140+
141+ element . addEventListener ( 'keyup' , handleEnter ) ;
147142 }
148143
149144 private startAnimation ( x : number , y : number ) : void {
@@ -187,5 +182,5 @@ export const actionHandlerBind = (element: ActionHandlerElement, options: Action
187182} ;
188183
189184export const actionHandler = directive ( ( options : ActionHandlerOptions = { } ) => ( part : PropertyPart ) : void => {
190- actionHandlerBind ( part . committer . element , options ) ;
185+ actionHandlerBind ( part . committer . element as ActionHandlerElement , options ) ;
191186} ) ;
0 commit comments