keeweb/app/scripts/util/ui/tip.js

211 lines
5.8 KiB
JavaScript
Raw Normal View History

2019-09-16 22:57:56 +02:00
import { Events } from 'framework/events';
2019-09-15 14:16:32 +02:00
import { Features } from 'util/features';
2019-09-18 07:12:06 +02:00
import { pick } from 'util/fn';
2016-01-13 19:21:48 +01:00
2020-06-01 16:53:51 +02:00
const Tip = function (el, config) {
2016-01-10 21:58:21 +01:00
this.el = el;
2019-08-16 23:05:39 +02:00
this.title = (config && config.title) || el.attr('title');
this.placement = (config && config.placement) || el.attr('tip-placement');
this.fast = (config && config.fast) || false;
2016-01-10 21:58:21 +01:00
this.tipEl = null;
this.showTimeout = null;
this.hideTimeout = null;
2019-08-16 23:05:39 +02:00
this.force = (config && config.force) || false;
2016-02-04 20:48:25 +01:00
this.hide = this.hide.bind(this);
2019-09-15 18:33:45 +02:00
this.destroy = this.destroy.bind(this);
this.mouseenter = this.mouseenter.bind(this);
this.mouseleave = this.mouseleave.bind(this);
2016-01-10 21:58:21 +01:00
};
2019-09-15 08:11:11 +02:00
Tip.enabled = !Features.isMobile;
2016-01-13 19:21:48 +01:00
2020-06-01 16:53:51 +02:00
Tip.prototype.init = function () {
2016-01-13 19:21:48 +01:00
if (!Tip.enabled) {
return;
}
2016-01-10 21:58:21 +01:00
this.el.removeAttr('title');
2016-02-28 11:36:56 +01:00
this.el.attr('data-title', this.title);
2019-09-15 18:33:45 +02:00
this.el.mouseenter(this.mouseenter).mouseleave(this.mouseleave);
this.el.click(this.mouseleave);
2016-01-10 21:58:21 +01:00
};
2020-06-01 16:53:51 +02:00
Tip.prototype.show = function () {
2019-08-16 23:05:39 +02:00
if ((!Tip.enabled && !this.force) || !this.title) {
2016-01-13 19:21:48 +01:00
return;
}
2019-09-16 22:57:56 +02:00
Events.on('page-geometry', this.hide);
2016-01-11 18:56:52 +01:00
if (this.tipEl) {
this.tipEl.remove();
if (this.hideTimeout) {
clearTimeout(this.hideTimeout);
this.hideTimeout = null;
}
}
2020-06-01 16:53:51 +02:00
const tipEl = (this.tipEl = $('<div></div>').addClass('tip').appendTo('body').text(this.title));
2017-01-31 07:50:28 +01:00
const rect = this.el[0].getBoundingClientRect();
const tipRect = this.tipEl[0].getBoundingClientRect();
const placement = this.placement || this.getAutoPlacement(rect, tipRect);
2016-01-11 18:56:52 +01:00
tipEl.addClass('tip--' + placement);
2016-01-12 22:08:24 +01:00
if (this.fast) {
tipEl.addClass('tip--fast');
}
2019-08-16 23:05:39 +02:00
let top, left;
2017-01-31 07:50:28 +01:00
const offset = 10;
const sideOffset = 10;
2016-01-11 18:56:52 +01:00
switch (placement) {
case 'top':
top = rect.top - tipRect.height - offset;
left = rect.left + rect.width / 2 - tipRect.width / 2;
break;
2016-02-04 20:38:57 +01:00
case 'top-left':
top = rect.top - tipRect.height - offset;
left = rect.left + rect.width / 2 - tipRect.width + sideOffset;
break;
2016-01-11 18:56:52 +01:00
case 'bottom':
top = rect.bottom + offset;
left = rect.left + rect.width / 2 - tipRect.width / 2;
break;
case 'left':
top = rect.top + rect.height / 2 - tipRect.height / 2;
left = rect.left - tipRect.width - offset;
break;
case 'right':
top = rect.top + rect.height / 2 - tipRect.height / 2;
left = rect.right + offset;
break;
}
2019-08-18 10:17:09 +02:00
tipEl.css({ top, left });
2016-01-11 18:56:52 +01:00
};
2020-06-01 16:53:51 +02:00
Tip.prototype.hide = function () {
2016-01-11 18:56:52 +01:00
if (this.tipEl) {
this.tipEl.remove();
this.tipEl = null;
2019-09-16 22:57:56 +02:00
Events.off('page-geometry', this.hide);
2016-01-11 18:56:52 +01:00
}
2019-09-15 18:33:45 +02:00
};
2020-06-01 16:53:51 +02:00
Tip.prototype.destroy = function () {
2019-09-15 18:33:45 +02:00
this.hide();
2020-05-31 23:31:14 +02:00
2019-09-15 18:33:45 +02:00
this.el.off('mouseenter', this.mouseenter);
this.el.off('mouseleave', this.mouseleave);
this.el.off('click', this.mouseleave);
2020-05-31 23:31:14 +02:00
if (this.showTimeout) {
clearTimeout(this.showTimeout);
this.showTimeout = null;
}
if (this.hideTimeout) {
clearTimeout(this.hideTimeout);
this.hideTimeout = null;
}
2016-01-11 18:56:52 +01:00
};
2020-06-01 16:53:51 +02:00
Tip.prototype.mouseenter = function () {
2016-01-10 21:58:21 +01:00
if (this.showTimeout) {
return;
}
2016-07-17 13:30:38 +02:00
this.showTimeout = setTimeout(() => {
this.showTimeout = null;
this.show();
2016-01-10 21:58:21 +01:00
}, 200);
};
2020-06-01 16:53:51 +02:00
Tip.prototype.mouseleave = function () {
2016-01-10 21:58:21 +01:00
if (this.tipEl) {
2016-07-17 13:30:38 +02:00
this.tipEl.addClass('tip--hide');
this.hideTimeout = setTimeout(() => {
this.hideTimeout = null;
this.hide();
2016-01-10 21:58:21 +01:00
}, 500);
}
if (this.showTimeout) {
clearTimeout(this.showTimeout);
this.showTimeout = null;
}
};
2020-06-01 16:53:51 +02:00
Tip.prototype.getAutoPlacement = function (rect, tipRect) {
2017-01-31 07:50:28 +01:00
const padding = 20;
const bodyRect = document.body.getBoundingClientRect();
const canShowToBottom = bodyRect.bottom - rect.bottom > padding + tipRect.height;
const canShowToHalfRight = bodyRect.right - rect.right > padding + tipRect.width / 2;
const canShowToRight = bodyRect.right - rect.right > padding + tipRect.width;
const canShowToHalfLeft = rect.left > padding + tipRect.width / 2;
const canShowToLeft = rect.left > padding + tipRect.width;
2016-01-10 21:58:21 +01:00
if (canShowToBottom) {
if (canShowToLeft && !canShowToHalfRight) {
return 'left';
} else if (canShowToRight && !canShowToHalfLeft) {
return 'right';
} else {
return 'bottom';
}
}
if (canShowToLeft && !canShowToHalfRight) {
return 'left';
} else if (canShowToRight && !canShowToHalfLeft) {
return 'right';
} else {
return 'top';
}
};
2020-06-01 16:53:51 +02:00
Tip.createTips = function (container) {
2016-01-13 19:21:48 +01:00
if (!Tip.enabled) {
return;
}
2019-09-15 18:33:45 +02:00
$('[title]', container).each((ix, el) => {
2016-06-04 14:47:56 +02:00
Tip.createTip(el);
2016-02-28 11:36:56 +01:00
});
};
2020-06-01 16:53:51 +02:00
Tip.createTip = function (el, options) {
2017-04-16 11:53:14 +02:00
if (!Tip.enabled && (!options || !options.force)) {
2016-06-04 14:47:56 +02:00
return;
}
2017-04-16 10:19:46 +02:00
const tip = new Tip($(el), options);
if (!options || !options.noInit) {
tip.init();
}
2016-06-04 14:47:56 +02:00
el._tip = tip;
2017-04-16 10:19:46 +02:00
return tip;
2016-06-04 14:47:56 +02:00
};
2020-06-01 16:53:51 +02:00
Tip.hideTips = function (container) {
2019-09-15 18:33:45 +02:00
if (!Tip.enabled || !container) {
2016-02-28 11:36:56 +01:00
return;
}
2019-09-15 18:33:45 +02:00
$('[data-title]', container).each((ix, el) => {
2016-06-04 14:47:56 +02:00
Tip.hideTip(el);
2016-01-10 21:58:21 +01:00
});
};
2020-06-01 16:53:51 +02:00
Tip.hideTip = function (el) {
2016-06-04 14:47:56 +02:00
if (el._tip) {
el._tip.hide();
}
};
2020-06-01 16:53:51 +02:00
Tip.updateTip = function (el, props) {
if (el._tip) {
el._tip.hide();
2019-09-18 07:12:06 +02:00
Object.assign(
el._tip,
pick(props, ['title', 'placement', 'fast', 'showTimeout', 'hideTimeout'])
);
}
};
2020-06-01 16:53:51 +02:00
Tip.destroyTips = function (container) {
2019-09-15 18:33:45 +02:00
$('[data-title]', container).each((ix, el) => {
if (el._tip) {
el._tip.destroy();
el._tip = undefined;
}
});
};
2019-09-15 14:16:32 +02:00
export { Tip };