UNPKG

@panoramax/web-viewer

Version:

Panoramax web viewer for geolocated pictures

158 lines (140 loc) 4.44 kB
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);