salix/front/core/components/popover/index.js

144 lines
4.4 KiB
JavaScript

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
*/
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'));
}]);