2018-02-10 15:18:01 +00:00
|
|
|
import ngModule from '../../module';
|
2018-05-22 09:44:24 +00:00
|
|
|
import Component from '../../lib/component';
|
2020-03-09 08:00:03 +00:00
|
|
|
import {kebabToCamel} from '../../lib/string';
|
2018-05-22 09:44:24 +00:00
|
|
|
import './style.scss';
|
|
|
|
|
|
|
|
let positions = ['left', 'right', 'up', 'down'];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A simple tooltip.
|
|
|
|
*
|
|
|
|
* @property {String} position The relative position to the parent
|
|
|
|
*/
|
|
|
|
export default class Tooltip extends Component {
|
|
|
|
constructor($element, $scope, $timeout) {
|
|
|
|
super($element, $scope);
|
|
|
|
this.$timeout = $timeout;
|
2019-10-18 19:36:30 +00:00
|
|
|
$element.addClass('vn-tooltip vn-shadow');
|
2018-05-22 09:44:24 +00:00
|
|
|
this.position = 'down';
|
2018-10-15 10:46:56 +00:00
|
|
|
this.margin = 10;
|
2017-06-14 07:54:00 +00:00
|
|
|
}
|
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
/**
|
|
|
|
* Shows the tooltip.
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} parent The parent element
|
|
|
|
*/
|
|
|
|
show(parent) {
|
|
|
|
this.parent = parent;
|
|
|
|
this.$element.addClass('show');
|
|
|
|
this.relocate();
|
2018-05-25 15:25:35 +00:00
|
|
|
this.cancelTimeout();
|
|
|
|
this.relocateTimeout = this.$timeout(() => this.relocate(), 50);
|
2018-05-22 09:44:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the tooltip.
|
|
|
|
*/
|
|
|
|
hide() {
|
|
|
|
this.$element.removeClass('show');
|
2018-05-25 15:25:35 +00:00
|
|
|
this.cancelTimeout();
|
|
|
|
}
|
2018-05-22 09:44:24 +00:00
|
|
|
|
2018-05-25 15:25:35 +00:00
|
|
|
cancelTimeout() {
|
2018-05-22 09:44:24 +00:00
|
|
|
if (this.relocateTimeout) {
|
|
|
|
this.$timeout.cancel(this.relocateTimeout);
|
|
|
|
this.relocateTimeout = null;
|
2017-06-15 05:45:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
/**
|
2020-03-31 07:51:10 +00:00
|
|
|
* Repositions the tooltip acording to it's own size, position and parent location.
|
2018-05-22 09:44:24 +00:00
|
|
|
*/
|
|
|
|
relocate() {
|
|
|
|
let axis;
|
|
|
|
let position = this.position;
|
|
|
|
|
|
|
|
if (positions.indexOf(position) == -1)
|
|
|
|
position = 'down';
|
|
|
|
|
|
|
|
switch (position) {
|
|
|
|
case 'right':
|
|
|
|
case 'left':
|
|
|
|
axis = 'x';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
axis = 'y';
|
|
|
|
}
|
2017-06-08 07:29:03 +00:00
|
|
|
|
2018-10-15 10:46:56 +00:00
|
|
|
let arrowSize = this.margin;
|
|
|
|
let tipMargin = this.margin;
|
2018-05-22 09:44:24 +00:00
|
|
|
let rect = this.parent.getBoundingClientRect();
|
|
|
|
let tipRect = this.element.getBoundingClientRect();
|
|
|
|
|
|
|
|
let tipComputed = this.window.getComputedStyle(this.element, null);
|
|
|
|
let bgColor = tipComputed.getPropertyValue('background-color');
|
|
|
|
|
|
|
|
let min = tipMargin;
|
2018-10-15 10:46:56 +00:00
|
|
|
let maxTop = this.window.innerHeight - tipRect.height - tipMargin;
|
|
|
|
let maxLeft = this.window.innerWidth - tipRect.width - tipMargin;
|
2018-05-22 09:44:24 +00:00
|
|
|
|
|
|
|
// Coordinates
|
|
|
|
|
|
|
|
let top;
|
|
|
|
let left;
|
|
|
|
|
|
|
|
function calcCoords() {
|
|
|
|
top = rect.top;
|
|
|
|
left = rect.left;
|
|
|
|
|
|
|
|
if (axis == 'x')
|
|
|
|
top += rect.height / 2 - tipRect.height / 2;
|
|
|
|
else
|
|
|
|
left += rect.width / 2 - tipRect.width / 2;
|
|
|
|
|
|
|
|
switch (position) {
|
|
|
|
case 'right':
|
|
|
|
left += arrowSize + rect.width;
|
|
|
|
break;
|
|
|
|
case 'left':
|
|
|
|
left -= arrowSize + tipRect.width;
|
|
|
|
break;
|
|
|
|
case 'up':
|
|
|
|
top -= arrowSize + tipRect.height;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
top += arrowSize + rect.height;
|
2017-06-08 07:29:03 +00:00
|
|
|
}
|
2018-05-22 09:44:24 +00:00
|
|
|
}
|
|
|
|
calcCoords();
|
|
|
|
|
2018-05-25 15:25:35 +00:00
|
|
|
// Overflow
|
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
let axisOverflow =
|
|
|
|
axis == 'x' && (left < min || left > maxLeft) ||
|
|
|
|
axis == 'y' && (top < min || top > maxTop);
|
|
|
|
|
|
|
|
function switchPosition(position) {
|
|
|
|
switch (position) {
|
|
|
|
case 'right':
|
|
|
|
return 'left';
|
|
|
|
case 'left':
|
|
|
|
return 'right';
|
|
|
|
case 'up':
|
|
|
|
return 'down';
|
|
|
|
default:
|
|
|
|
return 'up';
|
2017-06-15 05:45:01 +00:00
|
|
|
}
|
2018-05-22 09:44:24 +00:00
|
|
|
}
|
2017-06-08 07:29:03 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
if (axisOverflow) {
|
|
|
|
position = switchPosition(position);
|
|
|
|
calcCoords();
|
|
|
|
}
|
2017-06-08 07:29:03 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
function range(coord, min, max) {
|
|
|
|
return Math.min(Math.max(coord, min), max);
|
|
|
|
}
|
2017-06-15 05:45:01 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
if (axis == 'x')
|
|
|
|
top = range(top, min, maxTop);
|
|
|
|
else
|
|
|
|
left = range(left, min, maxLeft);
|
2017-06-15 05:45:01 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
let style = this.element.style;
|
|
|
|
style.top = `${top}px`;
|
|
|
|
style.left = `${left}px`;
|
2018-02-28 08:12:48 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
// Arrow position
|
2018-02-28 08:12:48 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
if (this.arrow)
|
|
|
|
this.element.removeChild(this.arrow);
|
2018-02-28 08:12:48 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
let arrow = document.createElement('div');
|
|
|
|
arrow.className = 'arrow';
|
|
|
|
arrow.style.borderWidth = `${arrowSize}px`;
|
2018-02-28 08:12:48 +00:00
|
|
|
|
2018-05-22 09:44:24 +00:00
|
|
|
let arrowStyle = arrow.style;
|
|
|
|
|
|
|
|
if (axis == 'x') {
|
|
|
|
let arrowTop = (rect.top + rect.height / 2) - top - arrowSize;
|
|
|
|
arrowStyle.top = `${arrowTop}px`;
|
|
|
|
} else {
|
|
|
|
let arrowLeft = (rect.left + rect.width / 2) - left - arrowSize;
|
|
|
|
arrowStyle.left = `${arrowLeft}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
let arrowCoord = `${-tipMargin}px`;
|
|
|
|
|
|
|
|
switch (position) {
|
|
|
|
case 'right':
|
|
|
|
arrowStyle.left = arrowCoord;
|
|
|
|
arrowStyle.borderRightColor = bgColor;
|
|
|
|
arrowStyle.borderLeftWidth = 0;
|
|
|
|
break;
|
|
|
|
case 'left':
|
|
|
|
arrowStyle.right = arrowCoord;
|
|
|
|
arrowStyle.borderLeftColor = bgColor;
|
|
|
|
arrowStyle.borderRightWidth = 0;
|
|
|
|
break;
|
|
|
|
case 'up':
|
|
|
|
arrowStyle.bottom = arrowCoord;
|
|
|
|
arrowStyle.borderTopColor = bgColor;
|
|
|
|
arrowStyle.borderBottomWidth = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
arrowStyle.top = arrowCoord;
|
|
|
|
arrowStyle.borderBottomColor = bgColor;
|
|
|
|
arrowStyle.borderTopWidth = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.element.appendChild(arrow);
|
|
|
|
this.arrow = arrow;
|
|
|
|
}
|
|
|
|
|
2018-05-25 15:25:35 +00:00
|
|
|
$onDestroy() {
|
2018-05-22 09:44:24 +00:00
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Tooltip.$inject = ['$element', '$scope', '$timeout'];
|
|
|
|
|
2020-07-24 15:46:06 +00:00
|
|
|
ngModule.vnComponent('vnTooltip', {
|
2018-05-22 09:44:24 +00:00
|
|
|
template: require('./tooltip.html'),
|
|
|
|
controller: Tooltip,
|
|
|
|
transclude: true,
|
|
|
|
bindings: {
|
|
|
|
position: '@?'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-03-09 08:00:03 +00:00
|
|
|
directive.$inject = ['$document', '$compile'];
|
|
|
|
export function directive($document, $compile) {
|
2018-05-22 09:44:24 +00:00
|
|
|
return {
|
|
|
|
restrict: 'A',
|
|
|
|
link: function($scope, $element, $attrs) {
|
|
|
|
let tooltip;
|
|
|
|
let $tooltip;
|
|
|
|
let $tooltipScope;
|
|
|
|
|
|
|
|
if ($attrs.tooltipId) {
|
|
|
|
let tooltipKey = kebabToCamel($attrs.tooltipId);
|
|
|
|
tooltip = $scope[tooltipKey];
|
|
|
|
if (!(tooltip instanceof Tooltip))
|
|
|
|
throw new Error(`vnTooltip id should reference a tooltip instance`);
|
|
|
|
} else {
|
|
|
|
$tooltipScope = $scope.$new();
|
|
|
|
$tooltipScope.text = $attrs.vnTooltip;
|
|
|
|
let template = `<vn-tooltip class="text"><span translate>{{::text}}</span></vn-tooltip>`;
|
|
|
|
$tooltip = $compile(template)($tooltipScope);
|
|
|
|
$document.find('body').append($tooltip);
|
|
|
|
tooltip = $tooltip[0].$ctrl;
|
2018-02-28 08:12:48 +00:00
|
|
|
}
|
2018-05-22 09:44:24 +00:00
|
|
|
|
|
|
|
if ($attrs.tooltipPosition)
|
|
|
|
tooltip.position = $attrs.tooltipPosition;
|
|
|
|
|
|
|
|
$element[0].title = '';
|
|
|
|
$element.on('mouseover', function(event) {
|
|
|
|
if (event.defaultPrevented) return;
|
|
|
|
tooltip.show($element[0]);
|
|
|
|
});
|
|
|
|
$element.on('mouseout', function() {
|
|
|
|
tooltip.hide();
|
|
|
|
});
|
|
|
|
$element.on('$destroy', function() {
|
|
|
|
tooltip.hide();
|
|
|
|
|
|
|
|
if ($tooltip) {
|
|
|
|
$tooltip.remove();
|
|
|
|
$tooltipScope.$destroy();
|
|
|
|
}
|
|
|
|
});
|
2017-06-08 07:29:03 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2018-05-22 09:44:24 +00:00
|
|
|
ngModule.directive('vnTooltip', directive);
|