You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
9.2 KiB
271 lines
9.2 KiB
4 years ago
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||
|
|
||
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||
|
|
||
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||
|
|
||
|
import { NAME_POPOVER } from '../../constants/components';
|
||
|
import getScopId from '../../utils/get-scope-id';
|
||
|
import identity from '../../utils/identity';
|
||
|
import looseEqual from '../../utils/loose-equal';
|
||
|
import { concat } from '../../utils/array';
|
||
|
import { getComponentConfig } from '../../utils/config';
|
||
|
import { isBrowser } from '../../utils/env';
|
||
|
import { isFunction, isNumber, isPlainObject, isString, isUndefined, isUndefinedOrNull } from '../../utils/inspect';
|
||
|
import { toInteger } from '../../utils/number';
|
||
|
import { keys } from '../../utils/object';
|
||
|
import { BVPopover } from '../../components/popover/helpers/bv-popover'; // Key which we use to store tooltip object on element
|
||
|
|
||
|
var BV_POPOVER = '__BV_Popover__'; // Default trigger
|
||
|
|
||
|
var DefaultTrigger = 'click'; // Valid event triggers
|
||
|
|
||
|
var validTriggers = {
|
||
|
focus: true,
|
||
|
hover: true,
|
||
|
click: true,
|
||
|
blur: true,
|
||
|
manual: true
|
||
|
}; // Directive modifier test regular expressions. Pre-compile for performance
|
||
|
|
||
|
var htmlRE = /^html$/i;
|
||
|
var noFadeRE = /^nofade$/i;
|
||
|
var placementRE = /^(auto|top(left|right)?|bottom(left|right)?|left(top|bottom)?|right(top|bottom)?)$/i;
|
||
|
var boundaryRE = /^(window|viewport|scrollParent)$/i;
|
||
|
var delayRE = /^d\d+$/i;
|
||
|
var delayShowRE = /^ds\d+$/i;
|
||
|
var delayHideRE = /^dh\d+$/i;
|
||
|
var offsetRE = /^o-?\d+$/i;
|
||
|
var variantRE = /^v-.+$/i;
|
||
|
var spacesRE = /\s+/; // Build a Popover config based on bindings (if any)
|
||
|
// Arguments and modifiers take precedence over passed value config object
|
||
|
|
||
|
var parseBindings = function parseBindings(bindings, vnode)
|
||
|
/* istanbul ignore next: not easy to test */
|
||
|
{
|
||
|
// We start out with a basic config
|
||
|
var config = {
|
||
|
title: undefined,
|
||
|
content: undefined,
|
||
|
trigger: '',
|
||
|
// Default set below if needed
|
||
|
placement: 'right',
|
||
|
fallbackPlacement: 'flip',
|
||
|
container: false,
|
||
|
// Default of body
|
||
|
animation: true,
|
||
|
offset: 0,
|
||
|
disabled: false,
|
||
|
id: null,
|
||
|
html: false,
|
||
|
delay: getComponentConfig(NAME_POPOVER, 'delay', 50),
|
||
|
boundary: String(getComponentConfig(NAME_POPOVER, 'boundary', 'scrollParent')),
|
||
|
boundaryPadding: toInteger(getComponentConfig(NAME_POPOVER, 'boundaryPadding', 5), 0),
|
||
|
variant: getComponentConfig(NAME_POPOVER, 'variant'),
|
||
|
customClass: getComponentConfig(NAME_POPOVER, 'customClass')
|
||
|
}; // Process `bindings.value`
|
||
|
|
||
|
if (isString(bindings.value) || isNumber(bindings.value)) {
|
||
|
// Value is popover content (html optionally supported)
|
||
|
config.content = bindings.value;
|
||
|
} else if (isFunction(bindings.value)) {
|
||
|
// Content generator function
|
||
|
config.content = bindings.value;
|
||
|
} else if (isPlainObject(bindings.value)) {
|
||
|
// Value is config object, so merge
|
||
|
config = _objectSpread(_objectSpread({}, config), bindings.value);
|
||
|
} // If argument, assume element ID of container element
|
||
|
|
||
|
|
||
|
if (bindings.arg) {
|
||
|
// Element ID specified as arg
|
||
|
// We must prepend '#' to become a CSS selector
|
||
|
config.container = "#".concat(bindings.arg);
|
||
|
} // If title is not provided, try title attribute
|
||
|
|
||
|
|
||
|
if (isUndefined(config.title)) {
|
||
|
// Try attribute
|
||
|
var data = vnode.data || {};
|
||
|
config.title = data.attrs && !isUndefinedOrNull(data.attrs.title) ? data.attrs.title : undefined;
|
||
|
} // Normalize delay
|
||
|
|
||
|
|
||
|
if (!isPlainObject(config.delay)) {
|
||
|
config.delay = {
|
||
|
show: toInteger(config.delay, 0),
|
||
|
hide: toInteger(config.delay, 0)
|
||
|
};
|
||
|
} // Process modifiers
|
||
|
|
||
|
|
||
|
keys(bindings.modifiers).forEach(function (mod) {
|
||
|
if (htmlRE.test(mod)) {
|
||
|
// Title/content allows HTML
|
||
|
config.html = true;
|
||
|
} else if (noFadeRE.test(mod)) {
|
||
|
// No animation
|
||
|
config.animation = false;
|
||
|
} else if (placementRE.test(mod)) {
|
||
|
// Placement of popover
|
||
|
config.placement = mod;
|
||
|
} else if (boundaryRE.test(mod)) {
|
||
|
// Boundary of popover
|
||
|
mod = mod === 'scrollparent' ? 'scrollParent' : mod;
|
||
|
config.boundary = mod;
|
||
|
} else if (delayRE.test(mod)) {
|
||
|
// Delay value
|
||
|
var delay = toInteger(mod.slice(1), 0);
|
||
|
config.delay.show = delay;
|
||
|
config.delay.hide = delay;
|
||
|
} else if (delayShowRE.test(mod)) {
|
||
|
// Delay show value
|
||
|
config.delay.show = toInteger(mod.slice(2), 0);
|
||
|
} else if (delayHideRE.test(mod)) {
|
||
|
// Delay hide value
|
||
|
config.delay.hide = toInteger(mod.slice(2), 0);
|
||
|
} else if (offsetRE.test(mod)) {
|
||
|
// Offset value, negative allowed
|
||
|
config.offset = toInteger(mod.slice(1), 0);
|
||
|
} else if (variantRE.test(mod)) {
|
||
|
// Variant
|
||
|
config.variant = mod.slice(2) || null;
|
||
|
}
|
||
|
}); // Special handling of event trigger modifiers trigger is
|
||
|
// a space separated list
|
||
|
|
||
|
var selectedTriggers = {}; // Parse current config object trigger
|
||
|
|
||
|
concat(config.trigger || '').filter(identity).join(' ').trim().toLowerCase().split(spacesRE).forEach(function (trigger) {
|
||
|
if (validTriggers[trigger]) {
|
||
|
selectedTriggers[trigger] = true;
|
||
|
}
|
||
|
}); // Parse modifiers for triggers
|
||
|
|
||
|
keys(bindings.modifiers).forEach(function (mod) {
|
||
|
mod = mod.toLowerCase();
|
||
|
|
||
|
if (validTriggers[mod]) {
|
||
|
// If modifier is a valid trigger
|
||
|
selectedTriggers[mod] = true;
|
||
|
}
|
||
|
}); // Sanitize triggers
|
||
|
|
||
|
config.trigger = keys(selectedTriggers).join(' ');
|
||
|
|
||
|
if (config.trigger === 'blur') {
|
||
|
// Blur by itself is useless, so convert it to 'focus'
|
||
|
config.trigger = 'focus';
|
||
|
}
|
||
|
|
||
|
if (!config.trigger) {
|
||
|
// Use default trigger
|
||
|
config.trigger = DefaultTrigger;
|
||
|
}
|
||
|
|
||
|
return config;
|
||
|
}; // Add or update Popover on our element
|
||
|
|
||
|
|
||
|
var applyPopover = function applyPopover(el, bindings, vnode) {
|
||
|
if (!isBrowser) {
|
||
|
/* istanbul ignore next */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var config = parseBindings(bindings, vnode);
|
||
|
|
||
|
if (!el[BV_POPOVER]) {
|
||
|
var $parent = vnode.context;
|
||
|
el[BV_POPOVER] = new BVPopover({
|
||
|
parent: $parent,
|
||
|
// Add the parent's scoped style attribute data
|
||
|
_scopeId: getScopId($parent, undefined)
|
||
|
});
|
||
|
el[BV_POPOVER].__bv_prev_data__ = {};
|
||
|
el[BV_POPOVER].$on('show', function ()
|
||
|
/* istanbul ignore next: for now */
|
||
|
{
|
||
|
// Before showing the popover, we update the title
|
||
|
// and content if they are functions
|
||
|
var data = {};
|
||
|
|
||
|
if (isFunction(config.title)) {
|
||
|
data.title = config.title(el);
|
||
|
}
|
||
|
|
||
|
if (isFunction(config.content)) {
|
||
|
data.content = config.content(el);
|
||
|
}
|
||
|
|
||
|
if (keys(data).length > 0) {
|
||
|
el[BV_POPOVER].updateData(data);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var data = {
|
||
|
title: config.title,
|
||
|
content: config.content,
|
||
|
triggers: config.trigger,
|
||
|
placement: config.placement,
|
||
|
fallbackPlacement: config.fallbackPlacement,
|
||
|
variant: config.variant,
|
||
|
customClass: config.customClass,
|
||
|
container: config.container,
|
||
|
boundary: config.boundary,
|
||
|
delay: config.delay,
|
||
|
offset: config.offset,
|
||
|
noFade: !config.animation,
|
||
|
id: config.id,
|
||
|
disabled: config.disabled,
|
||
|
html: config.html
|
||
|
};
|
||
|
var oldData = el[BV_POPOVER].__bv_prev_data__;
|
||
|
el[BV_POPOVER].__bv_prev_data__ = data;
|
||
|
|
||
|
if (!looseEqual(data, oldData)) {
|
||
|
// We only update the instance if data has changed
|
||
|
var newData = {
|
||
|
target: el
|
||
|
};
|
||
|
keys(data).forEach(function (prop) {
|
||
|
// We only pass data properties that have changed
|
||
|
if (data[prop] !== oldData[prop]) {
|
||
|
// If title/content is a function, we execute it here
|
||
|
newData[prop] = (prop === 'title' || prop === 'content') && isFunction(data[prop]) ?
|
||
|
/* istanbul ignore next */
|
||
|
data[prop](el) : data[prop];
|
||
|
}
|
||
|
});
|
||
|
el[BV_POPOVER].updateData(newData);
|
||
|
}
|
||
|
}; // Remove Popover from our element
|
||
|
|
||
|
|
||
|
var removePopover = function removePopover(el) {
|
||
|
if (el[BV_POPOVER]) {
|
||
|
el[BV_POPOVER].$destroy();
|
||
|
el[BV_POPOVER] = null;
|
||
|
}
|
||
|
|
||
|
delete el[BV_POPOVER];
|
||
|
}; // Export our directive
|
||
|
|
||
|
|
||
|
export var VBPopover = {
|
||
|
bind: function bind(el, bindings, vnode) {
|
||
|
applyPopover(el, bindings, vnode);
|
||
|
},
|
||
|
// We use `componentUpdated` here instead of `update`, as the former
|
||
|
// waits until the containing component and children have finished updating
|
||
|
componentUpdated: function componentUpdated(el, bindings, vnode) {
|
||
|
// Performed in a `$nextTick()` to prevent endless render/update loops
|
||
|
vnode.context.$nextTick(function () {
|
||
|
applyPopover(el, bindings, vnode);
|
||
|
});
|
||
|
},
|
||
|
unbind: function unbind(el) {
|
||
|
removePopover(el);
|
||
|
}
|
||
|
};
|