salix/front/core/components/tooltip/tooltip.js

256 lines
7.0 KiB
JavaScript
Raw Normal View History

2018-02-10 15:18:01 +00:00
import ngModule from '../../module';
import Component from '../../lib/component';
import {kebabToCamel} from '../../lib/string';
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');
this.position = 'down';
2018-10-15 10:46:56 +00:00
this.margin = 10;
}
/**
* Shows the tooltip.
*
* @param {HTMLElement} parent The parent element
*/
show(parent) {
this.parent = parent;
this.$element.addClass('show');
this.relocate();
this.cancelTimeout();
this.relocateTimeout = this.$timeout(() => this.relocate(), 50);
}
/**
* Hides the tooltip.
*/
hide() {
this.$element.removeClass('show');
this.cancelTimeout();
}
cancelTimeout() {
if (this.relocateTimeout) {
this.$timeout.cancel(this.relocateTimeout);
this.relocateTimeout = null;
}
}
/**
2020-03-31 07:51:10 +00:00
* Repositions the tooltip acording to it's own size, position and parent location.
*/
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;
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;
// 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
}
}
calcCoords();
// Overflow
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-08 07:29:03 +00:00
if (axisOverflow) {
position = switchPosition(position);
calcCoords();
}
2017-06-08 07:29:03 +00:00
function range(coord, min, max) {
return Math.min(Math.max(coord, min), max);
}
if (axis == 'x')
top = range(top, min, maxTop);
else
left = range(left, min, maxLeft);
let style = this.element.style;
style.top = `${top}px`;
style.left = `${left}px`;
2018-02-28 08:12:48 +00:00
// Arrow position
2018-02-28 08:12:48 +00:00
if (this.arrow)
this.element.removeChild(this.arrow);
2018-02-28 08:12:48 +00:00
let arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.style.borderWidth = `${arrowSize}px`;
2018-02-28 08:12:48 +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;
}
$onDestroy() {
this.hide();
}
}
Tooltip.$inject = ['$element', '$scope', '$timeout'];
2020-07-24 15:46:06 +00:00
ngModule.vnComponent('vnTooltip', {
template: require('./tooltip.html'),
controller: Tooltip,
transclude: true,
bindings: {
position: '@?'
}
});
directive.$inject = ['$document', '$compile'];
export function directive($document, $compile) {
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
}
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
}
};
}
ngModule.directive('vnTooltip', directive);