framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
336 lines (331 loc) • 11.4 kB
JavaScript
import { getDocument } from 'ssr-window';
import $ from '../../shared/dom7.js';
import { extend, deleteProps } from '../../shared/utils.js';
import { getSupport } from '../../shared/get-support.js';
import Framework7Class from '../../shared/class.js';
class Tooltip extends Framework7Class {
constructor(app, params) {
if (params === void 0) {
params = {};
}
super(params, [app]);
const tooltip = this;
const support = getSupport();
const defaults = extend({}, app.params.tooltip);
const document = getDocument();
// Extend defaults with modules params
tooltip.useModulesParams(defaults);
tooltip.params = extend(defaults, params);
if (typeof params.offset === 'undefined' && support.touch && tooltip.params.trigger === 'hover') {
tooltip.params.offset = 10;
}
const {
targetEl,
containerEl
} = tooltip.params;
if (!targetEl && !tooltip.params.delegated) return tooltip;
const $targetEl = $(targetEl);
if ($targetEl.length === 0 && !tooltip.params.delegated) return tooltip;
if ($targetEl[0] && $targetEl[0].f7Tooltip && !tooltip.params.delegated) return $targetEl[0].f7Tooltip;
let $containerEl = $(containerEl || app.$el).eq(0);
if ($containerEl.length === 0) {
$containerEl = app.$el;
}
const $el = $(tooltip.render()).eq(0);
extend(tooltip, {
app,
$targetEl,
targetEl: $targetEl && $targetEl[0],
$containerEl,
containerEl: $containerEl && $containerEl[0],
$el,
el: $el && $el[0],
text: tooltip.params.text || '',
visible: false,
opened: false
});
if ($targetEl[0]) $targetEl[0].f7Tooltip = tooltip;
const touchesStart = {};
let isTouched;
function handleClick() {
if (tooltip.opened) tooltip.hide();else tooltip.show(this);
}
function handleClickOut(e) {
if (tooltip.opened && ($(e.target).closest($targetEl).length || $(e.target).closest(tooltip.$el).length)) return;
tooltip.hide();
}
function handleTouchStart(e) {
if (isTouched) return;
isTouched = true;
touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
tooltip.show(this);
}
function handleTouchMove(e) {
if (!isTouched) return;
const x = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
const y = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
const distance = ((x - touchesStart.x) ** 2 + (y - touchesStart.y) ** 2) ** 0.5;
if (distance > 50) {
isTouched = false;
tooltip.hide();
}
}
function handleTouchEnd() {
if (!isTouched) return;
isTouched = false;
tooltip.hide();
}
function handleMouseEnter() {
tooltip.show(this);
}
function handleMouseLeave() {
tooltip.hide();
}
function handleTransitionEnd() {
if (!$el.hasClass('tooltip-in')) {
$el.removeClass('tooltip-out').remove();
}
}
tooltip.attachEvents = function attachEvents() {
$el.on('transitionend', handleTransitionEnd);
if (tooltip.params.trigger === 'click') {
if (tooltip.params.delegated) {
$(document).on('click', tooltip.params.targetEl, handleClick);
} else {
tooltip.$targetEl.on('click', handleClick);
}
$('html').on('click', handleClickOut);
return;
}
if (tooltip.params.trigger === 'manual') return;
if (support.touch) {
const passive = support.passiveListener ? {
passive: true
} : false;
if (tooltip.params.delegated) {
$(document).on(app.touchEvents.start, tooltip.params.targetEl, handleTouchStart, passive);
} else {
tooltip.$targetEl.on(app.touchEvents.start, handleTouchStart, passive);
}
app.on('touchmove', handleTouchMove);
app.on('touchend:passive', handleTouchEnd);
} else {
// eslint-disable-next-line
if (tooltip.params.delegated) {
$(document).on(support.pointerEvents ? 'pointerenter' : 'mouseenter', tooltip.params.targetEl, handleMouseEnter, true);
$(document).on(support.pointerEvents ? 'pointerleave' : 'mouseleave', tooltip.params.targetEl, handleMouseLeave, true);
} else {
tooltip.$targetEl.on(support.pointerEvents ? 'pointerenter' : 'mouseenter', handleMouseEnter);
tooltip.$targetEl.on(support.pointerEvents ? 'pointerleave' : 'mouseleave', handleMouseLeave);
}
}
};
tooltip.detachEvents = function detachEvents() {
$el.off('transitionend', handleTransitionEnd);
if (tooltip.params.trigger === 'click') {
if (tooltip.params.delegated) {
$(document).on('click', tooltip.params.targetEl, handleClick);
} else {
tooltip.$targetEl.off('click', handleClick);
}
$('html').off('click', handleClickOut);
return;
}
if (tooltip.params.trigger === 'manual') return;
if (support.touch) {
const passive = support.passiveListener ? {
passive: true
} : false;
if (tooltip.params.delegated) {
$(document).off(app.touchEvents.start, tooltip.params.targetEl, handleTouchStart, passive);
} else {
tooltip.$targetEl.off(app.touchEvents.start, handleTouchStart, passive);
}
app.off('touchmove', handleTouchMove);
app.off('touchend:passive', handleTouchEnd);
} else {
// eslint-disable-next-line
if (tooltip.params.delegated) {
$(document).off(support.pointerEvents ? 'pointerenter' : 'mouseenter', tooltip.params.targetEl, handleMouseEnter, true);
$(document).off(support.pointerEvents ? 'pointerleave' : 'mouseleave', tooltip.params.targetEl, handleMouseLeave, true);
} else {
tooltip.$targetEl.off(support.pointerEvents ? 'pointerenter' : 'mouseenter', handleMouseEnter);
tooltip.$targetEl.off(support.pointerEvents ? 'pointerleave' : 'mouseleave', handleMouseLeave);
}
}
};
// Install Modules
tooltip.useModules();
tooltip.init();
return tooltip;
}
setTargetEl(targetEl) {
const tooltip = this;
tooltip.detachEvents();
tooltip.$targetEl = $(targetEl);
tooltip.targetEl = tooltip.$targetEl[0];
tooltip.attachEvents();
return tooltip;
}
position(targetEl) {
const tooltip = this;
const {
$el,
app,
$containerEl
} = tooltip;
const hasContainerEl = !!tooltip.params.containerEl;
const tooltipOffset = tooltip.params.offset || 0;
$el.css({
left: '',
top: ''
});
const $targetEl = $(targetEl || tooltip.targetEl);
const [width, height] = [$el.width(), $el.height()];
$el.css({
left: '',
top: ''
});
let targetWidth;
let targetHeight;
let targetOffsetLeft;
let targetOffsetTop;
const boundaries = hasContainerEl && $containerEl.length ? $containerEl[0].getBoundingClientRect() : app;
if ($targetEl && $targetEl.length > 0) {
targetWidth = $targetEl.outerWidth();
targetHeight = $targetEl.outerHeight();
if (typeof targetWidth === 'undefined' && typeof targetHeight === 'undefined') {
const clientRect = $targetEl[0].getBoundingClientRect();
targetWidth = clientRect.width;
targetHeight = clientRect.height;
}
const targetOffset = $targetEl.offset();
targetOffsetLeft = targetOffset.left - boundaries.left;
targetOffsetTop = targetOffset.top - boundaries.top;
const targetParentPage = $targetEl.parents('.page');
if (targetParentPage.length > 0) {
targetOffsetTop -= targetParentPage[0].scrollTop;
}
}
let [left, top] = [0, 0, 0];
// Top Position
let position = 'top';
if (height + tooltipOffset < targetOffsetTop) {
// On top
top = targetOffsetTop - height - tooltipOffset;
} else if (height < boundaries.height - targetOffsetTop - targetHeight) {
// On bottom
position = 'bottom';
top = targetOffsetTop + targetHeight + tooltipOffset;
} else {
// On middle
position = 'middle';
top = targetHeight / 2 + targetOffsetTop - height / 2;
if (top <= 0) {
top = 8;
} else if (top + height >= boundaries.height) {
top = boundaries.height - height - 8;
}
}
// Horizontal Position
if (position === 'top' || position === 'bottom') {
left = targetWidth / 2 + targetOffsetLeft - width / 2;
if (left < 8) left = 8;
if (left + width > boundaries.width) left = boundaries.width - width - 8;
if (left < 0) left = 0;
} else if (position === 'middle') {
left = targetOffsetLeft - width;
if (left < 8 || left + width > boundaries.width) {
if (left < 8) left = targetOffsetLeft + targetWidth;
if (left + width > boundaries.width) left = boundaries.width - width - 8;
}
}
// Apply Styles
$el.css({
top: `${top}px`,
left: `${left}px`
});
}
show(aroundEl) {
const tooltip = this;
const {
$el,
$targetEl,
$containerEl
} = tooltip;
if ($containerEl[0] && $el[0] && !$containerEl[0].contains($el[0])) {
$containerEl.append($el);
}
tooltip.position(aroundEl);
const $aroundEl = $(aroundEl);
tooltip.visible = true;
tooltip.opened = true;
$targetEl.trigger('tooltip:show');
$el.trigger('tooltip:show');
if ($aroundEl.length && $aroundEl[0] !== $targetEl[0]) {
$aroundEl.trigger('tooltip:show');
}
tooltip.emit('local::show tooltipShow', tooltip);
$el.removeClass('tooltip-out').addClass('tooltip-in');
return tooltip;
}
hide() {
const tooltip = this;
const {
$el,
$targetEl
} = tooltip;
tooltip.visible = false;
tooltip.opened = false;
$targetEl.trigger('tooltip:hide');
$el.trigger('tooltip:hide');
tooltip.emit('local::hide tooltipHide', tooltip);
$el.addClass('tooltip-out').removeClass('tooltip-in');
return tooltip;
}
render() {
const tooltip = this;
if (tooltip.params.render) return tooltip.params.render.call(tooltip, tooltip);
const {
cssClass,
text
} = tooltip.params;
return `
<div class="tooltip ${cssClass || ''}">
<div class="tooltip-content">${text || ''}</div>
</div>
`.trim();
}
setText(newText) {
const tooltip = this;
if (typeof newText === 'undefined') {
return tooltip;
}
tooltip.params.text = newText;
tooltip.text = newText;
if (tooltip.$el) {
tooltip.$el.children('.tooltip-content').html(newText);
}
if (tooltip.opened) {
tooltip.position();
}
return tooltip;
}
init() {
const tooltip = this;
tooltip.attachEvents();
}
destroy() {
const tooltip = this;
if (!tooltip.$targetEl || tooltip.destroyed) return;
tooltip.$targetEl.trigger('tooltip:beforedestroy');
tooltip.emit('local::beforeDestroy tooltipBeforeDestroy', tooltip);
tooltip.$el.remove();
if (tooltip.$targetEl[0]) delete tooltip.$targetEl[0].f7Tooltip;
tooltip.detachEvents();
deleteProps(tooltip);
tooltip.destroyed = true;
}
}
export default Tooltip;