UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

336 lines (331 loc) 11.4 kB
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;