Merge pull request #422 from dermotduffy/stop-prop

Don't trigger card-wide actions on card controls
This commit is contained in:
Dermot Duffy
2022-02-27 11:22:38 -08:00
committed by GitHub
8 changed files with 61 additions and 24 deletions
+5 -4
View File
@@ -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 **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 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 that piece of media, etc). Efforts are taken to de-duplicate interactions (e.g.
means that a card-wide `tap` action probably isn't that useful as it may be card-wide actions will not be activated through interaction with menu buttons,
disorienting to the user and will trigger on all kinds of basic interaction on next/previous controls, thumbnails, etc), but in some cases this is not possible
the card (e.g. tapping/clicking a menu button). (e.g. embedded WebRTC card controls) -- in these cases duplicate actions may
occur with certain configurations (e.g. `tap`).
## Menu Modes ## Menu Modes
+14 -4
View File
@@ -1,11 +1,17 @@
import { noChange } from 'lit'; import { noChange } from 'lit';
import { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive.js'; import {
AttributePart,
directive,
Directive,
DirectiveParameters,
} from 'lit/directive.js';
import type { import type {
ActionHandlerDetail, ActionHandlerDetail,
ActionHandlerOptions, ActionHandlerOptions,
} from 'custom-card-helpers/dist/types.d.js'; } from 'custom-card-helpers/dist/types.d.js';
import { fireEvent } from 'custom-card-helpers'; import { fireEvent } from 'custom-card-helpers';
import { stopEventFromActivatingCardWideActions } from './common';
interface ActionHandler extends HTMLElement { interface ActionHandler extends HTMLElement {
holdTime: number; holdTime: number;
@@ -31,7 +37,6 @@ class ActionHandler extends HTMLElement implements ActionHandler {
private dblClickTimeout?: number; private dblClickTimeout?: number;
public connectedCallback(): void { public connectedCallback(): void {
[ [
'touchcancel', 'touchcancel',
'mouseout', 'mouseout',
@@ -89,12 +94,17 @@ class ActionHandler extends HTMLElement implements ActionHandler {
}; };
const end = (ev: Event): void => { const end = (ev: Event): void => {
// This will ensure only 1 actionHandler is invoked for a given interaction.
stopEventFromActivatingCardWideActions(ev);
const options = element.actionHandlerOptions; const options = element.actionHandlerOptions;
if (['touchend', 'touchcancel'].includes(ev.type) if (
['touchend', 'touchcancel'].includes(ev.type) &&
// This action handler by default relies on synthetic click events for // This action handler by default relies on synthetic click events for
// touch devices, in order to ensure that embedded cards (e.g. WebRTC) // touch devices, in order to ensure that embedded cards (e.g. WebRTC)
// can use stock click handlers. The exception is for hold events. // can use stock click handlers. The exception is for hold events.
&& !(options?.hasHold && this.held)) { !(options?.hasHold && this.held)
) {
return; return;
} }
if (options?.hasHold) { if (options?.hasHold) {
+7
View File
@@ -583,3 +583,10 @@ export const frigateCardHasAction = (
} }
return hasAction(config); return hasAction(config);
}; };
/**
* Stop an event from activating card wide actions.
*/
export const stopEventFromActivatingCardWideActions = (ev: Event): void => {
ev.stopPropagation();
}
+7 -3
View File
@@ -13,6 +13,7 @@ import {
import { BrowseMediaUtil } from '../browse-media-util.js'; import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js'; import { View } from '../view.js';
import { renderProgressIndicator } from './message.js'; import { renderProgressIndicator } from './message.js';
import { stopEventFromActivatingCardWideActions } from '../common.js';
import galleryStyle from '../scss/gallery.scss'; import galleryStyle from '../scss/gallery.scss';
@@ -161,10 +162,11 @@ export class FrigateCardGalleryCore extends LitElement {
<div class="mdc-image-list__image-aspect-container"> <div class="mdc-image-list__image-aspect-container">
<div class="mdc-image-list__image"> <div class="mdc-image-list__image">
<ha-card <ha-card
@click=${() => { @click=${(ev) => {
if (this.view && this.view.previous) { if (this.view && this.view.previous) {
this.view.previous.dispatchChangeEvent(this); this.view.previous.dispatchChangeEvent(this);
} }
stopEventFromActivatingCardWideActions(ev);
}} }}
outlined="" outlined=""
class="frigate-card-gallery-folder" class="frigate-card-gallery-folder"
@@ -182,7 +184,7 @@ export class FrigateCardGalleryCore extends LitElement {
${child.can_expand ${child.can_expand
? html`<div class="mdc-image-list__image"> ? html`<div class="mdc-image-list__image">
<ha-card <ha-card
@click=${() => { @click=${(ev) => {
if (this.hass && this.view) { if (this.hass && this.view) {
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange( BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
this, this,
@@ -191,6 +193,7 @@ export class FrigateCardGalleryCore extends LitElement {
child, child,
); );
} }
stopEventFromActivatingCardWideActions(ev);
}} }}
outlined="" outlined=""
class="frigate-card-gallery-folder" class="frigate-card-gallery-folder"
@@ -204,7 +207,7 @@ export class FrigateCardGalleryCore extends LitElement {
class="mdc-image-list__image" class="mdc-image-list__image"
src="${child.thumbnail}" src="${child.thumbnail}"
title="${child.title}" title="${child.title}"
@click=${() => { @click=${(ev) => {
if (this.view) { if (this.view) {
this.view this.view
.evolve({ .evolve({
@@ -214,6 +217,7 @@ export class FrigateCardGalleryCore extends LitElement {
}) })
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
stopEventFromActivatingCardWideActions(ev);
}} }}
/>` />`
: ``} : ``}
+5 -2
View File
@@ -50,6 +50,7 @@ import {
getCameraIcon, getCameraIcon,
getCameraTitle, getCameraTitle,
homeAssistantSignPath, homeAssistantSignPath,
stopEventFromActivatingCardWideActions,
} from '../common.js'; } from '../common.js';
import { renderProgressIndicator } from '../components/message.js'; import { renderProgressIndicator } from '../components/message.js';
@@ -550,8 +551,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
.label=${getCameraTitle(this.hass, prev)} .label=${getCameraTitle(this.hass, prev)}
.icon=${getCameraIcon(this.hass, prev)} .icon=${getCameraIcon(this.hass, prev)}
?disabled=${prev == null} ?disabled=${prev == null}
@click=${() => { @click=${(ev) => {
this._nextPreviousHandler('previous'); this._nextPreviousHandler('previous');
stopEventFromActivatingCardWideActions(ev);
}} }}
> >
</frigate-card-next-previous-control> </frigate-card-next-previous-control>
@@ -565,8 +567,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
.label=${getCameraTitle(this.hass, next)} .label=${getCameraTitle(this.hass, next)}
.icon=${getCameraIcon(this.hass, next)} .icon=${getCameraIcon(this.hass, next)}
?disabled=${next == null} ?disabled=${next == null}
@click=${() => { @click=${(ev) => {
this._nextPreviousHandler('next'); this._nextPreviousHandler('next');
stopEventFromActivatingCardWideActions(ev);
}} }}
> >
</frigate-card-next-previous-control> </frigate-card-next-previous-control>
+12 -4
View File
@@ -2,7 +2,11 @@ import type { Corner, Menu } from '@material/mwc-menu';
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { HomeAssistant } from 'custom-card-helpers'; import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, query } from 'lit/decorators'; 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 { ifDefined } from 'lit/directives/if-defined';
import { styleMap } from 'lit/directives/style-map'; import { styleMap } from 'lit/directives/style-map';
@@ -68,7 +72,10 @@ export class FrigateCardSubmenu extends LitElement {
protected _getFixedRoot(): HTMLElement | null { protected _getFixedRoot(): HTMLElement | null {
let n = this as Node | null; let n = this as Node | null;
while (n) { 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; return n as HTMLElement;
} }
n = n.parentNode n = n.parentNode
@@ -93,7 +100,7 @@ export class FrigateCardSubmenu extends LitElement {
hasHold: frigateCardHasAction(this.submenu.hold_action), hasHold: frigateCardHasAction(this.submenu.hold_action),
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action), hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
})} })}
@click=${() => { @click=${(ev) => {
if (this._menu) { if (this._menu) {
// Hack: This insanity is brought about by lack of MWCMenu playing // Hack: This insanity is brought about by lack of MWCMenu playing
// nicely with the Home Assistant view/sidepanel. The menu must be // nicely with the Home Assistant view/sidepanel. The menu must be
@@ -116,12 +123,13 @@ export class FrigateCardSubmenu extends LitElement {
} }
this._menu.show(); this._menu.show();
} }
stopEventFromActivatingCardWideActions(ev);
}} }}
> >
<ha-icon icon="${this.submenu.icon}"></ha-icon> <ha-icon icon="${this.submenu.icon}"></ha-icon>
</ha-icon-button> </ha-icon-button>
<mwc-menu <mwc-menu
.corner=${this.corner || "BOTTOM_LEFT"} .corner=${this.corner || 'BOTTOM_LEFT'}
fixed fixed
@closed=${ @closed=${
// Prevent the submenu closing from closing anything upstream (e.g. // Prevent the submenu closing from closing anything upstream (e.g.
+3 -2
View File
@@ -5,7 +5,7 @@ import { customElement, property } from 'lit/decorators.js';
import type { BrowseMediaSource, ThumbnailsControlConfig } from '../types.js'; import type { BrowseMediaSource, ThumbnailsControlConfig } from '../types.js';
import { FrigateCardCarousel } from './carousel.js'; import { FrigateCardCarousel } from './carousel.js';
import { dispatchFrigateCardEvent } from '../common.js'; import { dispatchFrigateCardEvent, stopEventFromActivatingCardWideActions } from '../common.js';
import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss'; import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss';
@@ -111,7 +111,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
return html`<div return html`<div
class="embla__slide" class="embla__slide"
@click=${() => { @click=${(ev) => {
if (this._carousel && this._carousel.clickAllowed()) { if (this._carousel && this._carousel.clickAllowed()) {
dispatchFrigateCardEvent<ThumbnailCarouselTap>(this, 'carousel:tap', { dispatchFrigateCardEvent<ThumbnailCarouselTap>(this, 'carousel:tap', {
slideIndex: slideIndex, slideIndex: slideIndex,
@@ -119,6 +119,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
childIndex: childIndex, childIndex: childIndex,
}); });
} }
stopEventFromActivatingCardWideActions(ev);
}} }}
> >
<img <img
+5 -2
View File
@@ -38,6 +38,7 @@ import {
contentsChanged, contentsChanged,
createMediaShowInfo, createMediaShowInfo,
dispatchErrorMessageEvent, dispatchErrorMessageEvent,
stopEventFromActivatingCardWideActions,
} from '../common.js'; } from '../common.js';
import { renderProgressIndicator } from '../components/message.js'; import { renderProgressIndicator } from '../components/message.js';
@@ -655,8 +656,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
.thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined} .thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined}
.label=${prev ? prev.title : ''} .label=${prev ? prev.title : ''}
?disabled=${!prev} ?disabled=${!prev}
@click=${() => { @click=${(ev) => {
this._nextPreviousHandler('previous'); this._nextPreviousHandler('previous');
stopEventFromActivatingCardWideActions(ev);
}} }}
></frigate-card-next-previous-control> ></frigate-card-next-previous-control>
<div class="embla__viewport"> <div class="embla__viewport">
@@ -669,8 +671,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined} .thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''} .label=${next ? next.title : ''}
?disabled=${!next} ?disabled=${!next}
@click=${() => { @click=${(ev) => {
this._nextPreviousHandler('next'); this._nextPreviousHandler('next');
stopEventFromActivatingCardWideActions(ev);
}} }}
></frigate-card-next-previous-control> ></frigate-card-next-previous-control>
</div> </div>