Merge pull request #461 from dermotduffy/menu-relative

Fix menu rendering placement
This commit is contained in:
Dermot Duffy
2022-03-13 13:37:33 -07:00
committed by GitHub
5 changed files with 51 additions and 77 deletions
+10 -4
View File
@@ -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 => {
const options = element.actionHandlerOptions;
if (!options?.allowPropagation) {
// This will ensure only 1 actionHandler is invoked for a given interaction. // This will ensure only 1 actionHandler is invoked for a given interaction.
stopEventFromActivatingCardWideActions(ev); stopEventFromActivatingCardWideActions(ev);
}
const options = element.actionHandlerOptions;
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
View File
@@ -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
View File
@@ -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;
+8
View File
@@ -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;
-4
View File
@@ -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);
}