2018-02-10 15:18:01 +00:00
|
|
|
import ngModule from '../../module';
|
2018-03-12 11:31:50 +00:00
|
|
|
import Component from '../../lib/component';
|
|
|
|
import './style.scss';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A simple popover.
|
|
|
|
*/
|
|
|
|
export default class Popover extends Component {
|
|
|
|
constructor($element, $scope, $timeout, $transitions) {
|
|
|
|
super($element, $scope);
|
|
|
|
this.$timeout = $timeout;
|
2017-07-05 11:02:49 +00:00
|
|
|
this.$transitions = $transitions;
|
2018-03-12 11:31:50 +00:00
|
|
|
this._shown = false;
|
2018-03-13 11:07:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$postLink() {
|
|
|
|
this.$element.addClass('vn-popover');
|
2018-03-12 11:31:50 +00:00
|
|
|
|
2017-07-05 11:02:49 +00:00
|
|
|
this.docKeyDownHandler = e => this.onDocKeyDown(e);
|
2018-03-12 11:31:50 +00:00
|
|
|
this.docFocusInHandler = e => this.onDocFocusIn(e);
|
|
|
|
|
|
|
|
this.element.addEventListener('mousedown',
|
|
|
|
e => this.onBackgroundMouseDown(e));
|
|
|
|
this.element.addEventListener('focusin',
|
|
|
|
e => this.onFocusIn(e));
|
|
|
|
|
|
|
|
this.popover = this.element.querySelector('.popover');
|
|
|
|
this.popover.addEventListener('mousedown',
|
|
|
|
e => this.onMouseDown(e));
|
|
|
|
|
|
|
|
this.arrow = this.element.querySelector('.arrow');
|
|
|
|
this.content = this.element.querySelector('.content');
|
|
|
|
}
|
|
|
|
|
|
|
|
set child(value) {
|
|
|
|
this.content.appendChild(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
get child() {
|
|
|
|
return this.content.firstChild;
|
|
|
|
}
|
|
|
|
|
|
|
|
get shown() {
|
|
|
|
return this._shown;
|
|
|
|
}
|
|
|
|
|
|
|
|
set shown(value) {
|
|
|
|
if (value)
|
|
|
|
this.show();
|
|
|
|
else
|
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows the popover. If a parent is specified it is shown in a visible
|
|
|
|
* relative position to it.
|
|
|
|
*/
|
|
|
|
show() {
|
|
|
|
if (this._shown) return;
|
|
|
|
|
|
|
|
this._shown = true;
|
|
|
|
this.element.style.display = 'block';
|
|
|
|
this.$timeout.cancel(this.showTimeout);
|
|
|
|
this.showTimeout = this.$timeout(() => {
|
|
|
|
this.$element.addClass('shown');
|
|
|
|
this.showTimeout = null;
|
|
|
|
}, 30);
|
|
|
|
|
2017-07-05 11:02:49 +00:00
|
|
|
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
2018-03-12 11:31:50 +00:00
|
|
|
this.document.addEventListener('focusin', this.docFocusInHandler);
|
|
|
|
|
|
|
|
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
|
|
|
|
this.relocate();
|
|
|
|
|
|
|
|
if (this.onOpen)
|
|
|
|
this.onOpen();
|
2017-07-05 11:02:49 +00:00
|
|
|
}
|
2018-03-12 11:31:50 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the popover.
|
|
|
|
*/
|
|
|
|
hide() {
|
|
|
|
if (!this._shown) return;
|
|
|
|
|
|
|
|
this._shown = false;
|
|
|
|
this.$element.removeClass('shown');
|
|
|
|
this.$timeout.cancel(this.showTimeout);
|
|
|
|
this.showTimeout = this.$timeout(() => {
|
|
|
|
this.element.style.display = 'none';
|
|
|
|
this.showTimeout = null;
|
|
|
|
}, 250);
|
|
|
|
|
2017-07-05 11:02:49 +00:00
|
|
|
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
2018-03-12 11:31:50 +00:00
|
|
|
this.document.removeEventListener('focusin', this.docFocusInHandler);
|
2017-07-05 07:47:24 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
if (this.deregisterCallback)
|
|
|
|
this.deregisterCallback();
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
if (this.onClose)
|
|
|
|
this.onClose();
|
|
|
|
}
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
/**
|
|
|
|
* Repositions the popover to a correct location relative to the parent.
|
|
|
|
*/
|
|
|
|
relocate() {
|
|
|
|
if (!(this.parent && this._shown)) return;
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let style = this.popover.style;
|
|
|
|
style.width = '';
|
|
|
|
style.height = '';
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let arrowStyle = this.arrow.style;
|
|
|
|
arrowStyle.top = '';
|
|
|
|
arrowStyle.bottom = '';
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let parentRect = this.parent.getBoundingClientRect();
|
|
|
|
let popoverRect = this.popover.getBoundingClientRect();
|
|
|
|
let arrowRect = this.arrow.getBoundingClientRect();
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let top = parentRect.top + parentRect.height + arrowHeight;
|
|
|
|
let left = parentRect.left;
|
|
|
|
let height = popoverRect.height;
|
|
|
|
let width = Math.max(popoverRect.width, parentRect.width);
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
let margin = 10;
|
|
|
|
let showTop = top + height + margin > window.innerHeight;
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
if (showTop)
|
|
|
|
top = Math.max(parentRect.top - height - arrowHeight, margin);
|
|
|
|
if (left + width + margin > window.innerWidth)
|
|
|
|
left = window.innerWidth - width - margin;
|
2017-01-31 13:13:06 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
if (showTop)
|
|
|
|
arrowStyle.bottom = `0`;
|
|
|
|
else
|
|
|
|
arrowStyle.top = `0`;
|
2017-02-01 09:05:15 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
arrowStyle.left = `${(parentRect.left - left) + parentRect.width / 2}px`;
|
2017-06-03 11:01:47 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
style.top = `${top}px`;
|
|
|
|
style.left = `${left}px`;
|
|
|
|
style.width = `${width}px`;
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
if (height + margin * 2 + arrowHeight > window.innerHeight)
|
|
|
|
style.height = `${window.innerHeight - margin * 2 - arrowHeight}px`;
|
2017-02-01 09:05:15 +00:00
|
|
|
}
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
onDocKeyDown(event) {
|
|
|
|
if (event.defaultPrevented) return;
|
|
|
|
|
|
|
|
if (event.keyCode == 27) { // Esc
|
|
|
|
event.preventDefault();
|
|
|
|
this.hide();
|
2017-06-15 13:00:43 +00:00
|
|
|
}
|
2017-02-01 09:05:15 +00:00
|
|
|
}
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
onMouseDown(event) {
|
|
|
|
this.lastMouseEvent = event;
|
2017-07-05 07:47:24 +00:00
|
|
|
}
|
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
onBackgroundMouseDown(event) {
|
|
|
|
if (event != this.lastMouseEvent)
|
|
|
|
this.hide();
|
2017-07-05 07:47:24 +00:00
|
|
|
}
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
onFocusIn(event) {
|
|
|
|
this.lastFocusEvent = event;
|
2017-02-01 09:05:15 +00:00
|
|
|
}
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
onDocFocusIn(event) {
|
|
|
|
if (event !== this.lastFocusEvent)
|
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];
|
|
|
|
|
|
|
|
ngModule.component('vnPopover', {
|
|
|
|
template: require('./popover.html'),
|
|
|
|
controller: Popover,
|
|
|
|
transclude: true,
|
|
|
|
bindings: {
|
|
|
|
onOpen: '&?',
|
|
|
|
onClose: '&?'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
class PopoverService {
|
|
|
|
constructor($document, $compile, $transitions, $rootScope) {
|
|
|
|
this.$compile = $compile;
|
|
|
|
this.$rootScope = $rootScope;
|
|
|
|
this.$document = $document;
|
|
|
|
this.stack = [];
|
|
|
|
}
|
|
|
|
show(child, parent, $scope) {
|
|
|
|
let element = this.$compile('<vn-popover/>')($scope || this.$rootScope)[0];
|
|
|
|
let popover = element.$ctrl;
|
|
|
|
popover.parent = parent;
|
|
|
|
popover.child = child;
|
|
|
|
popover.show();
|
|
|
|
popover.onClose = () => {
|
|
|
|
this.$document[0].body.removeChild(element);
|
|
|
|
if ($scope) $scope.$destroy();
|
|
|
|
};
|
|
|
|
this.$document[0].body.appendChild(element);
|
|
|
|
return popover;
|
2017-06-03 11:01:47 +00:00
|
|
|
}
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
showComponent(componentTag, $scope, parent) {
|
|
|
|
let $newScope = $scope.$new();
|
|
|
|
let childElement = this.$compile(`<${componentTag}/>`)($newScope)[0];
|
|
|
|
this.show(childElement, parent, $newScope);
|
|
|
|
return childElement;
|
2017-02-01 09:05:15 +00:00
|
|
|
}
|
2017-01-31 13:13:06 +00:00
|
|
|
}
|
2018-03-12 11:31:50 +00:00
|
|
|
PopoverService.$inject = ['$document', '$compile', '$transitions', '$rootScope'];
|
2017-10-04 14:02:53 +00:00
|
|
|
|
2018-03-12 11:31:50 +00:00
|
|
|
ngModule.service('vnPopover', PopoverService);
|