framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
293 lines (284 loc) • 10.4 kB
JavaScript
import { getWindow, getDocument } from 'ssr-window';
import $ from '../../shared/dom7.js';
import { extend } from '../../shared/utils.js';
import { getDevice } from '../../shared/get-device.js';
import Modal from '../modal/modal-class.js';
class Popover extends Modal {
constructor(app, params) {
const extendedParams = extend({
on: {}
}, app.params.popover, params);
// Extends with open/close Modal methods;
super(app, extendedParams);
const popover = this;
const device = getDevice();
const window = getWindow();
const document = getDocument();
popover.params = extendedParams;
// Find Element
let $el;
if (!popover.params.el) {
$el = $(popover.params.content).filter(node => node.nodeType === 1).eq(0);
} else {
$el = $(popover.params.el).eq(0);
}
if ($el && $el.length > 0 && $el[0].f7Modal) {
return $el[0].f7Modal;
}
// Find Target
const $targetEl = $(popover.params.targetEl).eq(0);
if ($el.length === 0) {
return popover.destroy();
}
// Backdrop
let $backdropEl;
const forceBackdropUnique = popover.params.backdrop && app.$el.find('.popover.modal-in').filter(anotherPopoverEl => anotherPopoverEl !== $el[0]).length > 0;
if (popover.params.backdrop && popover.params.backdropEl) {
$backdropEl = $(popover.params.backdropEl);
} else if (popover.params.backdrop) {
if (popover.params.backdropUnique || forceBackdropUnique) {
$backdropEl = $('<div class="popover-backdrop popover-backdrop-unique"></div>');
$backdropEl[0].f7PopoverRef = popover;
popover.$containerEl.append($backdropEl);
} else {
$backdropEl = popover.$containerEl.children('.popover-backdrop');
}
if ($backdropEl.length === 0) {
$backdropEl = $('<div class="popover-backdrop"></div>');
popover.$containerEl.append($backdropEl);
}
}
// Find Arrow
let $arrowEl;
if ($el.find('.popover-arrow').length === 0 && popover.params.arrow) {
$arrowEl = $('<div class="popover-arrow"></div>');
$el.prepend($arrowEl);
} else {
$arrowEl = $el.find('.popover-arrow');
}
// Open
const originalOpen = popover.open;
extend(popover, {
app,
$el,
el: $el[0],
$targetEl,
targetEl: $targetEl[0],
$arrowEl,
arrowEl: $arrowEl[0],
$backdropEl,
backdropEl: $backdropEl && $backdropEl[0],
type: 'popover',
forceBackdropUnique,
open() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
let [targetEl, animate] = args;
if (typeof args[0] === 'boolean') [animate, targetEl] = args;
if (targetEl) {
popover.$targetEl = $(targetEl);
popover.targetEl = popover.$targetEl[0];
}
return originalOpen.call(popover, animate);
}
});
function handleResize() {
popover.resize();
}
popover.on('popoverOpen', () => {
popover.resize();
app.on('resize', handleResize);
$(window).on('keyboardDidShow keyboardDidHide', handleResize);
popover.on('popoverClose popoverBeforeDestroy', () => {
app.off('resize', handleResize);
$(window).off('keyboardDidShow keyboardDidHide', handleResize);
});
});
let touchStartTarget = null;
function handleTouchStart(e) {
touchStartTarget = e.target;
}
function handleClick(e) {
const target = e.target;
const $target = $(target);
const keyboardOpened = !device.desktop && device.cordova && (window.Keyboard && window.Keyboard.isVisible || window.cordova.plugins && window.cordova.plugins.Keyboard && window.cordova.plugins.Keyboard.isVisible);
if (keyboardOpened) return;
if ($target.closest(popover.el).length === 0) {
if (popover.params.closeByBackdropClick && popover.params.backdrop && popover.backdropEl && popover.backdropEl === target && touchStartTarget === target) {
popover.close();
} else if (popover.params.closeByOutsideClick && touchStartTarget === target) {
const isAnotherPopoverBackdrop = $target.hasClass('popover-backdrop-unique') && target.f7PopoverRef !== popover || $target.hasClass('popover-backdrop') && target !== popover.backdropEl;
const isAnotherPopoverTarget = target.closest('.popover') && target.closest('.popover') !== popover.$el[0];
if (!isAnotherPopoverBackdrop && !isAnotherPopoverTarget) {
popover.close();
}
}
}
}
function onKeyDown(e) {
const keyCode = e.keyCode;
if (keyCode === 27 && popover.params.closeOnEscape) {
popover.close();
}
}
if (popover.params.closeOnEscape) {
popover.on('popoverOpen', () => {
$(document).on('keydown', onKeyDown);
});
popover.on('popoverClose', () => {
$(document).off('keydown', onKeyDown);
});
}
popover.on('popoverOpened', () => {
if (popover.params.closeByOutsideClick || popover.params.closeByBackdropClick) {
app.on('touchstart', handleTouchStart);
app.on('click', handleClick);
}
});
popover.on('popoverClose', () => {
if (popover.params.closeByOutsideClick || popover.params.closeByBackdropClick) {
app.off('touchstart', handleTouchStart);
app.off('click', handleClick);
}
});
$el[0].f7Modal = popover;
return popover;
}
resize() {
const popover = this;
const {
app,
$el,
$targetEl,
$arrowEl
} = popover;
const {
targetX,
targetY,
verticalPosition
} = popover.params;
$el.css({
left: '',
top: ''
});
const [width, height] = [$el.width(), $el.height()];
let arrowSize = 0;
let arrowLeft;
let arrowTop;
const hasArrow = $arrowEl.length > 0;
const arrowMin = app.theme === 'ios' ? 13 : 24;
if (hasArrow) {
$arrowEl.removeClass('on-left on-right on-top on-bottom').css({
left: '',
top: ''
});
arrowSize = $arrowEl.width() / 2;
}
$el.removeClass('popover-on-left popover-on-right popover-on-top popover-on-bottom popover-on-middle').css({
left: '',
top: ''
});
let targetWidth;
let targetHeight;
let targetOffsetLeft;
let targetOffsetTop;
let safeAreaTop = parseInt($('html').css('--f7-safe-area-top'), 10);
let safeAreaLeft = parseInt($('html').css('--f7-safe-area-left'), 10);
let safeAreaRight = parseInt($('html').css('--f7-safe-area-right'), 10);
if (Number.isNaN(safeAreaTop)) safeAreaTop = 0;
if (Number.isNaN(safeAreaLeft)) safeAreaLeft = 0;
if (Number.isNaN(safeAreaRight)) safeAreaRight = 0;
if ($targetEl && $targetEl.length > 0) {
targetWidth = $targetEl.outerWidth();
targetHeight = $targetEl.outerHeight();
const targetOffset = $targetEl.offset();
targetOffsetLeft = targetOffset.left - app.left;
targetOffsetTop = targetOffset.top - app.top;
const targetParentPage = $targetEl.parents('.page');
if (targetParentPage.length > 0) {
targetOffsetTop -= targetParentPage[0].scrollTop;
}
} else if (typeof targetX !== 'undefined' && targetY !== 'undefined') {
targetOffsetLeft = targetX;
targetOffsetTop = targetY;
targetWidth = popover.params.targetWidth || 0;
targetHeight = popover.params.targetHeight || 0;
}
let [left, top, diff] = [0, 0, 0];
// Top Position
const forcedPosition = verticalPosition === 'auto' ? false : verticalPosition;
let position = forcedPosition || 'top';
if (forcedPosition === 'top' || !forcedPosition && height + arrowSize < targetOffsetTop - safeAreaTop) {
// On top
top = targetOffsetTop - height - arrowSize;
} else if (forcedPosition === 'bottom' || !forcedPosition && height + arrowSize < app.height - targetOffsetTop - targetHeight) {
// On bottom
position = 'bottom';
top = targetOffsetTop + targetHeight + arrowSize;
} else {
// On middle
position = 'middle';
top = targetHeight / 2 + targetOffsetTop - height / 2;
diff = top;
top = Math.max(5, Math.min(top, app.height - height - 5));
diff -= top;
}
// Horizontal Position
if (position === 'top' || position === 'bottom') {
left = targetWidth / 2 + targetOffsetLeft - width / 2;
diff = left;
left = Math.max(5, Math.min(left, app.width - width - 5));
if (safeAreaLeft) {
left = Math.max(left, safeAreaLeft);
}
if (safeAreaRight && left + width > app.width - 5 - safeAreaRight) {
left = app.width - 5 - safeAreaRight - width;
}
diff -= left;
if (hasArrow) {
if (position === 'top') {
$arrowEl.addClass('on-bottom');
}
if (position === 'bottom') {
$arrowEl.addClass('on-top');
}
arrowLeft = width / 2 - arrowSize + diff;
arrowLeft = Math.max(Math.min(arrowLeft, width - arrowSize * 2 - arrowMin), arrowMin);
$arrowEl.css({
left: `${arrowLeft}px`
});
}
} else if (position === 'middle') {
left = targetOffsetLeft - width - arrowSize;
if (hasArrow) $arrowEl.addClass('on-right');
if (left < 5 || left + width + safeAreaRight > app.width || left < safeAreaLeft) {
if (left < 5) left = targetOffsetLeft + targetWidth + arrowSize;
if (left + width + safeAreaRight > app.width) left = app.width - width - 5 - safeAreaRight;
if (left < safeAreaLeft) left = safeAreaLeft;
if (hasArrow) $arrowEl.removeClass('on-right').addClass('on-left');
}
if (hasArrow) {
arrowTop = height / 2 - arrowSize + diff;
arrowTop = Math.max(Math.min(arrowTop, height - arrowSize * 2 - arrowMin), arrowMin);
$arrowEl.css({
top: `${arrowTop}px`
});
}
}
// Horizontal Position
let hPosition;
if (targetOffsetLeft < app.width / 2) {
hPosition = 'right';
} else {
hPosition = 'left';
}
$el.addClass(`popover-on-${position} popover-on-${hPosition}`);
// Apply Styles
$el.css({
top: `${top}px`,
left: `${left}px`
});
}
}
export default Popover;