/** @license Copyright (c) 2022 trading_peter This program is available under Apache License Version 2.0 */ import { LitElement, html, css } from 'lit'; import { DomQuery } from '@tp/helpers/dom-query.js'; import { Position } from '@tp/helpers/position.js'; import { EventHelpers } from '@tp/helpers/event-helpers.js'; import { closest } from '@tp/helpers/closest.js'; class TpPopup extends EventHelpers(Position(DomQuery(LitElement))) { static get styles() { return [ css` :host { display: inline-block; position: relative; outline: 0; } .toggle { cursor: pointer; } #content { pointer-events: none; position: fixed; z-index: 1; margin-top: 5px; transition: opacity 180ms; opacity: 0; border-radius: 2px; box-shadow: var(--tp-popup-shadow, none); } #content[open] { pointer-events: all; opacity: 1; background: var(--tp-popup-background, transparent); } #content .content-wrap { padding: var(--tp-popup-content-padding, 0px); } @media all and (min-width: 0) and (max-width: 480px) { :host(:not([not-responsive])) #content { bottom: 0 !important; top: auto !important; right: 0 !important; left: 0 !important; margin-top: 0; transition: transform 0.3s, opacity 0.3s; transform: translateY(100%); max-height: 80%; overflow: hidden; overflow-y: auto; box-shadow: var(--tp-popup-shadow-responsive, none); } :host(:not([not-responsive])) #content[open] { transform: translateY(0%); } } ` ]; } render() { const { isOpen } = this; return html`
`; } static get properties() { return { isOpen: { type: Boolean, reflect: true }, alwaysToggle: { type: Boolean, reflect: true }, halign: { type: String }, valign: { type: String }, scrollTarget: { type: Object } }; } constructor() { super(); this.isOpen = false; this.alwaysToggle = false; this.halign = 'middle'; this.valign = 'bottom'; this.scrollTarget = document; this.fit = this.fit.bind(this); this._onDocClickHandler = () => this.close(); } get toggleEl() { if (!this._toggleEl) { this._toggleEl = this.querySelector('[slot="toggle"]'); } return this._toggleEl; } firstUpdated() { super.firstUpdated(); this.listen(this, 'click', '_onClick'); } updated(changes) { // Fire event only if isOpen wasn't undefined before. Otherwise an event is fired at the first render. if (changes.has('isOpen') && changes.get('isOpen') !== undefined) { this.dispatchEvent(new CustomEvent('is-open-changed', { detail: this.isOpen, bubbles: true, composed: true })); } } disconnectedCallback() { super.disconnectedCallback(); this._cleanupEvents(); } close() { this.isOpen = false; this._cleanupEvents(); } open() { this.updateComplete.then(() => { this.fit(); }); this.isOpen = true; this._registerEvents(); const autofocusEl = this.querySelector('[autofocus]'); if (autofocusEl) { autofocusEl.focus(); } } toggle() { if (!this.isOpen) { this.open(); } else { this.isOpen = false; this._cleanupEvents(); } } /** * Set correct position on the popup to fit into the window. */ fit() { this._posFixed(this.shadowRoot.querySelector('.toggle'), this.$.content, { valign: this.valign || 'bottom', halign: this.halign || 'middle', spacing: 5 }); } _docClick(e) { const isThisPopup = e.composedPath().indexOf(this) > -1; if (!isThisPopup && this.isOpen) { this.close(); } } _onClick(e) { const toggle = closest(e.composedPath()[0], '[slot="toggle"]', true) === this.toggleEl; const closePopup = closest(e.composedPath()[0], '[close-popup]', true); if (toggle && this.toggleEl.hasAttribute('disabled')) return; if (toggle || this.alwaysToggle && this.isOpen) { this.toggle(); } if (closePopup) { this.close(); } } _registerEvents() { this._cleanupEvents(); this.listen(document, 'mousedown', '_docClick'); this.scrollTarget.addEventListener('scroll', this.fit, { passive: true }); this.scrollTarget.addEventListener('layout', this.fit, { passive: true }); } _cleanupEvents() { this.unlisten(document, 'mousedown', '_docClick'); this.scrollTarget.removeEventListener('scroll', this.fit, { passive: true }); this.scrollTarget.removeEventListener('layout', this.fit, { passive: true }); } } window.customElements.define('tp-popup', TpPopup);