Merge pull request #461 from dermotduffy/menu-relative
Fix menu rendering placement
This commit is contained in:
@@ -18,7 +18,7 @@ interface ActionHandler extends HTMLElement {
|
|||||||
bind(element: Element, options): void;
|
bind(element: Element, options): void;
|
||||||
}
|
}
|
||||||
interface ActionHandlerElement extends HTMLElement {
|
interface ActionHandlerElement extends HTMLElement {
|
||||||
actionHandlerOptions?: ActionHandlerOptions;
|
actionHandlerOptions?: FrigateCardActionHandlerOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -27,6 +27,10 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FrigateCardActionHandlerOptions extends ActionHandlerOptions {
|
||||||
|
allowPropagation?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
class ActionHandler extends HTMLElement implements ActionHandler {
|
class ActionHandler extends HTMLElement implements ActionHandler {
|
||||||
public holdTime = 400;
|
public holdTime = 400;
|
||||||
|
|
||||||
@@ -94,10 +98,12 @@ 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 (!options?.allowPropagation) {
|
||||||
|
// This will ensure only 1 actionHandler is invoked for a given interaction.
|
||||||
|
stopEventFromActivatingCardWideActions(ev);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
['touchend', 'touchcancel'].includes(ev.type) &&
|
['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
|
||||||
@@ -169,7 +175,7 @@ const getActionHandler = (): ActionHandler => {
|
|||||||
|
|
||||||
export const actionHandlerBind = (
|
export const actionHandlerBind = (
|
||||||
element: ActionHandlerElement,
|
element: ActionHandlerElement,
|
||||||
options?: ActionHandlerOptions,
|
options?: FrigateCardActionHandlerOptions,
|
||||||
): void => {
|
): void => {
|
||||||
const actionhandler: ActionHandler = getActionHandler();
|
const actionhandler: ActionHandler = getActionHandler();
|
||||||
if (!actionhandler) {
|
if (!actionhandler) {
|
||||||
@@ -186,6 +192,6 @@ export const actionHandler = directive(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||||
render(_options?: ActionHandlerOptions) {}
|
render(_options?: FrigateCardActionHandlerOptions) {}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+23
-66
@@ -1,7 +1,7 @@
|
|||||||
import type { Corner, Menu } from '@material/mwc-menu';
|
import type { Corner } 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.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import {
|
import {
|
||||||
frigateCardHasAction,
|
frigateCardHasAction,
|
||||||
refreshDynamicStateParameters,
|
refreshDynamicStateParameters,
|
||||||
@@ -26,8 +26,6 @@ export class FrigateCardSubmenu extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public corner?: Corner;
|
public corner?: Corner;
|
||||||
|
|
||||||
@query('mwc-menu') private _menu?: Menu;
|
|
||||||
|
|
||||||
protected _renderItem(item: MenuSubmenuItem): TemplateResult | void {
|
protected _renderItem(item: MenuSubmenuItem): TemplateResult | void {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
@@ -65,81 +63,40 @@ export class FrigateCardSubmenu extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the fixed root element in which fixed elements are positioned against.
|
|
||||||
* @returns The fixed root element or null if not found.
|
|
||||||
*/
|
|
||||||
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'
|
|
||||||
) {
|
|
||||||
return n as HTMLElement;
|
|
||||||
}
|
|
||||||
n = n.parentNode
|
|
||||||
? n.parentNode
|
|
||||||
: n.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
|
||||||
? (n as ShadowRoot).host
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.submenu) {
|
if (!this.submenu) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-icon-button
|
<ha-button-menu
|
||||||
style="${styleMap(this.submenu.style || {})}"
|
corner=${this.corner || 'BOTTOM_LEFT'}
|
||||||
class="button"
|
|
||||||
.label=${this.submenu.title || ''}
|
|
||||||
.actionHandler=${actionHandler({
|
|
||||||
hasHold: frigateCardHasAction(this.submenu.hold_action),
|
|
||||||
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
|
|
||||||
})}
|
|
||||||
@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
|
|
||||||
// rendered in fixed mode in order to allow the menu to render
|
|
||||||
// outside of the card boundaries (card has overflow as hidden).
|
|
||||||
// When in fixed mode, the menu anchoring refuses to get the
|
|
||||||
// placement correct -- it's always off by exactly the dimensions of
|
|
||||||
// the sidebar/header. To work around this we iterate up the DOM to
|
|
||||||
// find the main root (excl. the sidebar) and subtract those
|
|
||||||
// dimensions off wherever the menu believes it should render.
|
|
||||||
this._menu.anchor = this;
|
|
||||||
const root = this._getFixedRoot();
|
|
||||||
if (root) {
|
|
||||||
const rootPosition = root.getBoundingClientRect();
|
|
||||||
this._menu.x = -rootPosition.x;
|
|
||||||
this._menu.y = -rootPosition.y;
|
|
||||||
} else {
|
|
||||||
this._menu.x = 0;
|
|
||||||
this._menu.y = 0;
|
|
||||||
}
|
|
||||||
this._menu.show();
|
|
||||||
}
|
|
||||||
stopEventFromActivatingCardWideActions(ev);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ha-icon icon="${this.submenu.icon}"></ha-icon>
|
|
||||||
</ha-icon-button>
|
|
||||||
<mwc-menu
|
|
||||||
.corner=${this.corner || 'BOTTOM_LEFT'}
|
|
||||||
fixed
|
|
||||||
@closed=${
|
@closed=${
|
||||||
// Prevent the submenu closing from closing anything upstream (e.g.
|
// Prevent the submenu closing from closing anything upstream (e.g.
|
||||||
// selecting a submenu in the editor dialog should not close the
|
// selecting a submenu in the editor dialog should not close the
|
||||||
// editor, see https://github.com/dermotduffy/frigate-hass-card/issues/377).
|
// editor, see https://github.com/dermotduffy/frigate-hass-card/issues/377).
|
||||||
(ev) => ev.stopPropagation()
|
(ev) => ev.stopPropagation()
|
||||||
}
|
}
|
||||||
|
@click=${(ev) => stopEventFromActivatingCardWideActions(ev)}
|
||||||
>
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
style="${styleMap(this.submenu.style || {})}"
|
||||||
|
class="button"
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.submenu.title || ''}
|
||||||
|
.actionHandler=${actionHandler({
|
||||||
|
// Need to allow event to propagate upwards, as it's caught by the
|
||||||
|
// <ha-button-menu> trigger slot to open/close the menu. Further
|
||||||
|
// propagation is forbidden by the @click handler on
|
||||||
|
// <ha-button-menu>.
|
||||||
|
allowPropagation: true,
|
||||||
|
hasHold: frigateCardHasAction(this.submenu.hold_action),
|
||||||
|
hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-icon icon="${this.submenu.icon}"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
${this.submenu.items.map(this._renderItem.bind(this))}
|
${this.submenu.items.map(this._renderItem.bind(this))}
|
||||||
</mwc-menu>
|
</ha-button-menu>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -13,6 +13,11 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
// Need to apply the border radius on the container level, as the ha-card has
|
||||||
|
// overflow visible in order to allow a submenu to extend beyond the card
|
||||||
|
// boundary.
|
||||||
|
border-radius: var(--ha-card-border-radius, 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.frigate-card-contents {
|
.frigate-card-contents {
|
||||||
@@ -58,7 +63,9 @@ ha-card {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
overflow: hidden;
|
|
||||||
|
// Some elements (such as submenus) may need to extend beyond the card boundary.
|
||||||
|
overflow: visible;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: static;
|
position: static;
|
||||||
|
|||||||
@@ -18,6 +18,14 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--secondary-background-color);
|
background: var(--secondary-background-color);
|
||||||
}
|
}
|
||||||
|
:host([data-mode="above"]) {
|
||||||
|
border-top-left-radius: var(--ha-card-border-radius, 4px);
|
||||||
|
border-top-right-radius: var(--ha-card-border-radius, 4px);
|
||||||
|
}
|
||||||
|
:host([data-mode="below"]) {
|
||||||
|
border-bottom-left-radius: var(--ha-card-border-radius, 4px);
|
||||||
|
border-bottom-right-radius: var(--ha-card-border-radius, 4px);
|
||||||
|
}
|
||||||
|
|
||||||
:host([data-mode$="-left"]) {
|
:host([data-mode$="-left"]) {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
|||||||
@@ -7,7 +7,3 @@
|
|||||||
mwc-list-item {
|
mwc-list-item {
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-list-item[disabled] {
|
|
||||||
color: var(--disabled-text-color);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user