@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
158 lines (140 loc) • 4.44 kB
JavaScript
import { LitElement, html, css } from "lit";
import { panel } from "../styles";
import { listenForMenuClosure } from "../../utils/widgets";
import { classMap } from "lit/directives/class-map.js";
/**
* Togglable Group element allows to make a menu appear/disappear based on button click.
* @class Panoramax.components.ui.TogglableGroup
* @element pnx-togglable-group
* @slot `button` The button on which click leads to open/close menu
* @slot `default` The menu to make shown/hidden on button click
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
* @example
* ```html
* <pnx-togglable-group padded="false" ._parent=${viewer}>
* <pnx-button slot="button">
* Open menu
* </pnx-button>
* <div>Menu content</div>
* </pnx-togglable-group>
* ```
*/
export default class TogglableGroup extends LitElement {
/** @private */
static styles = [ panel, css`
div.container {
position: relative;
height: 100%;
}
.pnx-panel { z-index: 130; }
.pnx-panel-fixed { position: fixed; }
` ];
/**
* Component properties.
* @memberof Panoramax.components.ui.TogglableGroup#
* @type {Object}
* @property {string} [padded] If set, adds a 15px padding to the menu panel.
*/
static properties = {
_opened: {state: true},
padded: {type: String},
};
constructor() {
super();
this._opened = false;
}
/** @private */
open() {
this._opened = true;
this._parent?.dispatchEvent(new CustomEvent("menu-opened", { detail: { menu: this }}));
this.adjustMenuPosition(this._btn);
if(this._chevron) { this._chevron.style.rotate = "180deg"; }
}
/** @private */
close() {
this._opened = false;
if(this._chevron) { this._chevron.style.rotate = ""; }
}
/** @private */
connectedCallback() {
super.connectedCallback();
listenForMenuClosure(this, this.close.bind(this));
}
/** @private */
handleButtonSlotChange(e) {
this._btn = e.target.assignedNodes().pop();
if(this._btn) {
// Chevron
this._chevron = this._btn.querySelector("svg.fa-chevron-down");
if(this._chevron) {
this._chevron.style.transition = "rotate 0.2s";
this._chevron.style.rotate = this._opened ? "180deg" : "";
}
this._btn.addEventListener("click", () => {
if(this._opened) { this.close(); }
else { this.open(); }
});
}
}
/** @private */
adjustMenuPosition(btn) {
const btnMenuMargin = 5;
const borderMargin = 10;
// Reset menu position
const menu = this.shadowRoot.querySelector("div[part='menu']");
menu.style.top = null;
menu.style.bottom = null;
menu.style.right = null;
menu.style.left = null;
menu.style.overflowY = null;
menu.style.marginTop = null;
// Get positions on screen
const btnRect = btn.getBoundingClientRect();
let menuRect = menu.getBoundingClientRect();
const fitsWidth = menuRect.right <= window.innerWidth - borderMargin;
const fitsHeight = menuRect.bottom <= window.innerHeight - borderMargin;
// No overflow = space a bit under button
if(fitsWidth && fitsHeight) {
menu.style.top = `${btnRect.bottom+btnMenuMargin}px`;
}
// Overflows width+height = put at button's top+left
else if(!fitsWidth && !fitsHeight) {
menu.style.right = `${window.innerWidth - btnRect.left + btnMenuMargin}px`;
menu.style.bottom = `${window.innerHeight - btnRect.bottom}px`;
// Check if it doesn't overflow on top
menuRect = menu.getBoundingClientRect();
if(menuRect.top - borderMargin < 0) {
menu.style.bottom = `${borderMargin}px`;
menu.style.top = `${btnRect.top}px`;
menu.style.overflowY = "auto";
}
}
// Overflows height = limit height
else if(!fitsHeight) {
menu.style.bottom = `${borderMargin}px`;
menu.style.top = `${btnRect.bottom+btnMenuMargin}px`;
menu.style.overflowY = "auto";
}
// Overflows width = move to left
else if(!fitsWidth) {
menu.style.marginTop = `${btnMenuMargin}px`;
menu.style.right = `${window.innerWidth - btnRect.right}px`;
}
}
/** @private */
render() {
const panelClasses = {
"pnx-panel": true,
"pnx-panel-fixed": true,
"pnx-hidden": !this._opened,
"pnx-padded": this.padded !== "false",
};
return html`<div class="container" @click=${e => e.stopPropagation()}>
<slot name="button" @slotchange=${this.handleButtonSlotChange}></slot>
<div class=${classMap(panelClasses)} part="menu">
<slot></slot>
</div>
</div>`;
}
}
customElements.define("pnx-togglable-group", TogglableGroup);