Don't trigger card-wide actions on card controls
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}}
|
}}
|
||||||
/>`
|
/>`
|
||||||
: ``}
|
: ``}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user