diff --git a/README.md b/README.md
index 9a84d32a..8bca0032 100644
--- a/README.md
+++ b/README.md
@@ -697,10 +697,11 @@ specific overriding the less specific (see example below).
**Note:** The card itself relies on user interactions to function (e.g. `tap` on
the menu should activate that button, `tap` on a gallery thumbnail should open
-that piece of media, etc). These internal actions are executed _also_, which
-means that a card-wide `tap` action probably isn't that useful as it may be
-disorienting to the user and will trigger on all kinds of basic interaction on
-the card (e.g. tapping/clicking a menu button).
+that piece of media, etc). Efforts are taken to de-duplicate interactions (e.g.
+card-wide actions will not be activated through interaction with menu buttons,
+next/previous controls, thumbnails, etc), but in some cases this is not possible
+(e.g. embedded WebRTC card controls) -- in these cases duplicate actions may
+occur with certain configurations (e.g. `tap`).
## Menu Modes
diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts
index fc79388f..d7d94027 100644
--- a/src/action-handler-directive.ts
+++ b/src/action-handler-directive.ts
@@ -1,11 +1,17 @@
import { noChange } from 'lit';
-import { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive.js';
+import {
+ AttributePart,
+ directive,
+ Directive,
+ DirectiveParameters,
+} from 'lit/directive.js';
import type {
ActionHandlerDetail,
ActionHandlerOptions,
} from 'custom-card-helpers/dist/types.d.js';
import { fireEvent } from 'custom-card-helpers';
+import { stopEventFromActivatingCardWideActions } from './common';
interface ActionHandler extends HTMLElement {
holdTime: number;
@@ -31,7 +37,6 @@ class ActionHandler extends HTMLElement implements ActionHandler {
private dblClickTimeout?: number;
public connectedCallback(): void {
-
[
'touchcancel',
'mouseout',
@@ -89,12 +94,17 @@ class ActionHandler extends HTMLElement implements ActionHandler {
};
const end = (ev: Event): void => {
+ // This will ensure only 1 actionHandler is invoked for a given interaction.
+ stopEventFromActivatingCardWideActions(ev);
+
const options = element.actionHandlerOptions;
- if (['touchend', 'touchcancel'].includes(ev.type)
- // This action handler by default relies on synthetic click events for
- // touch devices, in order to ensure that embedded cards (e.g. WebRTC)
- // can use stock click handlers. The exception is for hold events.
- && !(options?.hasHold && this.held)) {
+ if (
+ ['touchend', 'touchcancel'].includes(ev.type) &&
+ // This action handler by default relies on synthetic click events for
+ // touch devices, in order to ensure that embedded cards (e.g. WebRTC)
+ // can use stock click handlers. The exception is for hold events.
+ !(options?.hasHold && this.held)
+ ) {
return;
}
if (options?.hasHold) {
diff --git a/src/common.ts b/src/common.ts
index bdc60e9e..9e3793e9 100644
--- a/src/common.ts
+++ b/src/common.ts
@@ -583,3 +583,10 @@ export const frigateCardHasAction = (
}
return hasAction(config);
};
+
+/**
+ * Stop an event from activating card wide actions.
+ */
+export const stopEventFromActivatingCardWideActions = (ev: Event): void => {
+ ev.stopPropagation();
+}
\ No newline at end of file
diff --git a/src/components/gallery.ts b/src/components/gallery.ts
index 6ba4c20c..13fa510f 100644
--- a/src/components/gallery.ts
+++ b/src/components/gallery.ts
@@ -13,6 +13,7 @@ import {
import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js';
import { renderProgressIndicator } from './message.js';
+import { stopEventFromActivatingCardWideActions } from '../common.js';
import galleryStyle from '../scss/gallery.scss';
@@ -161,10 +162,11 @@ export class FrigateCardGalleryCore extends LitElement {
{
+ @click=${(ev) => {
if (this.view && this.view.previous) {
this.view.previous.dispatchChangeEvent(this);
}
+ stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
class="frigate-card-gallery-folder"
@@ -182,7 +184,7 @@ export class FrigateCardGalleryCore extends LitElement {
${child.can_expand
? html`
{
+ @click=${(ev) => {
if (this.hass && this.view) {
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
this,
@@ -191,6 +193,7 @@ export class FrigateCardGalleryCore extends LitElement {
child,
);
}
+ stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
class="frigate-card-gallery-folder"
@@ -204,7 +207,7 @@ export class FrigateCardGalleryCore extends LitElement {
class="mdc-image-list__image"
src="${child.thumbnail}"
title="${child.title}"
- @click=${() => {
+ @click=${(ev) => {
if (this.view) {
this.view
.evolve({
@@ -214,6 +217,7 @@ export class FrigateCardGalleryCore extends LitElement {
})
.dispatchChangeEvent(this);
}
+ stopEventFromActivatingCardWideActions(ev);
}}
/>`
: ``}
diff --git a/src/components/live.ts b/src/components/live.ts
index 8bdfbd9d..6cd8bb39 100644
--- a/src/components/live.ts
+++ b/src/components/live.ts
@@ -50,6 +50,7 @@ import {
getCameraIcon,
getCameraTitle,
homeAssistantSignPath,
+ stopEventFromActivatingCardWideActions,
} from '../common.js';
import { renderProgressIndicator } from '../components/message.js';
@@ -550,8 +551,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
.label=${getCameraTitle(this.hass, prev)}
.icon=${getCameraIcon(this.hass, prev)}
?disabled=${prev == null}
- @click=${() => {
+ @click=${(ev) => {
this._nextPreviousHandler('previous');
+ stopEventFromActivatingCardWideActions(ev);
}}
>
@@ -565,8 +567,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
.label=${getCameraTitle(this.hass, next)}
.icon=${getCameraIcon(this.hass, next)}
?disabled=${next == null}
- @click=${() => {
+ @click=${(ev) => {
this._nextPreviousHandler('next');
+ stopEventFromActivatingCardWideActions(ev);
}}
>
diff --git a/src/components/submenu.ts b/src/components/submenu.ts
index 4476b8eb..79956c56 100644
--- a/src/components/submenu.ts
+++ b/src/components/submenu.ts
@@ -2,7 +2,11 @@ import type { Corner, Menu } from '@material/mwc-menu';
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, query } from 'lit/decorators';
-import { frigateCardHasAction, refreshDynamicStateParameters } from '../common.js';
+import {
+ frigateCardHasAction,
+ refreshDynamicStateParameters,
+ stopEventFromActivatingCardWideActions,
+} from '../common.js';
import { ifDefined } from 'lit/directives/if-defined';
import { styleMap } from 'lit/directives/style-map';
@@ -68,7 +72,10 @@ export class FrigateCardSubmenu extends LitElement {
protected _getFixedRoot(): HTMLElement | null {
let n = this as Node | null;
while (n) {
- if (n.nodeType === Node.ELEMENT_NODE && (n as Element).tagName === 'HA-APP-LAYOUT') {
+ if (
+ n.nodeType === Node.ELEMENT_NODE &&
+ (n as Element).tagName === 'HA-APP-LAYOUT'
+ ) {
return n as HTMLElement;
}
n = n.parentNode
@@ -93,7 +100,7 @@ export class FrigateCardSubmenu extends LitElement {
hasHold: frigateCardHasAction(this.submenu.hold_action),
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})}
- @click=${() => {
+ @click=${(ev) => {
if (this._menu) {
// Hack: This insanity is brought about by lack of MWCMenu playing
// nicely with the Home Assistant view/sidepanel. The menu must be
@@ -116,12 +123,13 @@ export class FrigateCardSubmenu extends LitElement {
}
this._menu.show();
}
+ stopEventFromActivatingCardWideActions(ev);
}}
>
{
+ @click=${(ev) => {
if (this._carousel && this._carousel.clickAllowed()) {
dispatchFrigateCardEvent(this, 'carousel:tap', {
slideIndex: slideIndex,
@@ -119,6 +119,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
childIndex: childIndex,
});
}
+ stopEventFromActivatingCardWideActions(ev);
}}
>
{
+ @click=${(ev) => {
this._nextPreviousHandler('previous');
+ stopEventFromActivatingCardWideActions(ev);
}}
>
@@ -669,8 +671,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''}
?disabled=${!next}
- @click=${() => {
+ @click=${(ev) => {
this._nextPreviousHandler('next');
+ stopEventFromActivatingCardWideActions(ev);
}}
>