import ngModule from '../../module'; import Popup from '../popup'; import isMobile from '../../lib/is-mobile'; import './style.scss'; /** * A simple popover. * * @property {HTMLElement} parent The parent element to show drop down relative to * @property {HTMLElement} content Element holding the popover content * * @event open Thrown when popover is displayed * @event close Thrown when popover is hidden */ export default class Popover extends Popup { constructor(...args) { super(...args); this.displayMode = isMobile ? 'centered' : 'relative'; } /** * Shows the popover emitting the open signal. If a parent is specified * it is shown in a visible relative position to it. * * @param {HTMLElement|Event} parent Overrides the parent property * @param {String} direction - Direction [left] */ show(parent, direction) { if (parent instanceof Event) parent = event.target; if (parent) this.parent = parent; if (direction) this.direction = direction; super.show(); this.content = this.popup.querySelector('.content'); this.$timeout(() => this.relocate(), 10); } hide() { this.content = null; super.hide(); } get parent() { return this.__parent; } // Bug #2147 Popover loses parent location set parent(value) { this.__parent = value; if (!value) return; const parentRect = value.getBoundingClientRect(); this.parentRect = {}; for (let prop in parentRect) this.parentRect[prop] = parentRect[prop]; } /** * Repositions the popover to a correct location relative to the parent. */ relocate() { if (!(this.parent && this._shown && this.displayMode == 'relative')) return; let margin = 10; let arrow = this.popup.querySelector('.arrow'); let style = this.windowEl.style; style.width = ''; style.height = ''; let arrowStyle = arrow.style; arrowStyle.top = ''; arrowStyle.bottom = ''; let parentRect = this.parentRect; let popoverRect = this.windowEl.getBoundingClientRect(); let arrowRect = arrow.getBoundingClientRect(); let clamp = (value, min, max) => Math.min(Math.max(value, min), max); let arrowHeight = Math.floor(arrowRect.height / 2); let arrowOffset = arrowHeight + margin / 2; let docEl = this.document.documentElement; let maxRight = Math.min(window.innerWidth, docEl.clientWidth) - margin; let maxBottom = Math.min(window.innerHeight, docEl.clientHeight) - margin; let maxWith = maxRight - margin; let maxHeight = maxBottom - margin - arrowHeight; let width = clamp(popoverRect.width, parentRect.width, maxWith); let height = popoverRect.height; let left; if (this.direction == 'left') { left = parentRect.left + parentRect.width; left = clamp(left, margin, maxRight - width); } else { left = parentRect.left + parentRect.width / 2 - width / 2; left = clamp(left, margin, maxRight - width); } let top; if (this.direction == 'left') top = parentRect.top; else top = parentRect.top + parentRect.height + arrowOffset; let showTop = top + height > maxBottom; if (showTop) top = parentRect.top - height - arrowOffset; top = Math.max(top, margin); if (this.direction == 'left') arrowStyle.left = `0`; else if (showTop) arrowStyle.bottom = `0`; else arrowStyle.top = `0`; let arrowLeft; if (this.direction == 'left') { arrowLeft = 0; let arrowTop = arrowOffset; arrowStyle.top = `${arrowTop}px`; } else { arrowLeft = (parentRect.left - left) + parentRect.width / 2; arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight); } arrowStyle.left = `${arrowLeft}px`; style.top = `${top}px`; style.left = `${left}px`; style.width = `${width}px`; if (height > maxHeight) style.height = `${maxHeight}px`; } } ngModule.vnComponent('vnPopover', { controller: Popover }); ngModule.run(['$compile', function($compile) { Popover.prototype.contentLinkFn = $compile(require('./index.html')); }]);