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.
32712 lines
1.0 MiB
32712 lines
1.0 MiB
/*!
|
|
* BootstrapVue 2.19.0
|
|
*
|
|
* @link https://bootstrap-vue.org
|
|
* @source https://github.com/bootstrap-vue/bootstrap-vue
|
|
* @copyright (c) 2016-2020 BootstrapVue
|
|
* @license MIT
|
|
* https://github.com/bootstrap-vue/bootstrap-vue/blob/master/LICENSE
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) :
|
|
typeof define === 'function' && define.amd ? define(['vue'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrapVue = factory(global.Vue));
|
|
}(this, (function (Vue) { 'use strict';
|
|
|
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
|
|
var Vue__default = /*#__PURE__*/_interopDefaultLegacy(Vue);
|
|
|
|
function _typeof(obj) {
|
|
"@babel/helpers - typeof";
|
|
|
|
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
_typeof = function (obj) {
|
|
return typeof obj;
|
|
};
|
|
} else {
|
|
_typeof = function (obj) {
|
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
};
|
|
}
|
|
|
|
return _typeof(obj);
|
|
}
|
|
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 _objectSpread2(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 _inherits(subClass, superClass) {
|
|
if (typeof superClass !== "function" && superClass !== null) {
|
|
throw new TypeError("Super expression must either be null or a function");
|
|
}
|
|
|
|
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
|
constructor: {
|
|
value: subClass,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
if (superClass) _setPrototypeOf(subClass, superClass);
|
|
}
|
|
|
|
function _getPrototypeOf(o) {
|
|
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
|
return o.__proto__ || Object.getPrototypeOf(o);
|
|
};
|
|
return _getPrototypeOf(o);
|
|
}
|
|
|
|
function _setPrototypeOf(o, p) {
|
|
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
|
|
return _setPrototypeOf(o, p);
|
|
}
|
|
|
|
function _isNativeReflectConstruct() {
|
|
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
|
|
if (Reflect.construct.sham) return false;
|
|
if (typeof Proxy === "function") return true;
|
|
|
|
try {
|
|
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function _construct(Parent, args, Class) {
|
|
if (_isNativeReflectConstruct()) {
|
|
_construct = Reflect.construct;
|
|
} else {
|
|
_construct = function _construct(Parent, args, Class) {
|
|
var a = [null];
|
|
a.push.apply(a, args);
|
|
var Constructor = Function.bind.apply(Parent, a);
|
|
var instance = new Constructor();
|
|
if (Class) _setPrototypeOf(instance, Class.prototype);
|
|
return instance;
|
|
};
|
|
}
|
|
|
|
return _construct.apply(null, arguments);
|
|
}
|
|
|
|
function _isNativeFunction(fn) {
|
|
return Function.toString.call(fn).indexOf("[native code]") !== -1;
|
|
}
|
|
|
|
function _wrapNativeSuper(Class) {
|
|
var _cache = typeof Map === "function" ? new Map() : undefined;
|
|
|
|
_wrapNativeSuper = function _wrapNativeSuper(Class) {
|
|
if (Class === null || !_isNativeFunction(Class)) return Class;
|
|
|
|
if (typeof Class !== "function") {
|
|
throw new TypeError("Super expression must either be null or a function");
|
|
}
|
|
|
|
if (typeof _cache !== "undefined") {
|
|
if (_cache.has(Class)) return _cache.get(Class);
|
|
|
|
_cache.set(Class, Wrapper);
|
|
}
|
|
|
|
function Wrapper() {
|
|
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
|
|
}
|
|
|
|
Wrapper.prototype = Object.create(Class.prototype, {
|
|
constructor: {
|
|
value: Wrapper,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
return _setPrototypeOf(Wrapper, Class);
|
|
};
|
|
|
|
return _wrapNativeSuper(Class);
|
|
}
|
|
|
|
function _assertThisInitialized(self) {
|
|
if (self === void 0) {
|
|
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
function _possibleConstructorReturn(self, call) {
|
|
if (call && (typeof call === "object" || typeof call === "function")) {
|
|
return call;
|
|
}
|
|
|
|
return _assertThisInitialized(self);
|
|
}
|
|
|
|
function _createSuper(Derived) {
|
|
var hasNativeReflectConstruct = _isNativeReflectConstruct();
|
|
|
|
return function _createSuperInternal() {
|
|
var Super = _getPrototypeOf(Derived),
|
|
result;
|
|
|
|
if (hasNativeReflectConstruct) {
|
|
var NewTarget = _getPrototypeOf(this).constructor;
|
|
|
|
result = Reflect.construct(Super, arguments, NewTarget);
|
|
} else {
|
|
result = Super.apply(this, arguments);
|
|
}
|
|
|
|
return _possibleConstructorReturn(this, result);
|
|
};
|
|
}
|
|
|
|
function _superPropBase(object, property) {
|
|
while (!Object.prototype.hasOwnProperty.call(object, property)) {
|
|
object = _getPrototypeOf(object);
|
|
if (object === null) break;
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
function _get(target, property, receiver) {
|
|
if (typeof Reflect !== "undefined" && Reflect.get) {
|
|
_get = Reflect.get;
|
|
} else {
|
|
_get = function _get(target, property, receiver) {
|
|
var base = _superPropBase(target, property);
|
|
|
|
if (!base) return;
|
|
var desc = Object.getOwnPropertyDescriptor(base, property);
|
|
|
|
if (desc.get) {
|
|
return desc.get.call(receiver);
|
|
}
|
|
|
|
return desc.value;
|
|
};
|
|
}
|
|
|
|
return _get(target, property, receiver || target);
|
|
}
|
|
|
|
function _slicedToArray(arr, i) {
|
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
|
}
|
|
|
|
function _toConsumableArray(arr) {
|
|
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
|
|
}
|
|
|
|
function _arrayWithoutHoles(arr) {
|
|
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
|
|
}
|
|
|
|
function _arrayWithHoles(arr) {
|
|
if (Array.isArray(arr)) return arr;
|
|
}
|
|
|
|
function _iterableToArray(iter) {
|
|
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
|
|
}
|
|
|
|
function _iterableToArrayLimit(arr, i) {
|
|
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
|
|
var _arr = [];
|
|
var _n = true;
|
|
var _d = false;
|
|
var _e = undefined;
|
|
|
|
try {
|
|
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
|
|
_arr.push(_s.value);
|
|
|
|
if (i && _arr.length === i) break;
|
|
}
|
|
} catch (err) {
|
|
_d = true;
|
|
_e = err;
|
|
} finally {
|
|
try {
|
|
if (!_n && _i["return"] != null) _i["return"]();
|
|
} finally {
|
|
if (_d) throw _e;
|
|
}
|
|
}
|
|
|
|
return _arr;
|
|
}
|
|
|
|
function _unsupportedIterableToArray(o, minLen) {
|
|
if (!o) return;
|
|
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
if (n === "Map" || n === "Set") return Array.from(o);
|
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
}
|
|
|
|
function _arrayLikeToArray(arr, len) {
|
|
if (len == null || len > arr.length) len = arr.length;
|
|
|
|
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
|
|
return arr2;
|
|
}
|
|
|
|
function _nonIterableSpread() {
|
|
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
|
|
function _nonIterableRest() {
|
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
|
|
var e=function(){return (e=Object.assign||function(e){for(var t,r=1,s=arguments.length;r<s;r++)for(var a in t=arguments[r])Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e}).apply(this,arguments)},t={kebab:/-(\w)/g,styleProp:/:(.*)/,styleList:/;(?![^(]*\))/g};function r(e,t){return t?t.toUpperCase():""}function s(e){for(var s,a={},c=0,o=e.split(t.styleList);c<o.length;c++){var n=o[c].split(t.styleProp),i=n[0],l=n[1];(i=i.trim())&&("string"==typeof l&&(l=l.trim()),a[(s=i,s.replace(t.kebab,r))]=l);}return a}function a(){for(var t,r,a={},c=arguments.length;c--;)for(var o=0,n=Object.keys(arguments[c]);o<n.length;o++)switch(t=n[o]){case"class":case"style":case"directives":if(Array.isArray(a[t])||(a[t]=[]),"style"===t){var i=void 0;i=Array.isArray(arguments[c].style)?arguments[c].style:[arguments[c].style];for(var l=0;l<i.length;l++){var y=i[l];"string"==typeof y&&(i[l]=s(y));}arguments[c].style=i;}a[t]=a[t].concat(arguments[c][t]);break;case"staticClass":if(!arguments[c][t])break;void 0===a[t]&&(a[t]=""),a[t]&&(a[t]+=" "),a[t]+=arguments[c][t].trim();break;case"on":case"nativeOn":a[t]||(a[t]={});for(var p=0,f=Object.keys(arguments[c][t]||{});p<f.length;p++)r=f[p],a[t][r]?a[t][r]=[].concat(a[t][r],arguments[c][t][r]):a[t][r]=arguments[c][t][r];break;case"attrs":case"props":case"domProps":case"scopedSlots":case"staticStyle":case"hook":case"transition":a[t]||(a[t]={}),a[t]=e({},arguments[c][t],a[t]);break;case"slot":case"key":case"ref":case"tag":case"show":case"keepAlive":default:a[t]||(a[t]=arguments[c][t]);}return a}
|
|
|
|
var NAME = 'BvConfig';
|
|
var PROP_NAME = '$bvConfig';
|
|
var DEFAULT_BREAKPOINT = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
|
|
/**
|
|
* Utilities to get information about the current environment
|
|
*/
|
|
// --- Constants ---
|
|
var hasWindowSupport = typeof window !== 'undefined';
|
|
var hasDocumentSupport = typeof document !== 'undefined';
|
|
var hasNavigatorSupport = typeof navigator !== 'undefined';
|
|
var hasPromiseSupport = typeof Promise !== 'undefined';
|
|
/* istanbul ignore next: JSDOM always returns false */
|
|
|
|
var hasMutationObserverSupport = typeof MutationObserver !== 'undefined' || typeof WebKitMutationObserver !== 'undefined' || typeof MozMutationObserver !== 'undefined';
|
|
var isBrowser = hasWindowSupport && hasDocumentSupport && hasNavigatorSupport; // Browser type sniffing
|
|
|
|
var userAgent = isBrowser ? window.navigator.userAgent.toLowerCase() : '';
|
|
var isJSDOM = userAgent.indexOf('jsdom') > 0;
|
|
var isIE = /msie|trident/.test(userAgent); // Determine if the browser supports the option passive for events
|
|
|
|
var hasPassiveEventSupport = function () {
|
|
var passiveEventSupported = false;
|
|
|
|
if (isBrowser) {
|
|
try {
|
|
var options = {
|
|
get passive() {
|
|
// This function will be called when the browser
|
|
// attempts to access the passive property.
|
|
|
|
/* istanbul ignore next: will never be called in JSDOM */
|
|
passiveEventSupported = true;
|
|
}
|
|
|
|
};
|
|
window.addEventListener('test', options, options);
|
|
window.removeEventListener('test', options, options);
|
|
} catch (err) {
|
|
/* istanbul ignore next: will never be called in JSDOM */
|
|
passiveEventSupported = false;
|
|
}
|
|
}
|
|
|
|
return passiveEventSupported;
|
|
}();
|
|
var hasTouchSupport = isBrowser && ('ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0);
|
|
var hasPointerEventSupport = isBrowser && Boolean(window.PointerEvent || window.MSPointerEvent);
|
|
/* istanbul ignore next: JSDOM only checks for 'IntersectionObserver' */
|
|
|
|
var hasIntersectionObserverSupport = isBrowser && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && // Edge 15 and UC Browser lack support for `isIntersecting`
|
|
// but we an use intersectionRatio > 0 instead
|
|
// 'isIntersecting' in window.IntersectionObserverEntry.prototype &&
|
|
'intersectionRatio' in window.IntersectionObserverEntry.prototype; // --- Getters ---
|
|
|
|
var getEnv = function getEnv(key) {
|
|
var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
var env = typeof process !== 'undefined' && process ? process.env || {} : {};
|
|
|
|
if (!key) {
|
|
/* istanbul ignore next */
|
|
return env;
|
|
}
|
|
|
|
return env[key] || fallback;
|
|
};
|
|
var getNoWarn = function getNoWarn() {
|
|
return getEnv('BOOTSTRAP_VUE_NO_WARN') || getEnv('NODE_ENV') === 'production';
|
|
};
|
|
|
|
var w = hasWindowSupport ? window : {};
|
|
var Element$1 = hasWindowSupport ? w.Element : /*#__PURE__*/function (_Object) {
|
|
_inherits(Element, _Object);
|
|
|
|
var _super = _createSuper(Element);
|
|
|
|
function Element() {
|
|
_classCallCheck(this, Element);
|
|
|
|
return _super.apply(this, arguments);
|
|
}
|
|
|
|
return Element;
|
|
}( /*#__PURE__*/_wrapNativeSuper(Object));
|
|
var HTMLElement = hasWindowSupport ? w.HTMLElement : /*#__PURE__*/function (_Element) {
|
|
_inherits(HTMLElement, _Element);
|
|
|
|
var _super2 = _createSuper(HTMLElement);
|
|
|
|
function HTMLElement() {
|
|
_classCallCheck(this, HTMLElement);
|
|
|
|
return _super2.apply(this, arguments);
|
|
}
|
|
|
|
return HTMLElement;
|
|
}(Element$1);
|
|
var SVGElement = hasWindowSupport ? w.SVGElement : /*#__PURE__*/function (_Element2) {
|
|
_inherits(SVGElement, _Element2);
|
|
|
|
var _super3 = _createSuper(SVGElement);
|
|
|
|
function SVGElement() {
|
|
_classCallCheck(this, SVGElement);
|
|
|
|
return _super3.apply(this, arguments);
|
|
}
|
|
|
|
return SVGElement;
|
|
}(Element$1);
|
|
var File = hasWindowSupport ? w.File : /*#__PURE__*/function (_Object2) {
|
|
_inherits(File, _Object2);
|
|
|
|
var _super4 = _createSuper(File);
|
|
|
|
function File() {
|
|
_classCallCheck(this, File);
|
|
|
|
return _super4.apply(this, arguments);
|
|
}
|
|
|
|
return File;
|
|
}( /*#__PURE__*/_wrapNativeSuper(Object));
|
|
|
|
var toType = function toType(val) {
|
|
return _typeof(val);
|
|
};
|
|
var toRawType = function toRawType(val) {
|
|
return Object.prototype.toString.call(val).slice(8, -1);
|
|
};
|
|
var isUndefined = function isUndefined(val) {
|
|
return val === undefined;
|
|
};
|
|
var isNull = function isNull(val) {
|
|
return val === null;
|
|
};
|
|
var isUndefinedOrNull = function isUndefinedOrNull(val) {
|
|
return isUndefined(val) || isNull(val);
|
|
};
|
|
var isFunction = function isFunction(val) {
|
|
return toType(val) === 'function';
|
|
};
|
|
var isBoolean = function isBoolean(val) {
|
|
return toType(val) === 'boolean';
|
|
};
|
|
var isString = function isString(val) {
|
|
return toType(val) === 'string';
|
|
};
|
|
var isNumber = function isNumber(val) {
|
|
return toType(val) === 'number';
|
|
}; // Is a value number like (i.e. a number or a number as string)
|
|
|
|
var isNumeric = function isNumeric(value) {
|
|
return !isNaN(parseInt(value, 10));
|
|
};
|
|
var isArray = function isArray(val) {
|
|
return Array.isArray(val);
|
|
}; // Quick object check
|
|
// This is primarily used to tell Objects from primitive values
|
|
// when we know the value is a JSON-compliant type
|
|
// Note object could be a complex type like array, Date, etc.
|
|
|
|
var isObject = function isObject(obj) {
|
|
return obj !== null && _typeof(obj) === 'object';
|
|
}; // Strict object type check
|
|
// Only returns true for plain JavaScript objects
|
|
|
|
var isPlainObject = function isPlainObject(obj) {
|
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
|
};
|
|
var isDate = function isDate(val) {
|
|
return val instanceof Date;
|
|
};
|
|
var isEvent = function isEvent(val) {
|
|
return val instanceof Event;
|
|
};
|
|
var isFile = function isFile(val) {
|
|
return val instanceof File;
|
|
};
|
|
var isRegExp = function isRegExp(val) {
|
|
return toRawType(val) === 'RegExp';
|
|
};
|
|
var isPromise = function isPromise(val) {
|
|
return !isUndefinedOrNull(val) && isFunction(val.then) && isFunction(val.catch);
|
|
};
|
|
|
|
var assign = function assign() {
|
|
return Object.assign.apply(Object, arguments);
|
|
};
|
|
var create = function create(proto, optionalProps) {
|
|
return Object.create(proto, optionalProps);
|
|
};
|
|
var defineProperties = function defineProperties(obj, props) {
|
|
return Object.defineProperties(obj, props);
|
|
};
|
|
var defineProperty = function defineProperty(obj, prop, descriptor) {
|
|
return Object.defineProperty(obj, prop, descriptor);
|
|
};
|
|
var getOwnPropertyNames = function getOwnPropertyNames(obj) {
|
|
return Object.getOwnPropertyNames(obj);
|
|
};
|
|
var keys = function keys(obj) {
|
|
return Object.keys(obj);
|
|
}; // --- "Instance" ---
|
|
|
|
var hasOwnProperty = function hasOwnProperty(obj, prop) {
|
|
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
};
|
|
var toString = function toString(obj) {
|
|
return Object.prototype.toString.call(obj);
|
|
}; // --- Utilities ---
|
|
|
|
/**
|
|
* Shallow copy an object. If the passed in object
|
|
* is null or undefined, returns an empty object
|
|
*/
|
|
|
|
var clone = function clone(obj) {
|
|
return _objectSpread2({}, obj);
|
|
};
|
|
/**
|
|
* Return a shallow copy of object with the specified properties only
|
|
* @link https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
|
|
*/
|
|
|
|
var pick = function pick(obj, props) {
|
|
return keys(obj).filter(function (key) {
|
|
return props.indexOf(key) !== -1;
|
|
}).reduce(function (result, key) {
|
|
return _objectSpread2(_objectSpread2({}, result), {}, _defineProperty({}, key, obj[key]));
|
|
}, {});
|
|
};
|
|
/**
|
|
* Return a shallow copy of object with the specified properties omitted
|
|
* @link https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
|
|
*/
|
|
|
|
var omit = function omit(obj, props) {
|
|
return keys(obj).filter(function (key) {
|
|
return props.indexOf(key) === -1;
|
|
}).reduce(function (result, key) {
|
|
return _objectSpread2(_objectSpread2({}, result), {}, _defineProperty({}, key, obj[key]));
|
|
}, {});
|
|
};
|
|
/**
|
|
* Merges two object deeply together
|
|
* @link https://gist.github.com/Salakar/1d7137de9cb8b704e48a
|
|
*/
|
|
|
|
var mergeDeep = function mergeDeep(target, source) {
|
|
if (isObject(target) && isObject(source)) {
|
|
keys(source).forEach(function (key) {
|
|
if (isObject(source[key])) {
|
|
if (!target[key] || !isObject(target[key])) {
|
|
target[key] = source[key];
|
|
}
|
|
|
|
mergeDeep(target[key], source[key]);
|
|
} else {
|
|
assign(target, _defineProperty({}, key, source[key]));
|
|
}
|
|
});
|
|
}
|
|
|
|
return target;
|
|
};
|
|
/**
|
|
* Convenience method to create a read-only descriptor
|
|
*/
|
|
|
|
var readonlyDescriptor = function readonlyDescriptor() {
|
|
return {
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false
|
|
};
|
|
};
|
|
|
|
var cloneDeep = function cloneDeep(obj) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : obj;
|
|
|
|
if (isArray(obj)) {
|
|
return obj.reduce(function (result, val) {
|
|
return [].concat(_toConsumableArray(result), [cloneDeep(val, val)]);
|
|
}, []);
|
|
}
|
|
|
|
if (isPlainObject(obj)) {
|
|
return keys(obj).reduce(function (result, key) {
|
|
return _objectSpread2(_objectSpread2({}, result), {}, _defineProperty({}, key, cloneDeep(obj[key], obj[key])));
|
|
}, {});
|
|
}
|
|
|
|
return defaultValue;
|
|
};
|
|
|
|
// --- General ---
|
|
var RX_ARRAY_NOTATION = /\[(\d+)]/g;
|
|
var RX_DIGITS = /^\d+$/;
|
|
var RX_EXTENSION = /^\..+/;
|
|
var RX_HASH = /^#/;
|
|
var RX_HASH_ID = /^#[A-Za-z]+[\w\-:.]*$/;
|
|
var RX_HTML_TAGS = /(<([^>]+)>)/gi;
|
|
var RX_HYPHENATE = /\B([A-Z])/g;
|
|
var RX_LOWER_UPPER = /([a-z])([A-Z])/g;
|
|
var RX_NUMBER = /^[0-9]*\.?[0-9]+$/;
|
|
var RX_PLUS = /\+/g;
|
|
var RX_REGEXP_REPLACE = /[-/\\^$*+?.()|[\]{}]/g;
|
|
var RX_SPACES = /[\s\uFEFF\xA0]+/g;
|
|
var RX_SPACE_SPLIT = /\s+/;
|
|
var RX_STAR = /\/\*$/;
|
|
var RX_START_SPACE_WORD = /(\s|^)(\w)/g;
|
|
var RX_TRIM_LEFT = /^\s+/;
|
|
var RX_UNDERSCORE = /_/g;
|
|
var RX_UN_KEBAB = /-(\w)/g; // --- Date ---
|
|
// Loose YYYY-MM-DD matching, ignores any appended time inforation
|
|
// Matches '1999-12-20', '1999-1-1', '1999-01-20T22:51:49.118Z', '1999-01-02 13:00:00'
|
|
|
|
var RX_DATE = /^\d+-\d\d?-\d\d?(?:\s|T|$)/; // Used to split off the date parts of the YYYY-MM-DD string
|
|
|
|
var RX_DATE_SPLIT = /-|\s|T/; // Time string RegEx (optional seconds)
|
|
|
|
var RX_TIME = /^([0-1]?[0-9]|2[0-3]):[0-5]?[0-9](:[0-5]?[0-9])?$/; // --- URL ---
|
|
// HREFs must end with a hash followed by at least one non-hash character
|
|
|
|
var RX_HREF = /^.*(#[^#]+)$/;
|
|
var RX_ENCODED_COMMA = /%2C/g;
|
|
var RX_ENCODE_REVERSE = /[!'()*]/g;
|
|
var RX_QUERY_START = /^(\?|#|&)/; // --- Aspect ---
|
|
|
|
var RX_ASPECT = /^\d+(\.\d*)?[/:]\d+(\.\d*)?$/;
|
|
var RX_ASPECT_SEPARATOR = /[/:]/; // --- Grid ---
|
|
|
|
var RX_COL_CLASS = /^col-/; // --- Icon ---
|
|
|
|
var RX_ICON_PREFIX = /^BIcon/; // --- Locale ---
|
|
|
|
var RX_STRIP_LOCALE_MODS = /-u-.+/;
|
|
|
|
var identity = function identity(x) {
|
|
return x;
|
|
};
|
|
|
|
/**
|
|
* Get property defined by dot/array notation in string, returns undefined if not found
|
|
*
|
|
* @link https://gist.github.com/jeneg/9767afdcca45601ea44930ea03e0febf#gistcomment-1935901
|
|
*
|
|
* @param {Object} obj
|
|
* @param {string|Array} path
|
|
* @return {*}
|
|
*/
|
|
|
|
var getRaw = function getRaw(obj, path) {
|
|
var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
|
|
// Handle array of path values
|
|
path = isArray(path) ? path.join('.') : path; // If no path or no object passed
|
|
|
|
if (!path || !isObject(obj)) {
|
|
return defaultValue;
|
|
} // Handle edge case where user has dot(s) in top-level item field key
|
|
// See https://github.com/bootstrap-vue/bootstrap-vue/issues/2762
|
|
// Switched to `in` operator vs `hasOwnProperty` to handle obj.prototype getters
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3463
|
|
|
|
|
|
if (path in obj) {
|
|
return obj[path];
|
|
} // Handle string array notation (numeric indices only)
|
|
|
|
|
|
path = String(path).replace(RX_ARRAY_NOTATION, '.$1');
|
|
var steps = path.split('.').filter(identity); // Handle case where someone passes a string of only dots
|
|
|
|
if (steps.length === 0) {
|
|
return defaultValue;
|
|
} // Traverse path in object to find result
|
|
// Switched to `in` operator vs `hasOwnProperty` to handle obj.prototype getters
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3463
|
|
|
|
|
|
return steps.every(function (step) {
|
|
return isObject(obj) && step in obj && !isUndefinedOrNull(obj = obj[step]);
|
|
}) ? obj : isNull(obj) ? null : defaultValue;
|
|
};
|
|
/**
|
|
* Get property defined by dot/array notation in string.
|
|
*
|
|
* @link https://gist.github.com/jeneg/9767afdcca45601ea44930ea03e0febf#gistcomment-1935901
|
|
*
|
|
* @param {Object} obj
|
|
* @param {string|Array} path
|
|
* @param {*} defaultValue (optional)
|
|
* @return {*}
|
|
*/
|
|
|
|
var get = function get(obj, path) {
|
|
var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
var val = getRaw(obj, path);
|
|
return isUndefinedOrNull(val) ? defaultValue : val;
|
|
};
|
|
|
|
/**
|
|
* Log a warning message to the console with BootstrapVue formatting
|
|
* @param {string} message
|
|
*/
|
|
|
|
var warn = function warn(message)
|
|
/* istanbul ignore next */
|
|
{
|
|
var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
|
|
if (!getNoWarn()) {
|
|
console.warn("[BootstrapVue warn]: ".concat(source ? "".concat(source, " - ") : '').concat(message));
|
|
}
|
|
};
|
|
/**
|
|
* Warn when no Promise support is given
|
|
* @param {string} source
|
|
* @returns {boolean} warned
|
|
*/
|
|
|
|
var warnNotClient = function warnNotClient(source) {
|
|
/* istanbul ignore else */
|
|
if (isBrowser) {
|
|
return false;
|
|
} else {
|
|
warn("".concat(source, ": Can not be called during SSR."));
|
|
return true;
|
|
}
|
|
};
|
|
/**
|
|
* Warn when no Promise support is given
|
|
* @param {string} source
|
|
* @returns {boolean} warned
|
|
*/
|
|
|
|
var warnNoPromiseSupport = function warnNoPromiseSupport(source) {
|
|
/* istanbul ignore else */
|
|
if (hasPromiseSupport) {
|
|
return false;
|
|
} else {
|
|
warn("".concat(source, ": Requires Promise support."));
|
|
return true;
|
|
}
|
|
};
|
|
/**
|
|
* Warn when no MutationObserver support is given
|
|
* @param {string} source
|
|
* @returns {boolean} warned
|
|
*/
|
|
|
|
var warnNoMutationObserverSupport = function warnNoMutationObserverSupport(source) {
|
|
/* istanbul ignore else */
|
|
if (hasMutationObserverSupport) {
|
|
return false;
|
|
} else {
|
|
warn("".concat(source, ": Requires MutationObserver support."));
|
|
return true;
|
|
}
|
|
};
|
|
|
|
var BvConfig = /*#__PURE__*/function () {
|
|
function BvConfig() {
|
|
_classCallCheck(this, BvConfig);
|
|
|
|
this.$_config = {};
|
|
} // Method to merge in user config parameters
|
|
|
|
|
|
_createClass(BvConfig, [{
|
|
key: "setConfig",
|
|
value: function setConfig() {
|
|
var _this = this;
|
|
|
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
|
|
/* istanbul ignore next */
|
|
if (!isPlainObject(config)) {
|
|
return;
|
|
}
|
|
|
|
var configKeys = getOwnPropertyNames(config);
|
|
configKeys.forEach(function (key) {
|
|
/* istanbul ignore next */
|
|
var subConfig = config[key];
|
|
|
|
if (key === 'breakpoints') {
|
|
/* istanbul ignore if */
|
|
if (!isArray(subConfig) || subConfig.length < 2 || subConfig.some(function (b) {
|
|
return !isString(b) || b.length === 0;
|
|
})) {
|
|
warn('"breakpoints" must be an array of at least 2 breakpoint names', NAME);
|
|
} else {
|
|
_this.$_config[key] = cloneDeep(subConfig);
|
|
}
|
|
} else if (isPlainObject(subConfig)) {
|
|
// Component prop defaults
|
|
_this.$_config[key] = getOwnPropertyNames(subConfig).reduce(function (config, prop) {
|
|
if (!isUndefined(subConfig[prop])) {
|
|
config[prop] = cloneDeep(subConfig[prop]);
|
|
}
|
|
|
|
return config;
|
|
}, _this.$_config[key] || {});
|
|
}
|
|
});
|
|
} // Clear the config
|
|
|
|
}, {
|
|
key: "resetConfig",
|
|
value: function resetConfig() {
|
|
this.$_config = {};
|
|
} // Returns a deep copy of the user config
|
|
|
|
}, {
|
|
key: "getConfig",
|
|
value: function getConfig() {
|
|
return cloneDeep(this.$_config);
|
|
} // Returns a deep copy of the config value
|
|
|
|
}, {
|
|
key: "getConfigValue",
|
|
value: function getConfigValue(key) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
|
|
return cloneDeep(getRaw(this.$_config, key, defaultValue));
|
|
}
|
|
}]);
|
|
|
|
return BvConfig;
|
|
}(); // Method for applying a global config
|
|
|
|
|
|
var setConfig = function setConfig() {
|
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var Vue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Vue__default['default'];
|
|
// Ensure we have a `$bvConfig` Object on the Vue prototype
|
|
// We set on Vue and OurVue just in case consumer has not set an alias of `vue`
|
|
Vue.prototype[PROP_NAME] = Vue__default['default'].prototype[PROP_NAME] = Vue.prototype[PROP_NAME] || Vue__default['default'].prototype[PROP_NAME] || new BvConfig(); // Apply the config values
|
|
|
|
Vue.prototype[PROP_NAME].setConfig(config);
|
|
}; // Method for resetting the user config
|
|
|
|
/**
|
|
* Checks if there are multiple instances of Vue, and warns (once) about possible issues.
|
|
* @param {object} Vue
|
|
*/
|
|
|
|
var checkMultipleVue = function () {
|
|
var checkMultipleVueWarned = false;
|
|
var MULTIPLE_VUE_WARNING = ['Multiple instances of Vue detected!', 'You may need to set up an alias for Vue in your bundler config.', 'See: https://bootstrap-vue.org/docs#using-module-bundlers'].join('\n');
|
|
return function (Vue) {
|
|
/* istanbul ignore next */
|
|
if (!checkMultipleVueWarned && Vue__default['default'] !== Vue && !isJSDOM) {
|
|
warn(MULTIPLE_VUE_WARNING);
|
|
}
|
|
|
|
checkMultipleVueWarned = true;
|
|
};
|
|
}();
|
|
/**
|
|
* Plugin install factory function.
|
|
* @param {object} { components, directives }
|
|
* @returns {function} plugin install function
|
|
*/
|
|
|
|
var installFactory = function installFactory() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
components = _ref.components,
|
|
directives = _ref.directives,
|
|
plugins = _ref.plugins;
|
|
|
|
var install = function install(Vue) {
|
|
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
if (install.installed) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
install.installed = true;
|
|
checkMultipleVue(Vue);
|
|
setConfig(config, Vue);
|
|
registerComponents(Vue, components);
|
|
registerDirectives(Vue, directives);
|
|
registerPlugins(Vue, plugins);
|
|
};
|
|
|
|
install.installed = false;
|
|
return install;
|
|
};
|
|
/**
|
|
* Plugin object factory function.
|
|
* @param {object} { components, directives, plugins }
|
|
* @returns {object} plugin install object
|
|
*/
|
|
|
|
var pluginFactory = function pluginFactory() {
|
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var extend = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
return _objectSpread2(_objectSpread2({}, extend), {}, {
|
|
install: installFactory(options)
|
|
});
|
|
};
|
|
/**
|
|
* Load a group of plugins.
|
|
* @param {object} Vue
|
|
* @param {object} Plugin definitions
|
|
*/
|
|
|
|
var registerPlugins = function registerPlugins(Vue) {
|
|
var plugins = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
for (var plugin in plugins) {
|
|
if (plugin && plugins[plugin]) {
|
|
Vue.use(plugins[plugin]);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Load a component.
|
|
* @param {object} Vue
|
|
* @param {string} Component name
|
|
* @param {object} Component definition
|
|
*/
|
|
|
|
var registerComponent = function registerComponent(Vue, name, def) {
|
|
if (Vue && name && def) {
|
|
Vue.component(name, def);
|
|
}
|
|
};
|
|
/**
|
|
* Load a group of components.
|
|
* @param {object} Vue
|
|
* @param {object} Object of component definitions
|
|
*/
|
|
|
|
var registerComponents = function registerComponents(Vue) {
|
|
var components = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
for (var component in components) {
|
|
registerComponent(Vue, component, components[component]);
|
|
}
|
|
};
|
|
/**
|
|
* Load a directive.
|
|
* @param {object} Vue
|
|
* @param {string} Directive name
|
|
* @param {object} Directive definition
|
|
*/
|
|
|
|
var registerDirective = function registerDirective(Vue, name, def) {
|
|
if (Vue && name && def) {
|
|
// Ensure that any leading V is removed from the
|
|
// name, as Vue adds it automatically
|
|
Vue.directive(name.replace(/^VB/, 'B'), def);
|
|
}
|
|
};
|
|
/**
|
|
* Load a group of directives.
|
|
* @param {object} Vue
|
|
* @param {object} Object of directive definitions
|
|
*/
|
|
|
|
var registerDirectives = function registerDirectives(Vue) {
|
|
var directives = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
for (var directive in directives) {
|
|
registerDirective(Vue, directive, directives[directive]);
|
|
}
|
|
};
|
|
/**
|
|
* Install plugin if window.Vue available
|
|
* @param {object} Plugin definition
|
|
*/
|
|
|
|
var vueUse = function vueUse(VuePlugin) {
|
|
/* istanbul ignore next */
|
|
if (hasWindowSupport && window.Vue) {
|
|
window.Vue.use(VuePlugin);
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
if (hasWindowSupport && VuePlugin.NAME) {
|
|
window[VuePlugin.NAME] = VuePlugin;
|
|
}
|
|
};
|
|
|
|
var NAME_ALERT = 'BAlert';
|
|
var NAME_ASPECT = 'BAspect';
|
|
var NAME_AVATAR = 'BAvatar';
|
|
var NAME_AVATAR_GROUP = 'BAvatarGroup';
|
|
var NAME_BADGE = 'BBadge';
|
|
var NAME_BREADCRUMB = 'BBreadcrumb';
|
|
var NAME_BREADCRUMB_ITEM = 'BBreadcrumbItem';
|
|
var NAME_BREADCRUMB_LINK = 'BBreadcrumbLink';
|
|
var NAME_BUTTON = 'BButton';
|
|
var NAME_BUTTON_CLOSE = 'BButtonClose';
|
|
var NAME_BUTTON_GROUP = 'BButtonGroup';
|
|
var NAME_BUTTON_TOOLBAR = 'BButtonToolbar';
|
|
var NAME_CALENDAR = 'BCalendar';
|
|
var NAME_CARD = 'BCard';
|
|
var NAME_CARD_BODY = 'BCardBody';
|
|
var NAME_CARD_FOOTER = 'BCardFooter';
|
|
var NAME_CARD_GROUP = 'BCardGroup';
|
|
var NAME_CARD_HEADER = 'BCardHeader';
|
|
var NAME_CARD_IMG = 'BCardImg';
|
|
var NAME_CARD_IMG_LAZY = 'BCardImgLazy';
|
|
var NAME_CARD_SUB_TITLE = 'BCardSubTitle';
|
|
var NAME_CARD_TEXT = 'BCardText';
|
|
var NAME_CARD_TITLE = 'BCardTitle';
|
|
var NAME_CAROUSEL = 'BCarousel';
|
|
var NAME_CAROUSEL_SLIDE = 'BCarouselSlide';
|
|
var NAME_COL = 'BCol';
|
|
var NAME_COLLAPSE = 'BCollapse';
|
|
var NAME_COLLAPSE_HELPER = 'BVCollapse';
|
|
var NAME_CONTAINER = 'BContainer';
|
|
var NAME_DROPDOWN = 'BDropdown';
|
|
var NAME_DROPDOWN_DIVIDER = 'BDropdownDivider';
|
|
var NAME_DROPDOWN_FORM = 'BDropdownForm';
|
|
var NAME_DROPDOWN_GROUP = 'BDropdownGroup';
|
|
var NAME_DROPDOWN_HEADER = 'BDropdownHeader';
|
|
var NAME_DROPDOWN_ITEM = 'BDropdownItem';
|
|
var NAME_DROPDOWN_ITEM_BUTTON = 'BDropdownItemButton';
|
|
var NAME_DROPDOWN_TEXT = 'BDropdownText';
|
|
var NAME_EMBED = 'BEmbed';
|
|
var NAME_FORM = 'BForm';
|
|
var NAME_FORM_BUTTON_LABEL_CONTROL = 'BVFormBtnLabelControl';
|
|
var NAME_FORM_CHECKBOX = 'BFormCheckbox';
|
|
var NAME_FORM_CHECKBOX_GROUP = 'BFormCheckboxGroup';
|
|
var NAME_FORM_DATALIST = 'BFormDatalist';
|
|
var NAME_FORM_DATEPICKER = 'BFormDatepicker';
|
|
var NAME_FORM_FILE = 'BFormFile';
|
|
var NAME_FORM_GROUP = 'BFormGroup';
|
|
var NAME_FORM_INPUT = 'BFormInput';
|
|
var NAME_FORM_INVALID_FEEDBACK = 'BFormInvalidFeedback';
|
|
var NAME_FORM_RADIO = 'BFormRadio';
|
|
var NAME_FORM_RADIO_GROUP = 'BFormRadioGroup';
|
|
var NAME_FORM_RATING = 'BFormRating';
|
|
var NAME_FORM_RATING_STAR = 'BVFormRatingStar';
|
|
var NAME_FORM_ROW = 'BFormRow';
|
|
var NAME_FORM_SELECT = 'BFormSelect';
|
|
var NAME_FORM_SELECT_OPTION = 'BFormSelectOption';
|
|
var NAME_FORM_SELECT_OPTION_GROUP = 'BFormSelectOptionGroup';
|
|
var NAME_FORM_SPINBUTTON = 'BFormSpinbutton';
|
|
var NAME_FORM_TAG = 'BFormTag';
|
|
var NAME_FORM_TAGS = 'BFormTags';
|
|
var NAME_FORM_TEXT = 'BFormText';
|
|
var NAME_FORM_TEXTAREA = 'BFormTextarea';
|
|
var NAME_FORM_TIMEPICKER = 'BFormTimepicker';
|
|
var NAME_FORM_VALID_FEEDBACK = 'BFormValidFeedback';
|
|
var NAME_ICON = 'BIcon';
|
|
var NAME_ICONSTACK = 'BIconstack';
|
|
var NAME_ICON_BASE = 'BIconBase';
|
|
var NAME_IMG = 'BImg';
|
|
var NAME_IMG_LAZY = 'BImgLazy';
|
|
var NAME_INPUT_GROUP = 'BInputGroup';
|
|
var NAME_INPUT_GROUP_ADDON = 'BInputGroupAddon';
|
|
var NAME_INPUT_GROUP_APPEND = 'BInputGroupAppend';
|
|
var NAME_INPUT_GROUP_PREPEND = 'BInputGroupPrepend';
|
|
var NAME_INPUT_GROUP_TEXT = 'BInputGroupText';
|
|
var NAME_JUMBOTRON = 'BJumbotron';
|
|
var NAME_LINK = 'BLink';
|
|
var NAME_LIST_GROUP = 'BListGroup';
|
|
var NAME_LIST_GROUP_ITEM = 'BListGroupItem';
|
|
var NAME_MEDIA = 'BMedia';
|
|
var NAME_MEDIA_ASIDE = 'BMediaAside';
|
|
var NAME_MEDIA_BODY = 'BMediaBody';
|
|
var NAME_MODAL = 'BModal';
|
|
var NAME_MSG_BOX = 'BMsgBox';
|
|
var NAME_NAV = 'BNav';
|
|
var NAME_NAVBAR = 'BNavbar';
|
|
var NAME_NAVBAR_BRAND = 'BNavbarBrand';
|
|
var NAME_NAVBAR_NAV = 'BNavbarNav';
|
|
var NAME_NAVBAR_TOGGLE = 'BNavbarToggle';
|
|
var NAME_NAV_FORM = 'BNavForm';
|
|
var NAME_NAV_ITEM = 'BNavItem';
|
|
var NAME_NAV_ITEM_DROPDOWN = 'BNavItemDropdown';
|
|
var NAME_NAV_TEXT = 'BNavText';
|
|
var NAME_OVERLAY = 'BOverlay';
|
|
var NAME_PAGINATION = 'BPagination';
|
|
var NAME_PAGINATION_NAV = 'BPaginationNav';
|
|
var NAME_POPOVER = 'BPopover';
|
|
var NAME_POPOVER_HELPER = 'BVPopover';
|
|
var NAME_POPOVER_TEMPLATE = 'BVPopoverTemplate';
|
|
var NAME_POPPER = 'BVPopper';
|
|
var NAME_PROGRESS = 'BProgress';
|
|
var NAME_PROGRESS_BAR = 'BProgressBar';
|
|
var NAME_ROW = 'BRow';
|
|
var NAME_SIDEBAR = 'BSidebar';
|
|
var NAME_SKELETON = 'BSkeleton';
|
|
var NAME_SKELETON_ICON = 'BSkeletonIcon';
|
|
var NAME_SKELETON_IMG = 'BSkeletonImg';
|
|
var NAME_SKELETON_TABLE = 'BSkeletonTable';
|
|
var NAME_SKELETON_WRAPPER = 'BSkeletonWrapper';
|
|
var NAME_SPINNER = 'BSpinner';
|
|
var NAME_TAB = 'BTab';
|
|
var NAME_TABLE = 'BTable';
|
|
var NAME_TABLE_CELL = 'BTableCell';
|
|
var NAME_TABLE_LITE = 'BTableLite';
|
|
var NAME_TABLE_SIMPLE = 'BTableSimple';
|
|
var NAME_TABS = 'BTabs';
|
|
var NAME_TAB_BUTTON_HELPER = 'BVTabButton';
|
|
var NAME_TBODY = 'BTbody';
|
|
var NAME_TFOOT = 'BTfoot';
|
|
var NAME_TH = 'BTh';
|
|
var NAME_THEAD = 'BThead';
|
|
var NAME_TIME = 'BTime';
|
|
var NAME_TOAST = 'BToast';
|
|
var NAME_TOASTER = 'BToaster';
|
|
var NAME_TOAST_POP = 'BVToastPop';
|
|
var NAME_TOOLTIP = 'BTooltip';
|
|
var NAME_TOOLTIP_HELPER = 'BVTooltip';
|
|
var NAME_TOOLTIP_TEMPLATE = 'BVTooltipTemplate';
|
|
var NAME_TR = 'BTr';
|
|
var NAME_TRANSITION = 'BVTransition';
|
|
var NAME_TRANSPORTER_SINGLE = 'BTransporterSingle';
|
|
var NAME_TRANSPORTER_TARGET_SINGLE = 'BTransporterTargetSingle';
|
|
|
|
var memoize = function memoize(fn) {
|
|
var cache = create(null);
|
|
return function () {
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
var argsKey = JSON.stringify(args);
|
|
return cache[argsKey] = cache[argsKey] || fn.apply(null, args);
|
|
};
|
|
};
|
|
|
|
var VueProto = Vue__default['default'].prototype; // --- Getter methods ---
|
|
|
|
var getConfigValue = function getConfigValue(key) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
|
|
var bvConfig = VueProto[PROP_NAME];
|
|
return bvConfig ? bvConfig.getConfigValue(key, defaultValue) : cloneDeep(defaultValue);
|
|
}; // Method to grab a config value for a particular component
|
|
|
|
var getComponentConfig = function getComponentConfig(key) {
|
|
var propKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
|
|
// Return the particular config value for key if specified,
|
|
// otherwise we return the full config (or an empty object if not found)
|
|
return propKey ? getConfigValue("".concat(key, ".").concat(propKey), defaultValue) : getConfigValue(key, {});
|
|
}; // Get all breakpoint names
|
|
|
|
var getBreakpoints = function getBreakpoints() {
|
|
return getConfigValue('breakpoints', DEFAULT_BREAKPOINT);
|
|
}; // Private method for caching breakpoint names
|
|
|
|
var _getBreakpointsCached = memoize(function () {
|
|
return getBreakpoints();
|
|
}); // Get all breakpoint names (cached)
|
|
|
|
|
|
var getBreakpointsCached = function getBreakpointsCached() {
|
|
return cloneDeep(_getBreakpointsCached());
|
|
}; // Get breakpoints with the smallest breakpoint set as ''
|
|
// Useful for components that create breakpoint specific props
|
|
|
|
var getBreakpointsUpCached = memoize(function () {
|
|
var breakpoints = getBreakpointsCached();
|
|
breakpoints[0] = '';
|
|
return breakpoints;
|
|
}); // Get breakpoints with the largest breakpoint set as ''
|
|
// Replaces the current `default` key of each prop with a `getComponentConfig()`
|
|
// call that falls back to the current default value of the prop
|
|
|
|
var makePropsConfigurable = function makePropsConfigurable(props, componentKey) {
|
|
return keys(props).reduce(function (result, prop) {
|
|
var currentProp = props[prop];
|
|
var defaultValue = currentProp.default;
|
|
result[prop] = _objectSpread2(_objectSpread2({}, cloneDeep(currentProp)), {}, {
|
|
default: function _default() {
|
|
return getComponentConfig(componentKey, prop, isFunction(defaultValue) ? defaultValue() : defaultValue);
|
|
}
|
|
});
|
|
return result;
|
|
}, {});
|
|
};
|
|
|
|
// --- Static ---
|
|
var from = function from() {
|
|
return Array.from.apply(Array, arguments);
|
|
}; // --- Instance ---
|
|
|
|
var arrayIncludes = function arrayIncludes(array, value) {
|
|
return array.indexOf(value) !== -1;
|
|
};
|
|
var concat = function concat() {
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
return Array.prototype.concat.apply([], args);
|
|
}; // --- Utilities ---
|
|
|
|
var createAndFillArray = function createAndFillArray(size, value) {
|
|
return Array(size).fill(value);
|
|
};
|
|
var flatten = function flatten(array) {
|
|
return array.reduce(function (result, item) {
|
|
return result.concat(item);
|
|
}, []);
|
|
};
|
|
var flattenDeep = function flattenDeep(array) {
|
|
return array.reduce(function (result, item) {
|
|
return result.concat(Array.isArray(item) ? flattenDeep(item) : item);
|
|
}, []);
|
|
};
|
|
|
|
// Number utilities
|
|
// Converts a value (string, number, etc.) to an integer number
|
|
// Assumes radix base 10
|
|
var toInteger = function toInteger(value) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : NaN;
|
|
var integer = parseInt(value, 10);
|
|
return isNaN(integer) ? defaultValue : integer;
|
|
}; // Converts a value (string, number, etc.) to a number
|
|
|
|
var toFloat = function toFloat(value) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : NaN;
|
|
var float = parseFloat(value);
|
|
return isNaN(float) ? defaultValue : float;
|
|
}; // Converts a value (string, number, etc.) to a string
|
|
// representation with `precision` digits after the decimal
|
|
// Returns the string 'NaN' if the value cannot be converted
|
|
|
|
var toFixed = function toFixed(val, precision) {
|
|
return toFloat(val).toFixed(toInteger(precision, 0));
|
|
};
|
|
|
|
// String utilities
|
|
// Converts PascalCase or camelCase to kebab-case
|
|
|
|
var kebabCase = function kebabCase(str) {
|
|
return str.replace(RX_HYPHENATE, '-$1').toLowerCase();
|
|
}; // Converts a kebab-case or camelCase string to PascalCase
|
|
|
|
var pascalCase = function pascalCase(str) {
|
|
str = kebabCase(str).replace(RX_UN_KEBAB, function (_, c) {
|
|
return c ? c.toUpperCase() : '';
|
|
});
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}; // Converts a string, including strings in camelCase or snake_case, into Start Case
|
|
// It keeps original single quote and hyphen in the word
|
|
// https://github.com/UrbanCompass/to-start-case
|
|
|
|
var startCase = function startCase(str) {
|
|
return str.replace(RX_UNDERSCORE, ' ').replace(RX_LOWER_UPPER, function (str, $1, $2) {
|
|
return $1 + ' ' + $2;
|
|
}).replace(RX_START_SPACE_WORD, function (str, $1, $2) {
|
|
return $1 + $2.toUpperCase();
|
|
});
|
|
}; // Lowercases the first letter of a string and returns a new string
|
|
|
|
var lowerFirst = function lowerFirst(str) {
|
|
str = isString(str) ? str.trim() : String(str);
|
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
}; // Uppercases the first letter of a string and returns a new string
|
|
|
|
var upperFirst = function upperFirst(str) {
|
|
str = isString(str) ? str.trim() : String(str);
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}; // Escape characters to be used in building a regular expression
|
|
|
|
var escapeRegExp = function escapeRegExp(str) {
|
|
return str.replace(RX_REGEXP_REPLACE, '\\$&');
|
|
}; // Convert a value to a string that can be rendered
|
|
// `undefined`/`null` will be converted to `''`
|
|
// Plain objects and arrays will be JSON stringified
|
|
|
|
var toString$1 = function toString(val) {
|
|
var spaces = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
|
|
return isUndefinedOrNull(val) ? '' : isArray(val) || isPlainObject(val) && val.toString === Object.prototype.toString ? JSON.stringify(val, null, spaces) : String(val);
|
|
}; // Remove leading white space from a string
|
|
|
|
var trimLeft = function trimLeft(str) {
|
|
return toString$1(str).replace(RX_TRIM_LEFT, '');
|
|
}; // Remove Trailing white space from a string
|
|
|
|
var trim = function trim(str) {
|
|
return toString$1(str).trim();
|
|
}; // Lower case a string
|
|
|
|
var lowerCase = function lowerCase(str) {
|
|
return toString$1(str).toLowerCase();
|
|
}; // Upper case a string
|
|
|
|
var TABABLE_SELECTOR = ['button', '[href]:not(.disabled)', 'input', 'select', 'textarea', '[tabindex]', '[contenteditable]'].map(function (s) {
|
|
return "".concat(s, ":not(:disabled):not([disabled])");
|
|
}).join(', ');
|
|
var w$1 = hasWindowSupport ? window : {};
|
|
var d = hasDocumentSupport ? document : {};
|
|
var elProto = typeof Element !== 'undefined' ? Element.prototype : {}; // --- Normalization utils ---
|
|
// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill
|
|
|
|
/* istanbul ignore next */
|
|
|
|
var matchesEl = elProto.matches || elProto.msMatchesSelector || elProto.webkitMatchesSelector; // See: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
|
|
|
/* istanbul ignore next */
|
|
|
|
var closestEl = elProto.closest || function (sel) {
|
|
var el = this;
|
|
|
|
do {
|
|
// Use our "patched" matches function
|
|
if (matches(el, sel)) {
|
|
return el;
|
|
}
|
|
|
|
el = el.parentElement || el.parentNode;
|
|
} while (!isNull(el) && el.nodeType === Node.ELEMENT_NODE);
|
|
|
|
return null;
|
|
}; // `requestAnimationFrame()` convenience method
|
|
|
|
/* istanbul ignore next: JSDOM always returns the first option */
|
|
|
|
var requestAF = w$1.requestAnimationFrame || w$1.webkitRequestAnimationFrame || w$1.mozRequestAnimationFrame || w$1.msRequestAnimationFrame || w$1.oRequestAnimationFrame || // Fallback, but not a true polyfill
|
|
// Only needed for Opera Mini
|
|
|
|
/* istanbul ignore next */
|
|
function (cb) {
|
|
return setTimeout(cb, 16);
|
|
};
|
|
var MutationObs = w$1.MutationObserver || w$1.WebKitMutationObserver || w$1.MozMutationObserver || null; // --- Utils ---
|
|
// Remove a node from DOM
|
|
|
|
var removeNode = function removeNode(el) {
|
|
return el && el.parentNode && el.parentNode.removeChild(el);
|
|
}; // Determine if an element is an HTML element
|
|
|
|
var isElement = function isElement(el) {
|
|
return !!(el && el.nodeType === Node.ELEMENT_NODE);
|
|
}; // Get the currently active HTML element
|
|
|
|
var getActiveElement = function getActiveElement() {
|
|
var excludes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
var activeElement = d.activeElement;
|
|
return activeElement && !excludes.some(function (el) {
|
|
return el === activeElement;
|
|
}) ? activeElement : null;
|
|
}; // Returns `true` if a tag's name equals `name`
|
|
|
|
var isTag = function isTag(tag, name) {
|
|
return toString$1(tag).toLowerCase() === toString$1(name).toLowerCase();
|
|
}; // Determine if an HTML element is the currently active element
|
|
|
|
var isActiveElement = function isActiveElement(el) {
|
|
return isElement(el) && el === getActiveElement();
|
|
}; // Determine if an HTML element is visible - Faster than CSS check
|
|
|
|
var isVisible = function isVisible(el) {
|
|
if (!isElement(el) || !el.parentNode || !contains(d.body, el)) {
|
|
// Note this can fail for shadow dom elements since they
|
|
// are not a direct descendant of document.body
|
|
return false;
|
|
}
|
|
|
|
if (getStyle(el, 'display') === 'none') {
|
|
// We do this check to help with vue-test-utils when using v-show
|
|
|
|
/* istanbul ignore next */
|
|
return false;
|
|
} // All browsers support getBoundingClientRect(), except JSDOM as it returns all 0's for values :(
|
|
// So any tests that need isVisible will fail in JSDOM
|
|
// Except when we override the getBCR prototype in some tests
|
|
|
|
|
|
var bcr = getBCR(el);
|
|
return !!(bcr && bcr.height > 0 && bcr.width > 0);
|
|
}; // Determine if an element is disabled
|
|
|
|
var isDisabled = function isDisabled(el) {
|
|
return !isElement(el) || el.disabled || hasAttr(el, 'disabled') || hasClass(el, 'disabled');
|
|
}; // Cause/wait-for an element to reflow its content (adjusting its height/width)
|
|
|
|
var reflow = function reflow(el) {
|
|
// Requesting an elements offsetHight will trigger a reflow of the element content
|
|
|
|
/* istanbul ignore next: reflow doesn't happen in JSDOM */
|
|
return isElement(el) && el.offsetHeight;
|
|
}; // Select all elements matching selector. Returns `[]` if none found
|
|
|
|
var selectAll = function selectAll(selector, root) {
|
|
return from((isElement(root) ? root : d).querySelectorAll(selector));
|
|
}; // Select a single element, returns `null` if not found
|
|
|
|
var select = function select(selector, root) {
|
|
return (isElement(root) ? root : d).querySelector(selector) || null;
|
|
}; // Determine if an element matches a selector
|
|
|
|
var matches = function matches(el, selector) {
|
|
return isElement(el) ? matchesEl.call(el, selector) : false;
|
|
}; // Finds closest element matching selector. Returns `null` if not found
|
|
|
|
var closest = function closest(selector, root) {
|
|
var includeRoot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
if (!isElement(root)) {
|
|
return null;
|
|
}
|
|
|
|
var el = closestEl.call(root, selector); // Native closest behaviour when `includeRoot` is truthy,
|
|
// else emulate jQuery closest and return `null` if match is
|
|
// the passed in root element when `includeRoot` is falsey
|
|
|
|
return includeRoot ? el : el === root ? null : el;
|
|
}; // Returns true if the parent element contains the child element
|
|
|
|
var contains = function contains(parent, child) {
|
|
return parent && isFunction(parent.contains) ? parent.contains(child) : false;
|
|
}; // Get an element given an ID
|
|
|
|
var getById = function getById(id) {
|
|
return d.getElementById(/^#/.test(id) ? id.slice(1) : id) || null;
|
|
}; // Add a class to an element
|
|
|
|
var addClass = function addClass(el, className) {
|
|
// We are checking for `el.classList` existence here since IE 11
|
|
// returns `undefined` for some elements (e.g. SVG elements)
|
|
// See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
|
|
if (className && isElement(el) && el.classList) {
|
|
el.classList.add(className);
|
|
}
|
|
}; // Remove a class from an element
|
|
|
|
var removeClass = function removeClass(el, className) {
|
|
// We are checking for `el.classList` existence here since IE 11
|
|
// returns `undefined` for some elements (e.g. SVG elements)
|
|
// See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
|
|
if (className && isElement(el) && el.classList) {
|
|
el.classList.remove(className);
|
|
}
|
|
}; // Test if an element has a class
|
|
|
|
var hasClass = function hasClass(el, className) {
|
|
// We are checking for `el.classList` existence here since IE 11
|
|
// returns `undefined` for some elements (e.g. SVG elements)
|
|
// See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
|
|
if (className && isElement(el) && el.classList) {
|
|
return el.classList.contains(className);
|
|
}
|
|
|
|
return false;
|
|
}; // Set an attribute on an element
|
|
|
|
var setAttr = function setAttr(el, attr, value) {
|
|
if (attr && isElement(el)) {
|
|
el.setAttribute(attr, value);
|
|
}
|
|
}; // Remove an attribute from an element
|
|
|
|
var removeAttr = function removeAttr(el, attr) {
|
|
if (attr && isElement(el)) {
|
|
el.removeAttribute(attr);
|
|
}
|
|
}; // Get an attribute value from an element
|
|
// Returns `null` if not found
|
|
|
|
var getAttr = function getAttr(el, attr) {
|
|
return attr && isElement(el) ? el.getAttribute(attr) : null;
|
|
}; // Determine if an attribute exists on an element
|
|
// Returns `true` or `false`, or `null` if element not found
|
|
|
|
var hasAttr = function hasAttr(el, attr) {
|
|
return attr && isElement(el) ? el.hasAttribute(attr) : null;
|
|
}; // Set an style property on an element
|
|
|
|
var setStyle = function setStyle(el, prop, value) {
|
|
if (prop && isElement(el)) {
|
|
el.style[prop] = value;
|
|
}
|
|
}; // Remove an style property from an element
|
|
|
|
var removeStyle = function removeStyle(el, prop) {
|
|
if (prop && isElement(el)) {
|
|
el.style[prop] = '';
|
|
}
|
|
}; // Get an style property value from an element
|
|
// Returns `null` if not found
|
|
|
|
var getStyle = function getStyle(el, prop) {
|
|
return prop && isElement(el) ? el.style[prop] || null : null;
|
|
}; // Return the Bounding Client Rect of an element
|
|
// Returns `null` if not an element
|
|
|
|
/* istanbul ignore next: getBoundingClientRect() doesn't work in JSDOM */
|
|
|
|
var getBCR = function getBCR(el) {
|
|
return isElement(el) ? el.getBoundingClientRect() : null;
|
|
}; // Get computed style object for an element
|
|
|
|
/* istanbul ignore next: getComputedStyle() doesn't work in JSDOM */
|
|
|
|
var getCS = function getCS(el) {
|
|
return hasWindowSupport && isElement(el) ? w$1.getComputedStyle(el) : {};
|
|
}; // Returns a `Selection` object representing the range of text selected
|
|
// Returns `null` if no window support is given
|
|
|
|
/* istanbul ignore next: getSelection() doesn't work in JSDOM */
|
|
|
|
var getSel = function getSel() {
|
|
return hasWindowSupport && w$1.getSelection ? w$1.getSelection() : null;
|
|
}; // Return an element's offset with respect to document element
|
|
// https://j11y.io/jquery/#v=git&fn=jQuery.fn.offset
|
|
|
|
var offset = function offset(el)
|
|
/* istanbul ignore next: getBoundingClientRect(), getClientRects() doesn't work in JSDOM */
|
|
{
|
|
var _offset = {
|
|
top: 0,
|
|
left: 0
|
|
};
|
|
|
|
if (!isElement(el) || el.getClientRects().length === 0) {
|
|
return _offset;
|
|
}
|
|
|
|
var bcr = getBCR(el);
|
|
|
|
if (bcr) {
|
|
var win = el.ownerDocument.defaultView;
|
|
_offset.top = bcr.top + win.pageYOffset;
|
|
_offset.left = bcr.left + win.pageXOffset;
|
|
}
|
|
|
|
return _offset;
|
|
}; // Return an element's offset with respect to to its offsetParent
|
|
// https://j11y.io/jquery/#v=git&fn=jQuery.fn.position
|
|
|
|
var position = function position(el)
|
|
/* istanbul ignore next: getBoundingClientRect() doesn't work in JSDOM */
|
|
{
|
|
var _offset = {
|
|
top: 0,
|
|
left: 0
|
|
};
|
|
|
|
if (!isElement(el)) {
|
|
return _offset;
|
|
}
|
|
|
|
var parentOffset = {
|
|
top: 0,
|
|
left: 0
|
|
};
|
|
var elStyles = getCS(el);
|
|
|
|
if (elStyles.position === 'fixed') {
|
|
_offset = getBCR(el) || _offset;
|
|
} else {
|
|
_offset = offset(el);
|
|
var doc = el.ownerDocument;
|
|
var offsetParent = el.offsetParent || doc.documentElement;
|
|
|
|
while (offsetParent && (offsetParent === doc.body || offsetParent === doc.documentElement) && getCS(offsetParent).position === 'static') {
|
|
offsetParent = offsetParent.parentNode;
|
|
}
|
|
|
|
if (offsetParent && offsetParent !== el && offsetParent.nodeType === Node.ELEMENT_NODE) {
|
|
parentOffset = offset(offsetParent);
|
|
var offsetParentStyles = getCS(offsetParent);
|
|
parentOffset.top += toFloat(offsetParentStyles.borderTopWidth, 0);
|
|
parentOffset.left += toFloat(offsetParentStyles.borderLeftWidth, 0);
|
|
}
|
|
}
|
|
|
|
return {
|
|
top: _offset.top - parentOffset.top - toFloat(elStyles.marginTop, 0),
|
|
left: _offset.left - parentOffset.left - toFloat(elStyles.marginLeft, 0)
|
|
};
|
|
}; // Find all tabable elements in the given element
|
|
// Assumes users have not used `tabindex` > `0` on elements
|
|
|
|
var getTabables = function getTabables() {
|
|
var rootEl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
|
|
return selectAll(TABABLE_SELECTOR, rootEl).filter(isVisible).filter(function (el) {
|
|
return el.tabIndex > -1 && !el.disabled;
|
|
});
|
|
}; // Attempt to focus an element, and return `true` if successful
|
|
|
|
var attemptFocus = function attemptFocus(el) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
try {
|
|
el.focus(options);
|
|
} catch (_unused) {}
|
|
|
|
return isActiveElement(el);
|
|
}; // Attempt to blur an element, and return `true` if successful
|
|
|
|
var attemptBlur = function attemptBlur(el) {
|
|
try {
|
|
el.blur();
|
|
} catch (_unused2) {}
|
|
|
|
return !isActiveElement(el);
|
|
};
|
|
|
|
var NO_FADE_PROPS = {
|
|
name: '',
|
|
enterClass: '',
|
|
enterActiveClass: '',
|
|
enterToClass: 'show',
|
|
leaveClass: 'show',
|
|
leaveActiveClass: '',
|
|
leaveToClass: ''
|
|
};
|
|
|
|
var FADE_PROPS = _objectSpread2(_objectSpread2({}, NO_FADE_PROPS), {}, {
|
|
enterActiveClass: 'fade',
|
|
leaveActiveClass: 'fade'
|
|
}); // @vue/component
|
|
|
|
|
|
var BVTransition = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TRANSITION,
|
|
functional: true,
|
|
props: {
|
|
noFade: {
|
|
// Only applicable to the built in transition
|
|
// Has no effect if `trans-props` provided
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
appear: {
|
|
// Has no effect if `trans-props` provided
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
mode: {
|
|
// Can be overridden by user supplied trans-props
|
|
type: String // default: undefined
|
|
|
|
},
|
|
// For user supplied transitions (if needed)
|
|
transProps: {
|
|
type: Object,
|
|
default: null
|
|
}
|
|
},
|
|
render: function render(h, _ref) {
|
|
var children = _ref.children,
|
|
data = _ref.data,
|
|
props = _ref.props;
|
|
var transProps = props.transProps;
|
|
|
|
if (!isPlainObject(transProps)) {
|
|
transProps = props.noFade ? NO_FADE_PROPS : FADE_PROPS;
|
|
|
|
if (props.appear) {
|
|
// Default the appear classes to equal the enter classes
|
|
transProps = _objectSpread2(_objectSpread2({}, transProps), {}, {
|
|
appear: true,
|
|
appearClass: transProps.enterClass,
|
|
appearActiveClass: transProps.enterActiveClass,
|
|
appearToClass: transProps.enterToClass
|
|
});
|
|
}
|
|
}
|
|
|
|
transProps = _objectSpread2(_objectSpread2({
|
|
mode: props.mode
|
|
}, transProps), {}, {
|
|
// We always need `css` true
|
|
css: true
|
|
});
|
|
return h('transition', // Any transition event listeners will get merged here
|
|
a(data, {
|
|
props: transProps
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var SLOT_NAME_APPEND = 'append';
|
|
var SLOT_NAME_BUTTON_CONTENT = 'button-content';
|
|
var SLOT_NAME_DEFAULT = 'default';
|
|
var SLOT_NAME_DESCRIPTION = 'description';
|
|
var SLOT_NAME_FIRST = 'first';
|
|
var SLOT_NAME_FOOTER = 'footer';
|
|
var SLOT_NAME_HEADER = 'header';
|
|
var SLOT_NAME_LABEL = 'label';
|
|
var SLOT_NAME_LEAD = 'lead';
|
|
var SLOT_NAME_PREPEND = 'prepend';
|
|
var SLOT_NAME_TEXT = 'text';
|
|
var SLOT_NAME_TITLE = 'title';
|
|
|
|
// In functional components, `slots` is a function so it must be called
|
|
// first before passing to the below methods. `scopedSlots` is always an
|
|
// object and may be undefined (for Vue < 2.6.x)
|
|
|
|
/**
|
|
* Returns true if either scoped or unscoped named slot exists
|
|
*
|
|
* @param {String, Array} name or name[]
|
|
* @param {Object} scopedSlots
|
|
* @param {Object} slots
|
|
* @returns {Array|undefined} VNodes
|
|
*/
|
|
|
|
var hasNormalizedSlot = function hasNormalizedSlot(names) {
|
|
var $scopedSlots = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
var $slots = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
// Ensure names is an array
|
|
names = concat(names).filter(identity); // Returns true if the either a $scopedSlot or $slot exists with the specified name
|
|
|
|
return names.some(function (name) {
|
|
return $scopedSlots[name] || $slots[name];
|
|
});
|
|
};
|
|
/**
|
|
* Returns VNodes for named slot either scoped or unscoped
|
|
*
|
|
* @param {String, Array} name or name[]
|
|
* @param {String} scope
|
|
* @param {Object} scopedSlots
|
|
* @param {Object} slots
|
|
* @returns {Array|undefined} VNodes
|
|
*/
|
|
|
|
|
|
var normalizeSlot = function normalizeSlot(names) {
|
|
var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
var $scopedSlots = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
var $slots = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
// Ensure names is an array
|
|
names = concat(names).filter(identity);
|
|
var slot;
|
|
|
|
for (var i = 0; i < names.length && !slot; i++) {
|
|
var name = names[i];
|
|
slot = $scopedSlots[name] || $slots[name];
|
|
} // Note: in Vue 2.6.x, all named slots are also scoped slots
|
|
|
|
|
|
return isFunction(slot) ? slot(scope) : slot;
|
|
}; // Named exports
|
|
|
|
var normalizeSlotMixin = {
|
|
methods: {
|
|
hasNormalizedSlot: function hasNormalizedSlot$1() {
|
|
var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SLOT_NAME_DEFAULT;
|
|
// Returns true if the either a $scopedSlot or $slot exists with the specified name
|
|
// `name` can be a string name or an array of names
|
|
return hasNormalizedSlot(name, this.$scopedSlots, this.$slots);
|
|
},
|
|
normalizeSlot: function normalizeSlot$1() {
|
|
var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SLOT_NAME_DEFAULT;
|
|
var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
// Returns an array of rendered VNodes if slot found.
|
|
// Returns undefined if not found.
|
|
// `name` can be a string name or an array of names
|
|
var vNodes = normalizeSlot(name, scope, this.$scopedSlots, this.$slots);
|
|
|
|
return vNodes ? concat(vNodes) : vNodes;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Normalize event options based on support of passive option
|
|
// Exported only for testing purposes
|
|
|
|
var parseEventOptions = function parseEventOptions(options) {
|
|
/* istanbul ignore else: can't test in JSDOM, as it supports passive */
|
|
if (hasPassiveEventSupport) {
|
|
return isObject(options) ? options : {
|
|
capture: !!options || false
|
|
};
|
|
} else {
|
|
// Need to translate to actual Boolean value
|
|
return !!(isObject(options) ? options.capture : options);
|
|
}
|
|
}; // Attach an event listener to an element
|
|
|
|
var eventOn = function eventOn(el, evtName, handler, options) {
|
|
if (el && el.addEventListener) {
|
|
el.addEventListener(evtName, handler, parseEventOptions(options));
|
|
}
|
|
}; // Remove an event listener from an element
|
|
|
|
var eventOff = function eventOff(el, evtName, handler, options) {
|
|
if (el && el.removeEventListener) {
|
|
el.removeEventListener(evtName, handler, parseEventOptions(options));
|
|
}
|
|
}; // Utility method to add/remove a event listener based on first argument (boolean)
|
|
// It passes all other arguments to the `eventOn()` or `eventOff` method
|
|
|
|
var eventOnOff = function eventOnOff(on) {
|
|
var method = on ? eventOn : eventOff;
|
|
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
args[_key - 1] = arguments[_key];
|
|
}
|
|
|
|
method.apply(void 0, args);
|
|
}; // Utility method to prevent the default event handling and propagation
|
|
|
|
var stopEvent = function stopEvent(evt) {
|
|
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
_ref$preventDefault = _ref.preventDefault,
|
|
preventDefault = _ref$preventDefault === void 0 ? true : _ref$preventDefault,
|
|
_ref$propagation = _ref.propagation,
|
|
propagation = _ref$propagation === void 0 ? true : _ref$propagation,
|
|
_ref$immediatePropaga = _ref.immediatePropagation,
|
|
immediatePropagation = _ref$immediatePropaga === void 0 ? false : _ref$immediatePropaga;
|
|
|
|
if (preventDefault) {
|
|
evt.preventDefault();
|
|
}
|
|
|
|
if (propagation) {
|
|
evt.stopPropagation();
|
|
}
|
|
|
|
if (immediatePropagation) {
|
|
evt.stopImmediatePropagation();
|
|
}
|
|
};
|
|
|
|
var props = makePropsConfigurable({
|
|
content: {
|
|
type: String,
|
|
default: '×'
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaLabel: {
|
|
type: String,
|
|
default: 'Close'
|
|
},
|
|
textVariant: {
|
|
type: String // `textVariant` is `undefined` to inherit the current text color
|
|
// default: undefined
|
|
|
|
}
|
|
}, NAME_BUTTON_CLOSE); // @vue/component
|
|
|
|
var BButtonClose = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BUTTON_CLOSE,
|
|
functional: true,
|
|
props: props,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var $slots = slots();
|
|
var $scopedSlots = scopedSlots || {};
|
|
var componentData = {
|
|
staticClass: 'close',
|
|
class: _defineProperty({}, "text-".concat(props.textVariant), props.textVariant),
|
|
attrs: {
|
|
type: 'button',
|
|
disabled: props.disabled,
|
|
'aria-label': props.ariaLabel ? String(props.ariaLabel) : null
|
|
},
|
|
on: {
|
|
click: function click(evt) {
|
|
// Ensure click on button HTML content is also disabled
|
|
|
|
/* istanbul ignore if: bug in JSDOM still emits click on inner element */
|
|
if (props.disabled && isEvent(evt)) {
|
|
stopEvent(evt);
|
|
}
|
|
}
|
|
}
|
|
}; // Careful not to override the default slot with innerHTML
|
|
|
|
if (!hasNormalizedSlot(SLOT_NAME_DEFAULT, $scopedSlots, $slots)) {
|
|
componentData.domProps = {
|
|
innerHTML: props.content
|
|
};
|
|
}
|
|
|
|
return h('button', a(data, componentData), normalizeSlot(SLOT_NAME_DEFAULT, {}, $scopedSlots, $slots));
|
|
}
|
|
});
|
|
|
|
var parseCountDown = function parseCountDown(show) {
|
|
if (show === '' || isBoolean(show)) {
|
|
return 0;
|
|
}
|
|
|
|
show = toInteger(show, 0);
|
|
return show > 0 ? show : 0;
|
|
}; // Convert `show` value to a boolean
|
|
|
|
|
|
var parseShow = function parseShow(show) {
|
|
if (show === '' || show === true) {
|
|
return true;
|
|
}
|
|
|
|
if (toInteger(show, 0) < 1) {
|
|
// Boolean will always return false for the above comparison
|
|
return false;
|
|
}
|
|
|
|
return !!show;
|
|
}; // @vue/component
|
|
|
|
|
|
var BAlert = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_ALERT,
|
|
mixins: [normalizeSlotMixin],
|
|
model: {
|
|
prop: 'show',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable({
|
|
variant: {
|
|
type: String,
|
|
default: 'info'
|
|
},
|
|
dismissible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dismissLabel: {
|
|
type: String,
|
|
default: 'Close'
|
|
},
|
|
show: {
|
|
type: [Boolean, Number, String],
|
|
default: false
|
|
},
|
|
fade: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_ALERT),
|
|
data: function data() {
|
|
return {
|
|
countDown: 0,
|
|
// If initially shown, we need to set these for SSR
|
|
localShow: parseShow(this.show)
|
|
};
|
|
},
|
|
watch: {
|
|
show: function show(newVal) {
|
|
this.countDown = parseCountDown(newVal);
|
|
this.localShow = parseShow(newVal);
|
|
},
|
|
countDown: function countDown(newVal) {
|
|
var _this = this;
|
|
|
|
this.clearCountDownInterval();
|
|
|
|
if (isNumeric(this.show)) {
|
|
// Ignore if this.show transitions to a boolean value.
|
|
this.$emit('dismiss-count-down', newVal);
|
|
|
|
if (this.show !== newVal) {
|
|
// Update the v-model if needed
|
|
this.$emit('input', newVal);
|
|
}
|
|
|
|
if (newVal > 0) {
|
|
this.localShow = true;
|
|
this.$_countDownTimeout = setTimeout(function () {
|
|
_this.countDown--;
|
|
}, 1000);
|
|
} else {
|
|
// Slightly delay the hide to allow any UI updates
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this.localShow = false;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
localShow: function localShow(newVal) {
|
|
if (!newVal && (this.dismissible || isNumeric(this.show))) {
|
|
// Only emit dismissed events for dismissible or auto dismissing alerts
|
|
this.$emit('dismissed');
|
|
}
|
|
|
|
if (!isNumeric(this.show) && this.show !== newVal) {
|
|
// Only emit booleans if we weren't passed a number via `this.show`
|
|
this.$emit('input', newVal);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_filterTimer = null;
|
|
this.countDown = parseCountDown(this.show);
|
|
this.localShow = parseShow(this.show);
|
|
},
|
|
mounted: function mounted() {
|
|
this.countDown = parseCountDown(this.show);
|
|
this.localShow = parseShow(this.show);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.clearCountDownInterval();
|
|
},
|
|
methods: {
|
|
dismiss: function dismiss() {
|
|
this.clearCountDownInterval();
|
|
this.countDown = 0;
|
|
this.localShow = false;
|
|
},
|
|
clearCountDownInterval: function clearCountDownInterval() {
|
|
clearTimeout(this.$_countDownTimeout);
|
|
this.$_countDownTimeout = null;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $alert; // undefined
|
|
|
|
if (this.localShow) {
|
|
var $dismissBtn = h();
|
|
|
|
if (this.dismissible) {
|
|
// Add dismiss button
|
|
$dismissBtn = h(BButtonClose, {
|
|
attrs: {
|
|
'aria-label': this.dismissLabel
|
|
},
|
|
on: {
|
|
click: this.dismiss
|
|
}
|
|
}, [this.normalizeSlot('dismiss')]);
|
|
}
|
|
|
|
$alert = h('div', {
|
|
key: this._uid,
|
|
staticClass: 'alert',
|
|
class: _defineProperty({
|
|
'alert-dismissible': this.dismissible
|
|
}, "alert-".concat(this.variant), this.variant),
|
|
attrs: {
|
|
role: 'alert',
|
|
'aria-live': 'polite',
|
|
'aria-atomic': true
|
|
}
|
|
}, [$dismissBtn, this.normalizeSlot()]);
|
|
$alert = [$alert];
|
|
}
|
|
|
|
return h(BVTransition, {
|
|
props: {
|
|
noFade: !this.fade
|
|
}
|
|
}, $alert);
|
|
}
|
|
});
|
|
|
|
var AlertPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BAlert: BAlert
|
|
}
|
|
});
|
|
|
|
// Math utilty functions
|
|
var mathMin = Math.min;
|
|
var mathMax = Math.max;
|
|
var mathAbs = Math.abs;
|
|
var mathCeil = Math.ceil;
|
|
var mathFloor = Math.floor;
|
|
var mathPow = Math.pow;
|
|
var mathRound = Math.round;
|
|
|
|
var CLASS_NAME = 'b-aspect'; // --- Main Component ---
|
|
|
|
var BAspect = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_ASPECT,
|
|
mixins: [normalizeSlotMixin],
|
|
props: makePropsConfigurable({
|
|
aspect: {
|
|
// Accepts a number (i.e. `16 / 9`, `1`, `4 / 3`)
|
|
// Or a string (i.e. '16/9', '16:9', '4:3' '1:1')
|
|
type: [Number, String],
|
|
default: '1:1'
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_ASPECT),
|
|
computed: {
|
|
padding: function padding() {
|
|
var aspect = this.aspect;
|
|
var ratio = 1;
|
|
|
|
if (RX_ASPECT.test(aspect)) {
|
|
// Width and/or Height can be a decimal value below `1`, so
|
|
// we only fallback to `1` if the value is `0` or `NaN`
|
|
var _aspect$split$map = aspect.split(RX_ASPECT_SEPARATOR).map(function (v) {
|
|
return toFloat(v) || 1;
|
|
}),
|
|
_aspect$split$map2 = _slicedToArray(_aspect$split$map, 2),
|
|
width = _aspect$split$map2[0],
|
|
height = _aspect$split$map2[1];
|
|
|
|
ratio = width / height;
|
|
} else {
|
|
ratio = toFloat(aspect) || 1;
|
|
}
|
|
|
|
return "".concat(100 / mathAbs(ratio), "%");
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $sizer = h('div', {
|
|
staticClass: "".concat(CLASS_NAME, "-sizer flex-grow-1"),
|
|
style: {
|
|
paddingBottom: this.padding,
|
|
height: 0
|
|
}
|
|
});
|
|
var $content = h('div', {
|
|
staticClass: "".concat(CLASS_NAME, "-content flex-grow-1 w-100 mw-100"),
|
|
style: {
|
|
marginLeft: '-100%'
|
|
}
|
|
}, [this.normalizeSlot()]);
|
|
return h(this.tag, {
|
|
staticClass: "".concat(CLASS_NAME, " d-flex")
|
|
}, [$sizer, $content]);
|
|
}
|
|
});
|
|
|
|
var AspectPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BAspect: BAspect
|
|
}
|
|
});
|
|
|
|
var prefixPropName = function prefixPropName(prefix, value) {
|
|
return prefix + upperFirst(value);
|
|
}; // Remove a prefix from a property
|
|
|
|
var unprefixPropName = function unprefixPropName(prefix, value) {
|
|
return lowerFirst(value.replace(prefix, ''));
|
|
}; // Suffix can be a falsey value so nothing is appended to string
|
|
// (helps when looping over props & some shouldn't change)
|
|
// Use data last parameters to allow for currying
|
|
|
|
var suffixPropName = function suffixPropName(suffix, str) {
|
|
return str + (suffix ? upperFirst(suffix) : '');
|
|
}; // Copies props from one array/object to a new array/object
|
|
// Prop values are also cloned as new references to prevent possible
|
|
// mutation of original prop object values
|
|
// Optionally accepts a function to transform the prop name
|
|
|
|
var copyProps = function copyProps(props) {
|
|
var transformFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : identity;
|
|
|
|
if (isArray(props)) {
|
|
return props.map(transformFn);
|
|
}
|
|
|
|
var copied = {};
|
|
|
|
for (var prop in props) {
|
|
/* istanbul ignore else */
|
|
if (hasOwnProperty(props, prop)) {
|
|
// If the prop value is an object, do a shallow clone
|
|
// to prevent potential mutations to the original object
|
|
copied[transformFn(prop)] = isObject(props[prop]) ? clone(props[prop]) : props[prop];
|
|
}
|
|
}
|
|
|
|
return copied;
|
|
}; // Given an array of properties or an object of property keys,
|
|
// plucks all the values off the target object, returning a new object
|
|
// that has props that reference the original prop values
|
|
|
|
var pluckProps = function pluckProps(keysToPluck, objToPluck) {
|
|
var transformFn = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : identity;
|
|
return (isArray(keysToPluck) ? keysToPluck.slice() : keys(keysToPluck)).reduce(function (memo, prop) {
|
|
memo[transformFn(prop)] = objToPluck[prop];
|
|
return memo;
|
|
}, {});
|
|
};
|
|
|
|
var ANCHOR_TAG = 'a'; // Method to replace reserved chars
|
|
|
|
var encodeReserveReplacer = function encodeReserveReplacer(c) {
|
|
return '%' + c.charCodeAt(0).toString(16);
|
|
}; // Fixed encodeURIComponent which is more conformant to RFC3986:
|
|
// - escapes [!'()*]
|
|
// - preserve commas
|
|
|
|
|
|
var encode = function encode(str) {
|
|
return encodeURIComponent(toString$1(str)).replace(RX_ENCODE_REVERSE, encodeReserveReplacer).replace(RX_ENCODED_COMMA, ',');
|
|
};
|
|
|
|
var decode = decodeURIComponent; // Stringifies an object of query parameters
|
|
// See: https://github.com/vuejs/vue-router/blob/dev/src/util/query.js
|
|
|
|
var stringifyQueryObj = function stringifyQueryObj(obj) {
|
|
if (!isPlainObject(obj)) {
|
|
return '';
|
|
}
|
|
|
|
var query = keys(obj).map(function (key) {
|
|
var val = obj[key];
|
|
|
|
if (isUndefined(val)) {
|
|
return '';
|
|
} else if (isNull(val)) {
|
|
return encode(key);
|
|
} else if (isArray(val)) {
|
|
return val.reduce(function (results, val2) {
|
|
if (isNull(val2)) {
|
|
results.push(encode(key));
|
|
} else if (!isUndefined(val2)) {
|
|
// Faster than string interpolation
|
|
results.push(encode(key) + '=' + encode(val2));
|
|
}
|
|
|
|
return results;
|
|
}, []).join('&');
|
|
} // Faster than string interpolation
|
|
|
|
|
|
return encode(key) + '=' + encode(val);
|
|
})
|
|
/* must check for length, as we only want to filter empty strings, not things that look falsey! */
|
|
.filter(function (x) {
|
|
return x.length > 0;
|
|
}).join('&');
|
|
return query ? "?".concat(query) : '';
|
|
};
|
|
var parseQuery = function parseQuery(query) {
|
|
var parsed = {};
|
|
query = toString$1(query).trim().replace(RX_QUERY_START, '');
|
|
|
|
if (!query) {
|
|
return parsed;
|
|
}
|
|
|
|
query.split('&').forEach(function (param) {
|
|
var parts = param.replace(RX_PLUS, ' ').split('=');
|
|
var key = decode(parts.shift());
|
|
var val = parts.length > 0 ? decode(parts.join('=')) : null;
|
|
|
|
if (isUndefined(parsed[key])) {
|
|
parsed[key] = val;
|
|
} else if (isArray(parsed[key])) {
|
|
parsed[key].push(val);
|
|
} else {
|
|
parsed[key] = [parsed[key], val];
|
|
}
|
|
});
|
|
return parsed;
|
|
};
|
|
var isLink = function isLink(props) {
|
|
return !!(props.href || props.to);
|
|
};
|
|
var isRouterLink = function isRouterLink(tag) {
|
|
return !!(tag && !isTag(tag, 'a'));
|
|
};
|
|
var computeTag = function computeTag(_ref, thisOrParent) {
|
|
var to = _ref.to,
|
|
disabled = _ref.disabled,
|
|
routerComponentName = _ref.routerComponentName;
|
|
var hasRouter = !!thisOrParent.$router;
|
|
|
|
if (!hasRouter || hasRouter && (disabled || !to)) {
|
|
return ANCHOR_TAG;
|
|
} // TODO:
|
|
// Check registered components for existence of user supplied router link component name
|
|
// We would need to check PascalCase, kebab-case, and camelCase versions of name:
|
|
// const name = routerComponentName
|
|
// const names = [name, PascalCase(name), KebabCase(name), CamelCase(name)]
|
|
// exists = names.some(name => !!thisOrParent.$options.components[name])
|
|
// And may want to cache the result for performance or we just let the render fail
|
|
// if the component is not registered
|
|
|
|
|
|
return routerComponentName || (thisOrParent.$nuxt ? 'nuxt-link' : 'router-link');
|
|
};
|
|
var computeRel = function computeRel() {
|
|
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
target = _ref2.target,
|
|
rel = _ref2.rel;
|
|
|
|
return target === '_blank' && isNull(rel) ? 'noopener' : rel || null;
|
|
};
|
|
var computeHref = function computeHref() {
|
|
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
href = _ref3.href,
|
|
to = _ref3.to;
|
|
|
|
var tag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ANCHOR_TAG;
|
|
var fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '#';
|
|
var toFallback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '/';
|
|
|
|
// Return `href` when explicitly provided
|
|
if (href) {
|
|
return href;
|
|
} // We've checked for `$router` in `computeTag()`, so `isRouterLink()` indicates a live router
|
|
// When deferring to Vue Router's `<router-link>`, don't use the `href` attribute at all
|
|
// We return `null`, and then remove `href` from the attributes passed to `<router-link>`
|
|
|
|
|
|
if (isRouterLink(tag)) {
|
|
return null;
|
|
} // Fallback to `to` prop (if `to` is a string)
|
|
|
|
|
|
if (isString(to)) {
|
|
return to || toFallback;
|
|
} // Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object)
|
|
|
|
|
|
if (isPlainObject(to) && (to.path || to.query || to.hash)) {
|
|
var path = toString$1(to.path);
|
|
var query = stringifyQueryObj(to.query);
|
|
var hash = toString$1(to.hash);
|
|
hash = !hash || hash.charAt(0) === '#' ? hash : "#".concat(hash);
|
|
return "".concat(path).concat(query).concat(hash) || toFallback;
|
|
} // If nothing is provided return the fallback
|
|
|
|
|
|
return fallback;
|
|
};
|
|
|
|
var CODE_BACKSPACE = 8;
|
|
var CODE_DELETE = 46;
|
|
var CODE_DOWN = 40;
|
|
var CODE_END = 35;
|
|
var CODE_ENTER = 13;
|
|
var CODE_ESC = 27;
|
|
var CODE_HOME = 36;
|
|
var CODE_LEFT = 37;
|
|
var CODE_PAGEDOWN = 34;
|
|
var CODE_PAGEUP = 33;
|
|
var CODE_RIGHT = 39;
|
|
var CODE_SPACE = 32;
|
|
var CODE_UP = 38;
|
|
|
|
// Handles when arrays are "sparse" (array.every(...) doesn't handle sparse)
|
|
|
|
var compareArrays = function compareArrays(a, b) {
|
|
if (a.length !== b.length) {
|
|
return false;
|
|
}
|
|
|
|
var equal = true;
|
|
|
|
for (var i = 0; equal && i < a.length; i++) {
|
|
equal = looseEqual(a[i], b[i]);
|
|
}
|
|
|
|
return equal;
|
|
};
|
|
/**
|
|
* Check if two values are loosely equal - that is,
|
|
* if they are plain objects, do they have the same shape?
|
|
* Returns boolean true or false
|
|
*/
|
|
|
|
|
|
var looseEqual = function looseEqual(a, b) {
|
|
if (a === b) {
|
|
return true;
|
|
}
|
|
|
|
var aValidType = isDate(a);
|
|
var bValidType = isDate(b);
|
|
|
|
if (aValidType || bValidType) {
|
|
return aValidType && bValidType ? a.getTime() === b.getTime() : false;
|
|
}
|
|
|
|
aValidType = isArray(a);
|
|
bValidType = isArray(b);
|
|
|
|
if (aValidType || bValidType) {
|
|
return aValidType && bValidType ? compareArrays(a, b) : false;
|
|
}
|
|
|
|
aValidType = isObject(a);
|
|
bValidType = isObject(b);
|
|
|
|
if (aValidType || bValidType) {
|
|
/* istanbul ignore if: this if will probably never be called */
|
|
if (!aValidType || !bValidType) {
|
|
return false;
|
|
}
|
|
|
|
var aKeysCount = keys(a).length;
|
|
var bKeysCount = keys(b).length;
|
|
|
|
if (aKeysCount !== bKeysCount) {
|
|
return false;
|
|
}
|
|
|
|
for (var key in a) {
|
|
var aHasKey = hasOwnProperty(a, key);
|
|
var bHasKey = hasOwnProperty(b, key);
|
|
|
|
if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return String(a) === String(b);
|
|
};
|
|
|
|
var isEmpty = function isEmpty(value) {
|
|
return !value || keys(value).length === 0;
|
|
};
|
|
|
|
var makePropWatcher = function makePropWatcher(propName) {
|
|
return {
|
|
handler: function handler(newValue, oldValue) {
|
|
if (looseEqual(newValue, oldValue)) {
|
|
return;
|
|
}
|
|
|
|
if (isEmpty(newValue) || isEmpty(oldValue)) {
|
|
this[propName] = cloneDeep(newValue);
|
|
return;
|
|
}
|
|
|
|
for (var key in oldValue) {
|
|
if (!hasOwnProperty(newValue, key)) {
|
|
this.$delete(this.$data[propName], key);
|
|
}
|
|
}
|
|
|
|
for (var _key in newValue) {
|
|
this.$set(this.$data[propName], _key, newValue[_key]);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
var makePropCacheMixin = function makePropCacheMixin(propName, proxyPropName) {
|
|
return {
|
|
data: function data() {
|
|
return _defineProperty({}, proxyPropName, cloneDeep(this[propName]));
|
|
},
|
|
watch: _defineProperty({}, propName, makePropWatcher(proxyPropName))
|
|
};
|
|
};
|
|
|
|
var attrsMixin = makePropCacheMixin('$attrs', 'bvAttrs');
|
|
|
|
var listenersMixin = makePropCacheMixin('$listeners', 'bvListeners');
|
|
|
|
// <router-link> specific props
|
|
|
|
var routerLinkProps = {
|
|
to: {
|
|
type: [String, Object],
|
|
default: null
|
|
},
|
|
append: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
replace: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
event: {
|
|
type: [String, Array],
|
|
default: 'click'
|
|
},
|
|
activeClass: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
exact: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
exactActiveClass: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
routerTag: {
|
|
type: String,
|
|
default: 'a'
|
|
}
|
|
}; // <nuxt-link> specific props
|
|
|
|
var nuxtLinkProps = {
|
|
prefetch: {
|
|
type: Boolean,
|
|
// Must be `null` to fall back to the value defined in the
|
|
// `nuxt.config.js` configuration file for `router.prefetchLinks`
|
|
// We convert `null` to `undefined`, so that Nuxt.js will use the
|
|
// compiled default. Vue treats `undefined` as default of `false`
|
|
// for Boolean props, so we must set it as `null` here to be a
|
|
// true tri-state prop
|
|
default: null
|
|
},
|
|
noPrefetch: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
};
|
|
var props$1 = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2({
|
|
href: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
rel: {
|
|
type: String,
|
|
// Must be `null` if no value provided
|
|
default: null
|
|
},
|
|
target: {
|
|
type: String,
|
|
default: '_self'
|
|
},
|
|
active: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, routerLinkProps), nuxtLinkProps), {}, {
|
|
// To support 3rd party router links based on `<router-link>` (i.e. `g-link` for Gridsome)
|
|
// Default is to auto choose between `<router-link>` and `<nuxt-link>`
|
|
// Gridsome doesn't provide a mechanism to auto detect and has caveats
|
|
// such as not supporting FQDN URLs or hash only URLs
|
|
routerComponentName: {
|
|
type: String // default: undefined
|
|
|
|
}
|
|
}), NAME_LINK); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BLink = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_LINK,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
inheritAttrs: false,
|
|
props: props$1,
|
|
computed: {
|
|
computedTag: function computedTag() {
|
|
// We don't pass `this` as the first arg as we need reactivity of the props
|
|
var to = this.to,
|
|
disabled = this.disabled,
|
|
routerComponentName = this.routerComponentName;
|
|
return computeTag({
|
|
to: to,
|
|
disabled: disabled,
|
|
routerComponentName: routerComponentName
|
|
}, this);
|
|
},
|
|
isRouterLink: function isRouterLink$1() {
|
|
return isRouterLink(this.computedTag);
|
|
},
|
|
computedRel: function computedRel() {
|
|
// We don't pass `this` as the first arg as we need reactivity of the props
|
|
var target = this.target,
|
|
rel = this.rel;
|
|
return computeRel({
|
|
target: target,
|
|
rel: rel
|
|
});
|
|
},
|
|
computedHref: function computedHref() {
|
|
// We don't pass `this` as the first arg as we need reactivity of the props
|
|
var to = this.to,
|
|
href = this.href;
|
|
return computeHref({
|
|
to: to,
|
|
href: href
|
|
}, this.computedTag);
|
|
},
|
|
computedProps: function computedProps() {
|
|
var prefetch = this.prefetch;
|
|
return this.isRouterLink ? _objectSpread2(_objectSpread2({}, pluckProps(_objectSpread2(_objectSpread2({}, routerLinkProps), nuxtLinkProps), this)), {}, {
|
|
// Coerce `prefetch` value `null` to be `undefined`
|
|
prefetch: isBoolean(prefetch) ? prefetch : undefined,
|
|
// Pass `router-tag` as `tag` prop
|
|
tag: this.routerTag
|
|
}) : {};
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
var bvAttrs = this.bvAttrs,
|
|
href = this.computedHref,
|
|
rel = this.computedRel,
|
|
disabled = this.disabled,
|
|
target = this.target,
|
|
routerTag = this.routerTag,
|
|
isRouterLink = this.isRouterLink;
|
|
return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, bvAttrs), href ? {
|
|
href: href
|
|
} : {}), isRouterLink && !isTag(routerTag, 'a') ? {} : {
|
|
rel: rel,
|
|
target: target
|
|
}), {}, {
|
|
tabindex: disabled ? '-1' : isUndefined(bvAttrs.tabindex) ? null : bvAttrs.tabindex,
|
|
'aria-disabled': disabled ? 'true' : null
|
|
});
|
|
},
|
|
computedListeners: function computedListeners() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvListeners), {}, {
|
|
// We want to overwrite any click handler since our callback
|
|
// will invoke the user supplied handler(s) if `!this.disabled`
|
|
click: this.onClick
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
onClick: function onClick(evt) {
|
|
var _arguments = arguments;
|
|
var evtIsEvent = isEvent(evt);
|
|
var isRouterLink = this.isRouterLink;
|
|
var suppliedHandler = this.bvListeners.click;
|
|
|
|
if (evtIsEvent && this.disabled) {
|
|
// Stop event from bubbling up
|
|
// Kill the event loop attached to this specific `EventTarget`
|
|
// Needed to prevent `vue-router` for doing its thing
|
|
stopEvent(evt, {
|
|
immediatePropagation: true
|
|
});
|
|
} else {
|
|
/* istanbul ignore next: difficult to test, but we know it works */
|
|
if (isRouterLink && evt.currentTarget.__vue__) {
|
|
// Router links do not emit instance `click` events, so we
|
|
// add in an `$emit('click', evt)` on its Vue instance
|
|
evt.currentTarget.__vue__.$emit('click', evt);
|
|
} // Call the suppliedHandler(s), if any provided
|
|
|
|
|
|
concat(suppliedHandler).filter(function (h) {
|
|
return isFunction(h);
|
|
}).forEach(function (handler) {
|
|
handler.apply(void 0, _toConsumableArray(_arguments));
|
|
}); // Emit the global `$root` click event
|
|
|
|
this.$root.$emit('clicked::link', evt);
|
|
} // Stop scroll-to-top behavior or navigation on
|
|
// regular links when href is just '#'
|
|
|
|
|
|
if (evtIsEvent && !isRouterLink && this.computedHref === '#') {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
}
|
|
},
|
|
focus: function focus() {
|
|
attemptFocus(this.$el);
|
|
},
|
|
blur: function blur() {
|
|
attemptBlur(this.$el);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var active = this.active,
|
|
disabled = this.disabled;
|
|
return h(this.computedTag, _defineProperty({
|
|
class: {
|
|
active: active,
|
|
disabled: disabled
|
|
},
|
|
attrs: this.computedAttrs,
|
|
props: this.computedProps
|
|
}, this.isRouterLink ? 'nativeOn' : 'on', this.computedListeners), this.normalizeSlot());
|
|
}
|
|
});
|
|
|
|
var linkProps = omit(props$1, ['event', 'routerTag']);
|
|
delete linkProps.href.default;
|
|
delete linkProps.to.default;
|
|
var props$2 = makePropsConfigurable(_objectSpread2({
|
|
block: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'button'
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'button'
|
|
},
|
|
pill: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
squared: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
pressed: {
|
|
// Tri-state: `true`, `false` or `null`
|
|
// => On, off, not a toggle
|
|
type: Boolean,
|
|
default: null
|
|
}
|
|
}, linkProps), NAME_BUTTON); // --- Helper methods ---
|
|
// Focus handler for toggle buttons
|
|
// Needs class of 'focus' when focused
|
|
|
|
var handleFocus = function handleFocus(evt) {
|
|
if (evt.type === 'focusin') {
|
|
addClass(evt.target, 'focus');
|
|
} else if (evt.type === 'focusout') {
|
|
removeClass(evt.target, 'focus');
|
|
}
|
|
}; // Is the requested button a link?
|
|
// If tag prop is set to `a`, we use a <b-link> to get proper disabled handling
|
|
|
|
|
|
var isLink$1 = function isLink$1(props) {
|
|
return isLink(props) || isTag(props.tag, 'a');
|
|
}; // Is the button to be a toggle button?
|
|
|
|
|
|
var isToggle = function isToggle(props) {
|
|
return isBoolean(props.pressed);
|
|
}; // Is the button "really" a button?
|
|
|
|
|
|
var isButton = function isButton(props) {
|
|
return !(isLink$1(props) || props.tag && !isTag(props.tag, 'button'));
|
|
}; // Is the requested tag not a button or link?
|
|
|
|
|
|
var isNonStandardTag = function isNonStandardTag(props) {
|
|
return !isLink$1(props) && !isButton(props);
|
|
}; // Compute required classes (non static classes)
|
|
|
|
|
|
var computeClass = function computeClass(props) {
|
|
var _ref;
|
|
|
|
return ["btn-".concat(props.variant || 'secondary'), (_ref = {}, _defineProperty(_ref, "btn-".concat(props.size), props.size), _defineProperty(_ref, 'btn-block', props.block), _defineProperty(_ref, 'rounded-pill', props.pill), _defineProperty(_ref, 'rounded-0', props.squared && !props.pill), _defineProperty(_ref, "disabled", props.disabled), _defineProperty(_ref, "active", props.pressed), _ref)];
|
|
}; // Compute the link props to pass to b-link (if required)
|
|
|
|
|
|
var computeLinkProps = function computeLinkProps(props) {
|
|
return isLink$1(props) ? pluckProps(linkProps, props) : {};
|
|
}; // Compute the attributes for a button
|
|
|
|
|
|
var computeAttrs = function computeAttrs(props, data) {
|
|
var button = isButton(props);
|
|
var link = isLink$1(props);
|
|
var toggle = isToggle(props);
|
|
var nonStandardTag = isNonStandardTag(props);
|
|
var hashLink = link && props.href === '#';
|
|
var role = data.attrs && data.attrs.role ? data.attrs.role : null;
|
|
var tabindex = data.attrs ? data.attrs.tabindex : null;
|
|
|
|
if (nonStandardTag || hashLink) {
|
|
tabindex = '0';
|
|
}
|
|
|
|
return {
|
|
// Type only used for "real" buttons
|
|
type: button && !link ? props.type : null,
|
|
// Disabled only set on "real" buttons
|
|
disabled: button ? props.disabled : null,
|
|
// We add a role of button when the tag is not a link or button for ARIA
|
|
// Don't bork any role provided in `data.attrs` when `isLink` or `isButton`
|
|
// Except when link has `href` of `#`
|
|
role: nonStandardTag || hashLink ? 'button' : role,
|
|
// We set the `aria-disabled` state for non-standard tags
|
|
'aria-disabled': nonStandardTag ? String(props.disabled) : null,
|
|
// For toggles, we need to set the pressed state for ARIA
|
|
'aria-pressed': toggle ? String(props.pressed) : null,
|
|
// `autocomplete="off"` is needed in toggle mode to prevent some browsers
|
|
// from remembering the previous setting when using the back button
|
|
autocomplete: toggle ? 'off' : null,
|
|
// `tabindex` is used when the component is not a button
|
|
// Links are tabbable, but don't allow disabled, while non buttons or links
|
|
// are not tabbable, so we mimic that functionality by disabling tabbing
|
|
// when disabled, and adding a `tabindex="0"` to non buttons or non links
|
|
tabindex: props.disabled && !button ? '-1' : tabindex
|
|
};
|
|
}; // --- Main component ---
|
|
// @vue/component
|
|
|
|
|
|
var BButton = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BUTTON,
|
|
functional: true,
|
|
props: props$2,
|
|
render: function render(h, _ref2) {
|
|
var props = _ref2.props,
|
|
data = _ref2.data,
|
|
listeners = _ref2.listeners,
|
|
children = _ref2.children;
|
|
var toggle = isToggle(props);
|
|
var link = isLink$1(props);
|
|
var nonStandardTag = isNonStandardTag(props);
|
|
var hashLink = link && props.href === '#';
|
|
var on = {
|
|
keydown: function keydown(evt) {
|
|
// When the link is a `href="#"` or a non-standard tag (has `role="button"`),
|
|
// we add a keydown handlers for CODE_SPACE/CODE_ENTER
|
|
|
|
/* istanbul ignore next */
|
|
if (props.disabled || !(nonStandardTag || hashLink)) {
|
|
return;
|
|
}
|
|
|
|
var keyCode = evt.keyCode; // Add CODE_SPACE handler for `href="#"` and CODE_ENTER handler for non-standard tags
|
|
|
|
if (keyCode === CODE_SPACE || keyCode === CODE_ENTER && nonStandardTag) {
|
|
var target = evt.currentTarget || evt.target;
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
target.click();
|
|
}
|
|
},
|
|
click: function click(evt) {
|
|
/* istanbul ignore if: blink/button disabled should handle this */
|
|
if (props.disabled && isEvent(evt)) {
|
|
stopEvent(evt);
|
|
} else if (toggle && listeners && listeners['update:pressed']) {
|
|
// Send `.sync` updates to any "pressed" prop (if `.sync` listeners)
|
|
// `concat()` will normalize the value to an array without
|
|
// double wrapping an array value in an array
|
|
concat(listeners['update:pressed']).forEach(function (fn) {
|
|
if (isFunction(fn)) {
|
|
fn(!props.pressed);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
if (toggle) {
|
|
on.focusin = handleFocus;
|
|
on.focusout = handleFocus;
|
|
}
|
|
|
|
var componentData = {
|
|
staticClass: 'btn',
|
|
class: computeClass(props),
|
|
props: computeLinkProps(props),
|
|
attrs: computeAttrs(props, data),
|
|
on: on
|
|
};
|
|
return h(link ? BLink : props.tag, a(data, componentData), children);
|
|
}
|
|
});
|
|
|
|
var commonIconProps = {
|
|
title: {
|
|
type: String // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
fontScale: {
|
|
type: [Number, String],
|
|
default: 1
|
|
},
|
|
scale: {
|
|
type: [Number, String],
|
|
default: 1
|
|
},
|
|
rotate: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
flipH: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
flipV: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
shiftH: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
shiftV: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
animation: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
}; // Base attributes needed on all icons
|
|
|
|
var baseAttrs = {
|
|
viewBox: '0 0 16 16',
|
|
width: '1em',
|
|
height: '1em',
|
|
focusable: 'false',
|
|
role: 'img',
|
|
'aria-label': 'icon'
|
|
}; // Attributes that are nulled out when stacked
|
|
|
|
var stackedAttrs = {
|
|
width: null,
|
|
height: null,
|
|
focusable: null,
|
|
role: null,
|
|
'aria-label': null
|
|
}; // Shared private base component to reduce bundle/runtime size
|
|
// @vue/component
|
|
|
|
var BVIconBase = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_ICON_BASE,
|
|
functional: true,
|
|
props: _objectSpread2({
|
|
content: {
|
|
type: String
|
|
},
|
|
stacked: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, commonIconProps),
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var data = _ref.data,
|
|
props = _ref.props,
|
|
children = _ref.children;
|
|
var fontScale = mathMax(toFloat(props.fontScale, 1), 0) || 1;
|
|
var scale = mathMax(toFloat(props.scale, 1), 0) || 1;
|
|
var rotate = toFloat(props.rotate, 0);
|
|
var shiftH = toFloat(props.shiftH, 0);
|
|
var shiftV = toFloat(props.shiftV, 0);
|
|
var flipH = props.flipH;
|
|
var flipV = props.flipV;
|
|
var animation = props.animation; // Compute the transforms
|
|
// Note that order is important as SVG transforms are applied in order from
|
|
// left to right and we want flipping/scale to occur before rotation
|
|
// Note shifting is applied separately
|
|
// Assumes that the viewbox is `0 0 16 16` (`8 8` is the center)
|
|
|
|
var hasScale = flipH || flipV || scale !== 1;
|
|
var hasTransforms = hasScale || rotate;
|
|
var hasShift = shiftH || shiftV;
|
|
var transforms = [hasTransforms ? 'translate(8 8)' : null, hasScale ? "scale(".concat((flipH ? -1 : 1) * scale, " ").concat((flipV ? -1 : 1) * scale, ")") : null, rotate ? "rotate(".concat(rotate, ")") : null, hasTransforms ? 'translate(-8 -8)' : null].filter(identity); // Handling stacked icons
|
|
|
|
var isStacked = props.stacked;
|
|
var hasContent = !isUndefinedOrNull(props.content); // We wrap the content in a `<g>` for handling the transforms (except shift)
|
|
|
|
var $inner = h('g', {
|
|
attrs: {
|
|
transform: transforms.join(' ') || null
|
|
},
|
|
domProps: hasContent ? {
|
|
innerHTML: props.content || ''
|
|
} : {}
|
|
}, children); // If needed, we wrap in an additional `<g>` in order to handle the shifting
|
|
|
|
if (hasShift) {
|
|
$inner = h('g', {
|
|
attrs: {
|
|
transform: "translate(".concat(16 * shiftH / 16, " ").concat(-16 * shiftV / 16, ")")
|
|
}
|
|
}, [$inner]);
|
|
}
|
|
|
|
if (isStacked) {
|
|
// Wrap in an additional `<g>` for proper
|
|
// animation handling if stacked
|
|
$inner = h('g', {}, [$inner]);
|
|
}
|
|
|
|
var $title = props.title ? h('title', props.title) : null;
|
|
return h('svg', a({
|
|
staticClass: 'b-icon bi',
|
|
class: (_class = {}, _defineProperty(_class, "text-".concat(props.variant), !!props.variant), _defineProperty(_class, "b-icon-animation-".concat(animation), !!animation), _class),
|
|
attrs: baseAttrs,
|
|
style: isStacked ? {} : {
|
|
fontSize: fontScale === 1 ? null : "".concat(fontScale * 100, "%")
|
|
}
|
|
}, // Merge in user supplied data
|
|
data, // If icon is stacked, null out some attrs
|
|
isStacked ? {
|
|
attrs: stackedAttrs
|
|
} : {}, // These cannot be overridden by users
|
|
{
|
|
attrs: {
|
|
xmlns: isStacked ? null : 'http://www.w3.org/2000/svg',
|
|
fill: 'currentColor'
|
|
}
|
|
}), [$title, $inner]);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Icon component generator function
|
|
*
|
|
* @param {string} icon name (minus the leading `BIcon`)
|
|
* @param {string} raw `innerHTML` for SVG
|
|
* @return {VueComponent}
|
|
*/
|
|
|
|
var makeIcon = function makeIcon(name, content) {
|
|
// For performance reason we pre-compute some values, so that
|
|
// they are not computed on each render of the icon component
|
|
var kebabName = kebabCase(name);
|
|
var iconName = "BIcon".concat(pascalCase(name));
|
|
var iconNameClass = "bi-".concat(kebabName);
|
|
var iconTitle = kebabName.replace(/-/g, ' ');
|
|
var svgContent = trim(content || ''); // Return the icon component definition
|
|
|
|
return /*#__PURE__*/Vue__default['default'].extend({
|
|
name: iconName,
|
|
functional: true,
|
|
props: _objectSpread2(_objectSpread2({}, commonIconProps), {}, {
|
|
stacked: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}),
|
|
render: function render(h, _ref) {
|
|
var data = _ref.data,
|
|
props = _ref.props;
|
|
return h(BVIconBase, a( // Defaults
|
|
{
|
|
props: {
|
|
title: iconTitle
|
|
},
|
|
attrs: {
|
|
'aria-label': iconTitle
|
|
}
|
|
}, // User data
|
|
data, // Required data
|
|
{
|
|
staticClass: iconNameClass,
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
content: svgContent
|
|
})
|
|
}));
|
|
}
|
|
});
|
|
};
|
|
|
|
// --- BEGIN AUTO-GENERATED FILE ---
|
|
var BIconBlank=/*#__PURE__*/makeIcon('Blank','');// --- Bootstrap Icons ---
|
|
var BIconCalendar=/*#__PURE__*/makeIcon('Calendar','<path fill-rule="evenodd" d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>');// eslint-disable-next-line
|
|
var BIconCalendarFill=/*#__PURE__*/makeIcon('CalendarFill','<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V5h16V4H0V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5z"/>');// eslint-disable-next-line
|
|
var BIconChevronBarLeft=/*#__PURE__*/makeIcon('ChevronBarLeft','<path fill-rule="evenodd" d="M11.854 3.646a.5.5 0 0 1 0 .708L8.207 8l3.647 3.646a.5.5 0 0 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 0 1 .708 0zM4.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/>');// eslint-disable-next-line
|
|
var BIconChevronDoubleLeft=/*#__PURE__*/makeIcon('ChevronDoubleLeft','<path fill-rule="evenodd" d="M8.354 1.646a.5.5 0 0 1 0 .708L2.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/><path fill-rule="evenodd" d="M12.354 1.646a.5.5 0 0 1 0 .708L6.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>');// eslint-disable-next-line
|
|
var BIconChevronDown=/*#__PURE__*/makeIcon('ChevronDown','<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/>');// eslint-disable-next-line
|
|
var BIconChevronLeft=/*#__PURE__*/makeIcon('ChevronLeft','<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>');// eslint-disable-next-line
|
|
var BIconChevronUp=/*#__PURE__*/makeIcon('ChevronUp','<path fill-rule="evenodd" d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708l6-6z"/>');// eslint-disable-next-line
|
|
var BIconCircleFill=/*#__PURE__*/makeIcon('CircleFill','<circle cx="8" cy="8" r="8"/>');// eslint-disable-next-line
|
|
var BIconClock=/*#__PURE__*/makeIcon('Clock','<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm8-7A8 8 0 1 1 0 8a8 8 0 0 1 16 0z"/><path fill-rule="evenodd" d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5z"/>');// eslint-disable-next-line
|
|
var BIconClockFill=/*#__PURE__*/makeIcon('ClockFill','<path fill-rule="evenodd" d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>');// eslint-disable-next-line
|
|
var BIconDash=/*#__PURE__*/makeIcon('Dash','<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>');// eslint-disable-next-line
|
|
var BIconPersonFill=/*#__PURE__*/makeIcon('PersonFill','<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>');// eslint-disable-next-line
|
|
var BIconPlus=/*#__PURE__*/makeIcon('Plus','<path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>');// eslint-disable-next-line
|
|
var BIconStar=/*#__PURE__*/makeIcon('Star','<path fill-rule="evenodd" d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.523-3.356c.329-.314.158-.888-.283-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767l-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288l1.847-3.658 1.846 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.564.564 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/>');// eslint-disable-next-line
|
|
var BIconStarFill=/*#__PURE__*/makeIcon('StarFill','<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.283.95l-3.523 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>');// eslint-disable-next-line
|
|
var BIconStarHalf=/*#__PURE__*/makeIcon('StarHalf','<path fill-rule="evenodd" d="M5.354 5.119L7.538.792A.516.516 0 0 1 8 .5c.183 0 .366.097.465.292l2.184 4.327 4.898.696A.537.537 0 0 1 16 6.32a.55.55 0 0 1-.17.445l-3.523 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256a.519.519 0 0 1-.146.05c-.341.06-.668-.254-.6-.642l.83-4.73L.173 6.765a.55.55 0 0 1-.171-.403.59.59 0 0 1 .084-.302.513.513 0 0 1 .37-.245l4.898-.696zM8 12.027c.08 0 .16.018.232.056l3.686 1.894-.694-3.957a.564.564 0 0 1 .163-.505l2.906-2.77-4.052-.576a.525.525 0 0 1-.393-.288L8.002 2.223 8 2.226v9.8z"/>');// eslint-disable-next-line
|
|
var BIconX=/*#__PURE__*/makeIcon('X','<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>');// eslint-disable-next-line
|
|
// --- END AUTO-GENERATED FILE ---
|
|
|
|
var findIconComponent = function findIconComponent(ctx, iconName) {
|
|
if (!ctx) {
|
|
return null;
|
|
}
|
|
|
|
var components = (ctx.$options || {}).components;
|
|
var iconComponent = components[iconName];
|
|
return iconComponent || findIconComponent(ctx.$parent, iconName);
|
|
}; // Helper BIcon component
|
|
// Requires the requested icon component to be installed
|
|
|
|
|
|
var BIcon = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_ICON,
|
|
functional: true,
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({
|
|
icon: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
}, commonIconProps), {}, {
|
|
stacked: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_ICON),
|
|
render: function render(h, _ref) {
|
|
var data = _ref.data,
|
|
props = _ref.props,
|
|
parent = _ref.parent;
|
|
var icon = pascalCase(trim(props.icon || '')).replace(RX_ICON_PREFIX, ''); // If parent context exists, we check to see if the icon has been registered
|
|
// either locally in the parent component, or globally at the `$root` level
|
|
// If not registered, we render a blank icon
|
|
|
|
return h(icon ? findIconComponent(parent, "BIcon".concat(icon)) || BIconBlank : BIconBlank, a(data, {
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
icon: null
|
|
})
|
|
}));
|
|
}
|
|
});
|
|
|
|
var CLASS_NAME$1 = 'b-avatar';
|
|
var SIZES = ['sm', null, 'lg'];
|
|
var FONT_SIZE_SCALE = 0.4;
|
|
var BADGE_FONT_SIZE_SCALE = FONT_SIZE_SCALE * 0.7; // --- Utility methods ---
|
|
|
|
var computeSize = function computeSize(value) {
|
|
// Parse to number when value is a float-like string
|
|
value = isString(value) && RX_NUMBER.test(value) ? toFloat(value, 0) : value; // Convert all numbers to pixel values
|
|
|
|
return isNumber(value) ? "".concat(value, "px") : value || null;
|
|
}; // --- Props ---
|
|
|
|
var linkProps$1 = omit(props$1, ['active', 'event', 'routerTag']); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BAvatar = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_AVATAR,
|
|
mixins: [normalizeSlotMixin],
|
|
inject: {
|
|
bvAvatarGroup: {
|
|
default: null
|
|
}
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({
|
|
src: {
|
|
type: String // default: null
|
|
|
|
},
|
|
text: {
|
|
type: String // default: null
|
|
|
|
},
|
|
icon: {
|
|
type: String // default: null
|
|
|
|
},
|
|
alt: {
|
|
type: String,
|
|
default: 'avatar'
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
size: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
square: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
rounded: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
button: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonType: {
|
|
type: String,
|
|
default: 'button'
|
|
},
|
|
badge: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
badgeVariant: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
badgeTop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
badgeLeft: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
badgeOffset: {
|
|
type: String,
|
|
default: '0px'
|
|
}
|
|
}, linkProps$1), {}, {
|
|
ariaLabel: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}), NAME_AVATAR),
|
|
data: function data() {
|
|
return {
|
|
localSrc: this.src || null
|
|
};
|
|
},
|
|
computed: {
|
|
computedSize: function computedSize() {
|
|
// Always use the avatar group size
|
|
var bvAvatarGroup = this.bvAvatarGroup;
|
|
return computeSize(bvAvatarGroup ? bvAvatarGroup.size : this.size);
|
|
},
|
|
computedVariant: function computedVariant() {
|
|
var bvAvatarGroup = this.bvAvatarGroup;
|
|
return bvAvatarGroup && bvAvatarGroup.variant ? bvAvatarGroup.variant : this.variant;
|
|
},
|
|
computedRounded: function computedRounded() {
|
|
var bvAvatarGroup = this.bvAvatarGroup;
|
|
var square = bvAvatarGroup && bvAvatarGroup.square ? true : this.square;
|
|
var rounded = bvAvatarGroup && bvAvatarGroup.rounded ? bvAvatarGroup.rounded : this.rounded;
|
|
return square ? '0' : rounded === '' ? true : rounded || 'circle';
|
|
},
|
|
fontStyle: function fontStyle() {
|
|
var size = this.computedSize;
|
|
var fontSize = SIZES.indexOf(size) === -1 ? "calc(".concat(size, " * ").concat(FONT_SIZE_SCALE, ")") : null;
|
|
return fontSize ? {
|
|
fontSize: fontSize
|
|
} : {};
|
|
},
|
|
marginStyle: function marginStyle() {
|
|
var size = this.computedSize,
|
|
bvAvatarGroup = this.bvAvatarGroup;
|
|
var overlapScale = bvAvatarGroup ? bvAvatarGroup.overlapScale : 0;
|
|
var value = size && overlapScale ? "calc(".concat(size, " * -").concat(overlapScale, ")") : null;
|
|
return value ? {
|
|
marginLeft: value,
|
|
marginRight: value
|
|
} : {};
|
|
},
|
|
badgeStyle: function badgeStyle() {
|
|
var size = this.computedSize,
|
|
badgeTop = this.badgeTop,
|
|
badgeLeft = this.badgeLeft,
|
|
badgeOffset = this.badgeOffset;
|
|
var offset = badgeOffset || '0px';
|
|
return {
|
|
fontSize: SIZES.indexOf(size) === -1 ? "calc(".concat(size, " * ").concat(BADGE_FONT_SIZE_SCALE, " )") : null,
|
|
top: badgeTop ? offset : null,
|
|
bottom: badgeTop ? null : offset,
|
|
left: badgeLeft ? offset : null,
|
|
right: badgeLeft ? null : offset
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
src: function src(newSrc, oldSrc) {
|
|
if (newSrc !== oldSrc) {
|
|
this.localSrc = newSrc || null;
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
onImgError: function onImgError(evt) {
|
|
this.localSrc = null;
|
|
this.$emit('img-error', evt);
|
|
},
|
|
onClick: function onClick(evt) {
|
|
this.$emit('click', evt);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _class2;
|
|
|
|
var variant = this.computedVariant,
|
|
disabled = this.disabled,
|
|
rounded = this.computedRounded,
|
|
icon = this.icon,
|
|
src = this.localSrc,
|
|
text = this.text,
|
|
fontStyle = this.fontStyle,
|
|
marginStyle = this.marginStyle,
|
|
size = this.computedSize,
|
|
button = this.button,
|
|
type = this.buttonType,
|
|
badge = this.badge,
|
|
badgeVariant = this.badgeVariant,
|
|
badgeStyle = this.badgeStyle;
|
|
var link = !button && isLink(this);
|
|
var tag = button ? BButton : link ? BLink : 'span';
|
|
var alt = this.alt;
|
|
var ariaLabel = this.ariaLabel || null;
|
|
var $content = null;
|
|
|
|
if (this.hasNormalizedSlot()) {
|
|
// Default slot overrides props
|
|
$content = h('span', {
|
|
staticClass: 'b-avatar-custom'
|
|
}, [this.normalizeSlot()]);
|
|
} else if (src) {
|
|
$content = h('img', {
|
|
style: variant ? {} : {
|
|
width: '100%',
|
|
height: '100%'
|
|
},
|
|
attrs: {
|
|
src: src,
|
|
alt: alt
|
|
},
|
|
on: {
|
|
error: this.onImgError
|
|
}
|
|
});
|
|
$content = h('span', {
|
|
staticClass: 'b-avatar-img'
|
|
}, [$content]);
|
|
} else if (icon) {
|
|
$content = h(BIcon, {
|
|
props: {
|
|
icon: icon
|
|
},
|
|
attrs: {
|
|
'aria-hidden': 'true',
|
|
alt: alt
|
|
}
|
|
});
|
|
} else if (text) {
|
|
$content = h('span', {
|
|
staticClass: 'b-avatar-text',
|
|
style: fontStyle
|
|
}, [h('span', text)]);
|
|
} else {
|
|
// Fallback default avatar content
|
|
$content = h(BIconPersonFill, {
|
|
attrs: {
|
|
'aria-hidden': 'true',
|
|
alt: alt
|
|
}
|
|
});
|
|
}
|
|
|
|
var $badge = h();
|
|
var hasBadgeSlot = this.hasNormalizedSlot('badge');
|
|
|
|
if (badge || badge === '' || hasBadgeSlot) {
|
|
var badgeText = badge === true ? '' : badge;
|
|
$badge = h('span', {
|
|
staticClass: 'b-avatar-badge',
|
|
class: _defineProperty({}, "badge-".concat(badgeVariant), !!badgeVariant),
|
|
style: badgeStyle
|
|
}, [hasBadgeSlot ? this.normalizeSlot('badge') : badgeText]);
|
|
}
|
|
|
|
var componentData = {
|
|
staticClass: CLASS_NAME$1,
|
|
class: (_class2 = {}, _defineProperty(_class2, "".concat(CLASS_NAME$1, "-").concat(size), size && SIZES.indexOf(size) !== -1), _defineProperty(_class2, "badge-".concat(variant), !button && variant), _defineProperty(_class2, "rounded", rounded === true), _defineProperty(_class2, "rounded-".concat(rounded), rounded && rounded !== true), _defineProperty(_class2, "disabled", disabled), _class2),
|
|
style: _objectSpread2(_objectSpread2({}, marginStyle), {}, {
|
|
width: size,
|
|
height: size
|
|
}),
|
|
attrs: {
|
|
'aria-label': ariaLabel || null
|
|
},
|
|
props: button ? {
|
|
variant: variant,
|
|
disabled: disabled,
|
|
type: type
|
|
} : link ? pluckProps(linkProps$1, this) : {},
|
|
on: button || link ? {
|
|
click: this.onClick
|
|
} : {}
|
|
};
|
|
return h(tag, componentData, [$content, $badge]);
|
|
}
|
|
});
|
|
|
|
// @vue/component
|
|
|
|
var BAvatarGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_AVATAR_GROUP,
|
|
mixins: [normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvAvatarGroup: this
|
|
};
|
|
},
|
|
props: makePropsConfigurable({
|
|
variant: {
|
|
// Child avatars will prefer this variant over their own
|
|
type: String,
|
|
default: null
|
|
},
|
|
size: {
|
|
// Child avatars will always use this over their own size
|
|
type: String // default: null
|
|
|
|
},
|
|
overlap: {
|
|
type: [Number, String],
|
|
default: 0.3
|
|
},
|
|
square: {
|
|
// Child avatars will prefer this prop (if set) over their own
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
rounded: {
|
|
// Child avatars will prefer this prop (if set) over their own
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_AVATAR_GROUP),
|
|
computed: {
|
|
computedSize: function computedSize() {
|
|
return computeSize(this.size);
|
|
},
|
|
overlapScale: function overlapScale() {
|
|
return mathMin(mathMax(toFloat(this.overlap, 0), 0), 1) / 2;
|
|
},
|
|
paddingStyle: function paddingStyle() {
|
|
var value = this.computedSize;
|
|
value = value ? "calc(".concat(value, " * ").concat(this.overlapScale, ")") : null;
|
|
return value ? {
|
|
paddingLeft: value,
|
|
paddingRight: value
|
|
} : {};
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $inner = h('div', {
|
|
staticClass: 'b-avatar-group-inner',
|
|
style: this.paddingStyle
|
|
}, [this.normalizeSlot()]);
|
|
return h(this.tag, {
|
|
staticClass: 'b-avatar-group',
|
|
attrs: {
|
|
role: 'group'
|
|
}
|
|
}, [$inner]);
|
|
}
|
|
});
|
|
|
|
var AvatarPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BAvatar: BAvatar,
|
|
BAvatarGroup: BAvatarGroup
|
|
}
|
|
});
|
|
|
|
var linkProps$2 = omit(props$1, ['event', 'routerTag']);
|
|
delete linkProps$2.href.default;
|
|
delete linkProps$2.to.default;
|
|
var props$3 = makePropsConfigurable(_objectSpread2({
|
|
tag: {
|
|
type: String,
|
|
default: 'span'
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
pill: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, linkProps$2), NAME_BADGE); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BBadge = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BADGE,
|
|
functional: true,
|
|
props: props$3,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var link = isLink(props);
|
|
var tag = link ? BLink : props.tag;
|
|
var componentData = {
|
|
staticClass: 'badge',
|
|
class: [props.variant ? "badge-".concat(props.variant) : 'badge-secondary', {
|
|
'badge-pill': props.pill,
|
|
active: props.active,
|
|
disabled: props.disabled
|
|
}],
|
|
props: link ? pluckProps(linkProps$2, props) : {}
|
|
};
|
|
return h(tag, a(data, componentData), children);
|
|
}
|
|
});
|
|
|
|
var BadgePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BBadge: BBadge
|
|
}
|
|
});
|
|
|
|
var stripTags = function stripTags() {
|
|
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
return String(text).replace(RX_HTML_TAGS, '');
|
|
}; // Generate a `domProps` object for either `innerHTML`, `textContent` or an empty object
|
|
|
|
var htmlOrText = function htmlOrText(innerHTML, textContent) {
|
|
return innerHTML ? {
|
|
innerHTML: innerHTML
|
|
} : textContent ? {
|
|
textContent: textContent
|
|
} : {};
|
|
};
|
|
|
|
var props$4 = makePropsConfigurable(_objectSpread2({
|
|
text: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
html: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
ariaCurrent: {
|
|
type: String,
|
|
default: 'location'
|
|
}
|
|
}, omit(props$1, ['event', 'routerTag'])), NAME_BREADCRUMB_LINK); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BBreadcrumbLink = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BREADCRUMB_LINK,
|
|
functional: true,
|
|
props: props$4,
|
|
render: function render(h, _ref) {
|
|
var suppliedProps = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var active = suppliedProps.active;
|
|
var tag = active ? 'span' : BLink;
|
|
var componentData = {
|
|
attrs: {
|
|
'aria-current': active ? suppliedProps.ariaCurrent : null
|
|
},
|
|
props: pluckProps(props$4, suppliedProps)
|
|
};
|
|
|
|
if (!children) {
|
|
componentData.domProps = htmlOrText(suppliedProps.html, suppliedProps.text);
|
|
}
|
|
|
|
return h(tag, a(data, componentData), children);
|
|
}
|
|
});
|
|
|
|
var BBreadcrumbItem = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BREADCRUMB_ITEM,
|
|
functional: true,
|
|
props: makePropsConfigurable(props$4, NAME_BREADCRUMB_ITEM),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h('li', a(data, {
|
|
staticClass: 'breadcrumb-item',
|
|
class: {
|
|
active: props.active
|
|
}
|
|
}), [h(BBreadcrumbLink, {
|
|
props: props
|
|
}, children)]);
|
|
}
|
|
});
|
|
|
|
var props$5 = makePropsConfigurable({
|
|
items: {
|
|
type: Array,
|
|
default: null
|
|
}
|
|
}, NAME_BREADCRUMB); // @vue/component
|
|
|
|
var BBreadcrumb = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BREADCRUMB,
|
|
functional: true,
|
|
props: props$5,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var childNodes = children; // Build child nodes from items if given.
|
|
|
|
if (isArray(props.items)) {
|
|
var activeDefined = false;
|
|
childNodes = props.items.map(function (item, idx) {
|
|
if (!isObject(item)) {
|
|
item = {
|
|
text: toString$1(item)
|
|
};
|
|
} // Copy the value here so we can normalize it.
|
|
|
|
|
|
var active = item.active;
|
|
|
|
if (active) {
|
|
activeDefined = true;
|
|
}
|
|
|
|
if (!active && !activeDefined) {
|
|
// Auto-detect active by position in list.
|
|
active = idx + 1 === props.items.length;
|
|
}
|
|
|
|
return h(BBreadcrumbItem, {
|
|
props: _objectSpread2(_objectSpread2({}, item), {}, {
|
|
active: active
|
|
})
|
|
});
|
|
});
|
|
}
|
|
|
|
return h('ol', a(data, {
|
|
staticClass: 'breadcrumb'
|
|
}), childNodes);
|
|
}
|
|
});
|
|
|
|
var BreadcrumbPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BBreadcrumb: BBreadcrumb,
|
|
BBreadcrumbItem: BBreadcrumbItem,
|
|
BBreadcrumbLink: BBreadcrumbLink
|
|
}
|
|
});
|
|
|
|
var ButtonPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BButton: BButton,
|
|
BBtn: BButton,
|
|
BButtonClose: BButtonClose,
|
|
BBtnClose: BButtonClose
|
|
}
|
|
});
|
|
|
|
var props$6 = makePropsConfigurable(_objectSpread2({
|
|
vertical: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
ariaRole: {
|
|
type: String,
|
|
default: 'group'
|
|
}
|
|
}, pick(props$2, ['size'])), NAME_BUTTON_GROUP); // @vue/component
|
|
|
|
var BButtonGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BUTTON_GROUP,
|
|
functional: true,
|
|
props: props$6,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
class: _defineProperty({
|
|
'btn-group': !props.vertical,
|
|
'btn-group-vertical': props.vertical
|
|
}, "btn-group-".concat(props.size), props.size),
|
|
attrs: {
|
|
role: props.ariaRole
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var ButtonGroupPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BButtonGroup: BButtonGroup,
|
|
BBtnGroup: BButtonGroup
|
|
}
|
|
});
|
|
|
|
var ITEM_SELECTOR = ['.btn:not(.disabled):not([disabled]):not(.dropdown-item)', '.form-control:not(.disabled):not([disabled])', 'select:not(.disabled):not([disabled])', 'input[type="checkbox"]:not(.disabled)', 'input[type="radio"]:not(.disabled)'].join(','); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BButtonToolbar = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_BUTTON_TOOLBAR,
|
|
mixins: [normalizeSlotMixin],
|
|
props: makePropsConfigurable({
|
|
justify: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
keyNav: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_BUTTON_TOOLBAR),
|
|
mounted: function mounted() {
|
|
// Pre-set the tabindexes if the markup does not include
|
|
// `tabindex="-1"` on the toolbar items
|
|
if (this.keyNav) {
|
|
this.getItems();
|
|
}
|
|
},
|
|
methods: {
|
|
getItems: function getItems() {
|
|
var items = selectAll(ITEM_SELECTOR, this.$el); // Ensure `tabindex="-1"` is set on every item
|
|
|
|
items.forEach(function (item) {
|
|
item.tabIndex = -1;
|
|
});
|
|
return items.filter(function (el) {
|
|
return isVisible(el);
|
|
});
|
|
},
|
|
focusFirst: function focusFirst() {
|
|
var items = this.getItems();
|
|
attemptFocus(items[0]);
|
|
},
|
|
focusPrev: function focusPrev(evt) {
|
|
var items = this.getItems();
|
|
var index = items.indexOf(evt.target);
|
|
|
|
if (index > -1) {
|
|
items = items.slice(0, index).reverse();
|
|
attemptFocus(items[0]);
|
|
}
|
|
},
|
|
focusNext: function focusNext(evt) {
|
|
var items = this.getItems();
|
|
var index = items.indexOf(evt.target);
|
|
|
|
if (index > -1) {
|
|
items = items.slice(index + 1);
|
|
attemptFocus(items[0]);
|
|
}
|
|
},
|
|
focusLast: function focusLast() {
|
|
var items = this.getItems().reverse();
|
|
attemptFocus(items[0]);
|
|
},
|
|
onFocusin: function onFocusin(evt) {
|
|
var $el = this.$el;
|
|
|
|
if (evt.target === $el && !contains($el, evt.relatedTarget)) {
|
|
stopEvent(evt);
|
|
this.focusFirst(evt);
|
|
}
|
|
},
|
|
onKeydown: function onKeydown(evt) {
|
|
var keyCode = evt.keyCode,
|
|
shiftKey = evt.shiftKey;
|
|
|
|
if (keyCode === CODE_UP || keyCode === CODE_LEFT) {
|
|
stopEvent(evt);
|
|
shiftKey ? this.focusFirst(evt) : this.focusPrev(evt);
|
|
} else if (keyCode === CODE_DOWN || keyCode === CODE_RIGHT) {
|
|
stopEvent(evt);
|
|
shiftKey ? this.focusLast(evt) : this.focusNext(evt);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('div', {
|
|
staticClass: 'btn-toolbar',
|
|
class: {
|
|
'justify-content-between': this.justify
|
|
},
|
|
attrs: {
|
|
role: 'toolbar',
|
|
tabindex: this.keyNav ? '0' : null
|
|
},
|
|
on: this.keyNav ? {
|
|
focusin: this.onFocusin,
|
|
keydown: this.onKeydown
|
|
} : {}
|
|
}, [this.normalizeSlot()]);
|
|
}
|
|
});
|
|
|
|
var ButtonToolbarPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BButtonToolbar: BButtonToolbar,
|
|
BBtnToolbar: BButtonToolbar
|
|
}
|
|
});
|
|
|
|
var CALENDAR_GREGORY = 'gregory';
|
|
var CALENDAR_LONG = 'long';
|
|
var CALENDAR_NARROW = 'narrow';
|
|
var CALENDAR_SHORT = 'short';
|
|
var DATE_FORMAT_2_DIGIT = '2-digit';
|
|
var DATE_FORMAT_NUMERIC = 'numeric';
|
|
|
|
// Create or clone a date (`new Date(...)` shortcut)
|
|
|
|
var createDate = function createDate() {
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
return _construct(Date, args);
|
|
}; // Parse a date sting, or Date object, into a Date object (with no time information)
|
|
|
|
var parseYMD = function parseYMD(date) {
|
|
if (isString(date) && RX_DATE.test(date.trim())) {
|
|
var _date$split$map = date.split(RX_DATE_SPLIT).map(function (v) {
|
|
return toInteger(v, 1);
|
|
}),
|
|
_date$split$map2 = _slicedToArray(_date$split$map, 3),
|
|
year = _date$split$map2[0],
|
|
month = _date$split$map2[1],
|
|
day = _date$split$map2[2];
|
|
|
|
return createDate(year, month - 1, day);
|
|
} else if (isDate(date)) {
|
|
return createDate(date.getFullYear(), date.getMonth(), date.getDate());
|
|
}
|
|
|
|
return null;
|
|
}; // Format a date object as `YYYY-MM-DD` format
|
|
|
|
var formatYMD = function formatYMD(date) {
|
|
date = parseYMD(date);
|
|
|
|
if (!date) {
|
|
return null;
|
|
}
|
|
|
|
var year = date.getFullYear();
|
|
var month = "0".concat(date.getMonth() + 1).slice(-2);
|
|
var day = "0".concat(date.getDate()).slice(-2);
|
|
return "".concat(year, "-").concat(month, "-").concat(day);
|
|
}; // Given a locale (or locales), resolve the browser available locale
|
|
|
|
var resolveLocale = function resolveLocale(locales)
|
|
/* istanbul ignore next */
|
|
{
|
|
var calendar = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CALENDAR_GREGORY;
|
|
locales = concat(locales).filter(identity);
|
|
var fmt = new Intl.DateTimeFormat(locales, {
|
|
calendar: calendar
|
|
});
|
|
return fmt.resolvedOptions().locale;
|
|
}; // Create a `Intl.DateTimeFormat` formatter function
|
|
|
|
var createDateFormatter = function createDateFormatter(locale, options)
|
|
/* istanbul ignore next */
|
|
{
|
|
var dtf = new Intl.DateTimeFormat(locale, options);
|
|
return dtf.format;
|
|
}; // Determine if two dates are the same date (ignoring time portion)
|
|
|
|
var datesEqual = function datesEqual(date1, date2) {
|
|
// Returns true of the date portion of two date objects are equal
|
|
// We don't compare the time portion
|
|
return formatYMD(date1) === formatYMD(date2);
|
|
}; // --- Date "math" utility methods (for BCalendar component mainly) ---
|
|
|
|
var firstDateOfMonth = function firstDateOfMonth(date) {
|
|
date = createDate(date);
|
|
date.setDate(1);
|
|
return date;
|
|
};
|
|
var lastDateOfMonth = function lastDateOfMonth(date) {
|
|
date = createDate(date);
|
|
date.setMonth(date.getMonth() + 1);
|
|
date.setDate(0);
|
|
return date;
|
|
};
|
|
var addYears = function addYears(date, numberOfYears) {
|
|
date = createDate(date);
|
|
var month = date.getMonth();
|
|
date.setFullYear(date.getFullYear() + numberOfYears); // Handle Feb 29th for leap years
|
|
|
|
if (date.getMonth() !== month) {
|
|
date.setDate(0);
|
|
}
|
|
|
|
return date;
|
|
};
|
|
var oneMonthAgo = function oneMonthAgo(date) {
|
|
date = createDate(date);
|
|
var month = date.getMonth();
|
|
date.setMonth(month - 1); // Handle when days in month are different
|
|
|
|
if (date.getMonth() === month) {
|
|
date.setDate(0);
|
|
}
|
|
|
|
return date;
|
|
};
|
|
var oneMonthAhead = function oneMonthAhead(date) {
|
|
date = createDate(date);
|
|
var month = date.getMonth();
|
|
date.setMonth(month + 1); // Handle when days in month are different
|
|
|
|
if (date.getMonth() === (month + 2) % 12) {
|
|
date.setDate(0);
|
|
}
|
|
|
|
return date;
|
|
};
|
|
var oneYearAgo = function oneYearAgo(date) {
|
|
return addYears(date, -1);
|
|
};
|
|
var oneYearAhead = function oneYearAhead(date) {
|
|
return addYears(date, 1);
|
|
};
|
|
var oneDecadeAgo = function oneDecadeAgo(date) {
|
|
return addYears(date, -10);
|
|
};
|
|
var oneDecadeAhead = function oneDecadeAhead(date) {
|
|
return addYears(date, 10);
|
|
}; // Helper function to constrain a date between two values
|
|
// Always returns a `Date` object or `null` if no date passed
|
|
|
|
var constrainDate = function constrainDate(date) {
|
|
var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
// Ensure values are `Date` objects (or `null`)
|
|
date = parseYMD(date);
|
|
min = parseYMD(min) || date;
|
|
max = parseYMD(max) || date; // Return a new `Date` object (or `null`)
|
|
|
|
return date ? date < min ? min : date > max ? max : date : null;
|
|
};
|
|
|
|
// Localization utilities
|
|
|
|
var RTL_LANGS = ['ar', 'az', 'ckb', 'fa', 'he', 'ks', 'lrc', 'mzn', 'ps', 'sd', 'te', 'ug', 'ur', 'yi'].map(function (locale) {
|
|
return locale.toLowerCase();
|
|
}); // Returns true if the locale is RTL
|
|
|
|
var isLocaleRTL = function isLocaleRTL(locale) {
|
|
// Determines if the locale is RTL (only single locale supported)
|
|
var parts = toString$1(locale).toLowerCase().replace(RX_STRIP_LOCALE_MODS, '').split('-');
|
|
var locale1 = parts.slice(0, 2).join('-');
|
|
var locale2 = parts[0];
|
|
return arrayIncludes(RTL_LANGS, locale1) || arrayIncludes(RTL_LANGS, locale2);
|
|
};
|
|
|
|
// SSR safe client-side ID attribute generation
|
|
// ID's can only be generated client-side, after mount
|
|
// `this._uid` is not synched between server and client
|
|
// @vue/component
|
|
var idMixin = {
|
|
props: {
|
|
id: {
|
|
type: String // default: null
|
|
|
|
}
|
|
},
|
|
data: function data() {
|
|
return {
|
|
localId_: null
|
|
};
|
|
},
|
|
computed: {
|
|
safeId: function safeId() {
|
|
// Computed property that returns a dynamic function for creating the ID
|
|
// Reacts to changes in both `.id` and `.localId_` and regenerates a new function
|
|
var id = this.id || this.localId_; // We return a function that accepts an optional suffix string
|
|
// So this computed prop looks and works like a method
|
|
// but benefits from Vue's computed prop caching
|
|
|
|
var fn = function fn(suffix) {
|
|
if (!id) {
|
|
return null;
|
|
}
|
|
|
|
suffix = String(suffix || '').replace(/\s+/g, '_');
|
|
return suffix ? id + '_' + suffix : id;
|
|
};
|
|
|
|
return fn;
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// `mounted()` only occurs client-side
|
|
this.$nextTick(function () {
|
|
// Update DOM with auto-generated ID after mount
|
|
// to prevent SSR hydration errors
|
|
_this.localId_ = "__BVID__".concat(_this._uid);
|
|
});
|
|
}
|
|
};
|
|
|
|
var props$7 = makePropsConfigurable({
|
|
value: {
|
|
type: [String, Date] // default: null
|
|
|
|
},
|
|
valueAsDate: {
|
|
// Always return the `v-model` value as a date object
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
initialDate: {
|
|
// This specifies the calendar year/month/day that will be shown when
|
|
// first opening the datepicker if no v-model value is provided
|
|
// Default is the current date (or `min`/`max`)
|
|
type: [String, Date] // default: null
|
|
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
min: {
|
|
type: [String, Date] // default: null
|
|
|
|
},
|
|
max: {
|
|
type: [String, Date] // default: null
|
|
|
|
},
|
|
dateDisabledFn: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
startWeekday: {
|
|
// `0` (Sunday), `1` (Monday), ... `6` (Saturday)
|
|
// Day of week to start calendar on
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
locale: {
|
|
// Locale(s) to use
|
|
// Default is to use page/browser default setting
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
direction: {
|
|
// 'ltr', 'rtl', or `null` (for auto detect)
|
|
type: String // default: null
|
|
|
|
},
|
|
selectedVariant: {
|
|
// Variant color to use for the selected date
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
todayVariant: {
|
|
// Variant color to use for today's date (defaults to `selectedVariant`)
|
|
type: String // default: null
|
|
|
|
},
|
|
navButtonVariant: {
|
|
// Variant color to use for the navigation buttons
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
noHighlightToday: {
|
|
// Disable highlighting today's date
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dateInfoFn: {
|
|
// Function to set a class of (classes) on the date cell
|
|
// if passed a string or an array
|
|
// TODO:
|
|
// If the function returns an object, look for class prop for classes,
|
|
// and other props for handling events/details/descriptions
|
|
type: Function // default: null
|
|
|
|
},
|
|
width: {
|
|
// Has no effect if prop `block` is set
|
|
type: String,
|
|
default: '270px'
|
|
},
|
|
block: {
|
|
// Makes calendar the full width of its parent container
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hideHeader: {
|
|
// When true makes the selected date header `sr-only`
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showDecadeNav: {
|
|
// When `true` enables the decade navigation buttons
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hidden: {
|
|
// When `true`, renders a comment node, but keeps the component instance active
|
|
// Mainly for <b-form-date>, so that we can get the component's value and locale
|
|
// But we might just use separate date formatters, using the resolved locale
|
|
// (adjusted for the gregorian calendar)
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaControls: {
|
|
type: String // default: null
|
|
|
|
},
|
|
noKeyNav: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
roleDescription: {
|
|
type: String // default: null
|
|
|
|
},
|
|
// Labels for buttons and keyboard shortcuts
|
|
labelPrevDecade: {
|
|
type: String,
|
|
default: 'Previous decade'
|
|
},
|
|
labelPrevYear: {
|
|
type: String,
|
|
default: 'Previous year'
|
|
},
|
|
labelPrevMonth: {
|
|
type: String,
|
|
default: 'Previous month'
|
|
},
|
|
labelCurrentMonth: {
|
|
type: String,
|
|
default: 'Current month'
|
|
},
|
|
labelNextMonth: {
|
|
type: String,
|
|
default: 'Next month'
|
|
},
|
|
labelNextYear: {
|
|
type: String,
|
|
default: 'Next year'
|
|
},
|
|
labelNextDecade: {
|
|
type: String,
|
|
default: 'Next decade'
|
|
},
|
|
labelToday: {
|
|
type: String,
|
|
default: 'Today'
|
|
},
|
|
labelSelected: {
|
|
type: String,
|
|
default: 'Selected date'
|
|
},
|
|
labelNoDateSelected: {
|
|
type: String,
|
|
default: 'No date selected'
|
|
},
|
|
labelCalendar: {
|
|
type: String,
|
|
default: 'Calendar'
|
|
},
|
|
labelNav: {
|
|
type: String,
|
|
default: 'Calendar navigation'
|
|
},
|
|
labelHelp: {
|
|
type: String,
|
|
default: 'Use cursor keys to navigate calendar dates'
|
|
},
|
|
dateFormatOptions: {
|
|
// `Intl.DateTimeFormat` object
|
|
// Note: This value is *not* to be placed in the global config
|
|
type: Object,
|
|
default: function _default() {
|
|
return {
|
|
year: DATE_FORMAT_NUMERIC,
|
|
month: CALENDAR_LONG,
|
|
day: DATE_FORMAT_NUMERIC,
|
|
weekday: CALENDAR_LONG
|
|
};
|
|
}
|
|
},
|
|
weekdayHeaderFormat: {
|
|
// Format of the weekday names at the top of the calendar
|
|
// Note: This value is *not* to be placed in the global config
|
|
type: String,
|
|
// `short` is typically a 3 letter abbreviation,
|
|
// `narrow` is typically a single letter
|
|
// `long` is the full week day name
|
|
// Although some locales may override this (i.e `ar`, etc.)
|
|
default: CALENDAR_SHORT,
|
|
validator: function validator(value) {
|
|
return arrayIncludes([CALENDAR_LONG, CALENDAR_SHORT, CALENDAR_NARROW], value);
|
|
}
|
|
}
|
|
}, NAME_CALENDAR); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCalendar = Vue__default['default'].extend({
|
|
name: NAME_CALENDAR,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, idMixin, normalizeSlotMixin],
|
|
model: {
|
|
// Even though this is the default that Vue assumes, we need
|
|
// to add it for the docs to reflect that this is the model
|
|
// And also for some validation libraries to work
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: props$7,
|
|
data: function data() {
|
|
var selected = formatYMD(this.value) || '';
|
|
return {
|
|
// Selected date
|
|
selectedYMD: selected,
|
|
// Date in calendar grid that has `tabindex` of `0`
|
|
activeYMD: selected || formatYMD(constrainDate(this.initialDate || this.getToday()), this.min, this.max),
|
|
// Will be true if the calendar grid has/contains focus
|
|
gridHasFocus: false,
|
|
// Flag to enable the `aria-live` region(s) after mount
|
|
// to prevent screen reader "outbursts" when mounting
|
|
isLive: false
|
|
};
|
|
},
|
|
computed: {
|
|
valueId: function valueId() {
|
|
return this.safeId();
|
|
},
|
|
widgetId: function widgetId() {
|
|
return this.safeId('_calendar-wrapper_');
|
|
},
|
|
navId: function navId() {
|
|
return this.safeId('_calendar-nav_');
|
|
},
|
|
gridId: function gridId() {
|
|
return this.safeId('_calendar-grid_');
|
|
},
|
|
gridCaptionId: function gridCaptionId() {
|
|
return this.safeId('_calendar-grid-caption_');
|
|
},
|
|
gridHelpId: function gridHelpId() {
|
|
return this.safeId('_calendar-grid-help_');
|
|
},
|
|
activeId: function activeId() {
|
|
return this.activeYMD ? this.safeId("_cell-".concat(this.activeYMD, "_")) : null;
|
|
},
|
|
// TODO: Use computed props to convert `YYYY-MM-DD` to `Date` object
|
|
selectedDate: function selectedDate() {
|
|
// Selected as a `Date` object
|
|
return parseYMD(this.selectedYMD);
|
|
},
|
|
activeDate: function activeDate() {
|
|
// Active as a `Date` object
|
|
return parseYMD(this.activeYMD);
|
|
},
|
|
computedMin: function computedMin() {
|
|
return parseYMD(this.min);
|
|
},
|
|
computedMax: function computedMax() {
|
|
return parseYMD(this.max);
|
|
},
|
|
computedWeekStarts: function computedWeekStarts() {
|
|
// `startWeekday` is a prop (constrained to `0` through `6`)
|
|
return mathMax(toInteger(this.startWeekday, 0), 0) % 7;
|
|
},
|
|
computedLocale: function computedLocale() {
|
|
// Returns the resolved locale used by the calendar
|
|
return resolveLocale(concat(this.locale).filter(identity), CALENDAR_GREGORY);
|
|
},
|
|
computedDateDisabledFn: function computedDateDisabledFn() {
|
|
var dateDisabledFn = this.dateDisabledFn;
|
|
var result = null;
|
|
|
|
try {
|
|
result = dateDisabledFn();
|
|
} catch (_unused) {}
|
|
|
|
return isUndefined(result) ? function () {
|
|
return false;
|
|
} : dateDisabledFn;
|
|
},
|
|
// TODO: Change `dateInfoFn` to handle events and notes as well as classes
|
|
computedDateInfoFn: function computedDateInfoFn() {
|
|
var dateInfoFn = this.dateInfoFn;
|
|
var result = null;
|
|
|
|
try {
|
|
result = dateInfoFn();
|
|
} catch (_unused2) {}
|
|
|
|
return isUndefined(result) ? function () {
|
|
return {};
|
|
} : dateInfoFn;
|
|
},
|
|
calendarLocale: function calendarLocale() {
|
|
// This locale enforces the gregorian calendar (for use in formatter functions)
|
|
// Needed because IE 11 resolves `ar-IR` as islamic-civil calendar
|
|
// and IE 11 (and some other browsers) do not support the `calendar` option
|
|
// And we currently only support the gregorian calendar
|
|
var fmt = new Intl.DateTimeFormat(this.computedLocale, {
|
|
calendar: CALENDAR_GREGORY
|
|
});
|
|
var calendar = fmt.resolvedOptions().calendar;
|
|
var locale = fmt.resolvedOptions().locale;
|
|
/* istanbul ignore if: mainly for IE 11 and a few other browsers, hard to test in JSDOM */
|
|
|
|
if (calendar !== CALENDAR_GREGORY) {
|
|
// Ensure the locale requests the gregorian calendar
|
|
// Mainly for IE 11, and currently we can't handle non-gregorian calendars
|
|
// TODO: Should we always return this value?
|
|
locale = locale.replace(/-u-.+$/i, '').concat('-u-ca-gregory');
|
|
}
|
|
|
|
return locale;
|
|
},
|
|
calendarYear: function calendarYear() {
|
|
return this.activeDate.getFullYear();
|
|
},
|
|
calendarMonth: function calendarMonth() {
|
|
return this.activeDate.getMonth();
|
|
},
|
|
calendarFirstDay: function calendarFirstDay() {
|
|
// We set the time for this date to 12pm to work around
|
|
// date formatting issues in Firefox and Safari
|
|
// See: https://github.com/bootstrap-vue/bootstrap-vue/issues/5818
|
|
return createDate(this.calendarYear, this.calendarMonth, 1, 12);
|
|
},
|
|
calendarDaysInMonth: function calendarDaysInMonth() {
|
|
// We create a new date as to not mutate the original
|
|
var date = createDate(this.calendarFirstDay);
|
|
date.setMonth(date.getMonth() + 1, 0);
|
|
return date.getDate();
|
|
},
|
|
computedVariant: function computedVariant() {
|
|
return "btn-".concat(this.selectedVariant || 'primary');
|
|
},
|
|
computedTodayVariant: function computedTodayVariant() {
|
|
return "btn-outline-".concat(this.todayVariant || this.selectedVariant || 'primary');
|
|
},
|
|
computedNavButtonVariant: function computedNavButtonVariant() {
|
|
return "btn-outline-".concat(this.navButtonVariant || 'primary');
|
|
},
|
|
isRTL: function isRTL() {
|
|
// `true` if the language requested is RTL
|
|
var dir = toString$1(this.direction).toLowerCase();
|
|
|
|
if (dir === 'rtl') {
|
|
/* istanbul ignore next */
|
|
return true;
|
|
} else if (dir === 'ltr') {
|
|
/* istanbul ignore next */
|
|
return false;
|
|
}
|
|
|
|
return isLocaleRTL(this.computedLocale);
|
|
},
|
|
context: function context() {
|
|
var selectedYMD = this.selectedYMD,
|
|
activeYMD = this.activeYMD;
|
|
var selectedDate = parseYMD(selectedYMD);
|
|
var activeDate = parseYMD(activeYMD);
|
|
return {
|
|
// The current value of the `v-model`
|
|
selectedYMD: selectedYMD,
|
|
selectedDate: selectedDate,
|
|
selectedFormatted: selectedDate ? this.formatDateString(selectedDate) : this.labelNoDateSelected,
|
|
// Which date cell is considered active due to navigation
|
|
activeYMD: activeYMD,
|
|
activeDate: activeDate,
|
|
activeFormatted: activeDate ? this.formatDateString(activeDate) : '',
|
|
// `true` if the date is disabled (when using keyboard navigation)
|
|
disabled: this.dateDisabled(activeDate),
|
|
// Locales used in formatting dates
|
|
locale: this.computedLocale,
|
|
calendarLocale: this.calendarLocale,
|
|
rtl: this.isRTL
|
|
};
|
|
},
|
|
// Computed props that return a function reference
|
|
dateOutOfRange: function dateOutOfRange() {
|
|
// Check whether a date is within the min/max range
|
|
// Returns a new function ref if the pops change
|
|
// We do this as we need to trigger the calendar computed prop
|
|
// to update when these props update
|
|
var min = this.computedMin,
|
|
max = this.computedMax;
|
|
return function (date) {
|
|
// Handle both `YYYY-MM-DD` and `Date` objects
|
|
date = parseYMD(date);
|
|
return min && date < min || max && date > max;
|
|
};
|
|
},
|
|
dateDisabled: function dateDisabled() {
|
|
var _this = this;
|
|
|
|
// Returns a function for validating if a date is within range
|
|
// We grab this variables first to ensure a new function ref
|
|
// is generated when the props value changes
|
|
// We do this as we need to trigger the calendar computed prop
|
|
// to update when these props update
|
|
var rangeFn = this.dateOutOfRange; // Return the function ref
|
|
|
|
return function (date) {
|
|
// Handle both `YYYY-MM-DD` and `Date` objects
|
|
date = parseYMD(date);
|
|
var ymd = formatYMD(date);
|
|
return !!(rangeFn(date) || _this.computedDateDisabledFn(ymd, date));
|
|
};
|
|
},
|
|
// Computed props that return date formatter functions
|
|
formatDateString: function formatDateString() {
|
|
// Returns a date formatter function
|
|
return createDateFormatter(this.calendarLocale, _objectSpread2(_objectSpread2({
|
|
// Ensure we have year, month, day shown for screen readers/ARIA
|
|
// If users really want to leave one of these out, they can
|
|
// pass `undefined` for the property value
|
|
year: DATE_FORMAT_NUMERIC,
|
|
month: DATE_FORMAT_2_DIGIT,
|
|
day: DATE_FORMAT_2_DIGIT
|
|
}, this.dateFormatOptions), {}, {
|
|
// Ensure hours/minutes/seconds are not shown
|
|
// As we do not support the time portion (yet)
|
|
hour: undefined,
|
|
minute: undefined,
|
|
second: undefined,
|
|
// Ensure calendar is gregorian
|
|
calendar: CALENDAR_GREGORY
|
|
}));
|
|
},
|
|
formatYearMonth: function formatYearMonth() {
|
|
// Returns a date formatter function
|
|
return createDateFormatter(this.calendarLocale, {
|
|
year: DATE_FORMAT_NUMERIC,
|
|
month: CALENDAR_LONG,
|
|
calendar: CALENDAR_GREGORY
|
|
});
|
|
},
|
|
formatWeekdayName: function formatWeekdayName() {
|
|
// Long weekday name for weekday header aria-label
|
|
return createDateFormatter(this.calendarLocale, {
|
|
weekday: CALENDAR_LONG,
|
|
calendar: CALENDAR_GREGORY
|
|
});
|
|
},
|
|
formatWeekdayNameShort: function formatWeekdayNameShort() {
|
|
// Weekday header cell format
|
|
// defaults to 'short' 3 letter days, where possible
|
|
return createDateFormatter(this.calendarLocale, {
|
|
weekday: this.weekdayHeaderFormat || CALENDAR_SHORT,
|
|
calendar: CALENDAR_GREGORY
|
|
});
|
|
},
|
|
formatDay: function formatDay() {
|
|
// Calendar grid day number formatter
|
|
// We don't use DateTimeFormatter here as it can place extra
|
|
// character(s) after the number (i.e the `zh` locale)
|
|
var nf = new Intl.NumberFormat([this.computedLocale], {
|
|
style: 'decimal',
|
|
minimumIntegerDigits: 1,
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
notation: 'standard'
|
|
}); // Return a formatter function instance
|
|
|
|
return function (date) {
|
|
return nf.format(date.getDate());
|
|
};
|
|
},
|
|
// Disabled states for the nav buttons
|
|
prevDecadeDisabled: function prevDecadeDisabled() {
|
|
var min = this.computedMin;
|
|
return this.disabled || min && lastDateOfMonth(oneDecadeAgo(this.activeDate)) < min;
|
|
},
|
|
prevYearDisabled: function prevYearDisabled() {
|
|
var min = this.computedMin;
|
|
return this.disabled || min && lastDateOfMonth(oneYearAgo(this.activeDate)) < min;
|
|
},
|
|
prevMonthDisabled: function prevMonthDisabled() {
|
|
var min = this.computedMin;
|
|
return this.disabled || min && lastDateOfMonth(oneMonthAgo(this.activeDate)) < min;
|
|
},
|
|
thisMonthDisabled: function thisMonthDisabled() {
|
|
// TODO: We could/should check if today is out of range
|
|
return this.disabled;
|
|
},
|
|
nextMonthDisabled: function nextMonthDisabled() {
|
|
var max = this.computedMax;
|
|
return this.disabled || max && firstDateOfMonth(oneMonthAhead(this.activeDate)) > max;
|
|
},
|
|
nextYearDisabled: function nextYearDisabled() {
|
|
var max = this.computedMax;
|
|
return this.disabled || max && firstDateOfMonth(oneYearAhead(this.activeDate)) > max;
|
|
},
|
|
nextDecadeDisabled: function nextDecadeDisabled() {
|
|
var max = this.computedMax;
|
|
return this.disabled || max && firstDateOfMonth(oneDecadeAhead(this.activeDate)) > max;
|
|
},
|
|
// Calendar dates generation
|
|
calendar: function calendar() {
|
|
var matrix = [];
|
|
var firstDay = this.calendarFirstDay;
|
|
var calendarYear = firstDay.getFullYear();
|
|
var calendarMonth = firstDay.getMonth();
|
|
var daysInMonth = this.calendarDaysInMonth;
|
|
var startIndex = firstDay.getDay(); // `0`..`6`
|
|
|
|
var weekOffset = (this.computedWeekStarts > startIndex ? 7 : 0) - this.computedWeekStarts; // Build the calendar matrix
|
|
|
|
var currentDay = 0 - weekOffset - startIndex;
|
|
|
|
for (var week = 0; week < 6 && currentDay < daysInMonth; week++) {
|
|
// For each week
|
|
matrix[week] = []; // The following could be a map function
|
|
|
|
for (var j = 0; j < 7; j++) {
|
|
// For each day in week
|
|
currentDay++;
|
|
var date = createDate(calendarYear, calendarMonth, currentDay);
|
|
var month = date.getMonth();
|
|
var dayYMD = formatYMD(date);
|
|
var dayDisabled = this.dateDisabled(date); // TODO: This could be a normalizer method
|
|
|
|
var dateInfo = this.computedDateInfoFn(dayYMD, parseYMD(dayYMD));
|
|
dateInfo = isString(dateInfo) || isArray(dateInfo) ?
|
|
/* istanbul ignore next */
|
|
{
|
|
class: dateInfo
|
|
} : isPlainObject(dateInfo) ? _objectSpread2({
|
|
class: ''
|
|
}, dateInfo) :
|
|
/* istanbul ignore next */
|
|
{
|
|
class: ''
|
|
};
|
|
matrix[week].push({
|
|
ymd: dayYMD,
|
|
// Cell content
|
|
day: this.formatDay(date),
|
|
label: this.formatDateString(date),
|
|
// Flags for styling
|
|
isThisMonth: month === calendarMonth,
|
|
isDisabled: dayDisabled,
|
|
// TODO: Handle other dateInfo properties such as notes/events
|
|
info: dateInfo
|
|
});
|
|
}
|
|
}
|
|
|
|
return matrix;
|
|
},
|
|
calendarHeadings: function calendarHeadings() {
|
|
var _this2 = this;
|
|
|
|
return this.calendar[0].map(function (d) {
|
|
return {
|
|
text: _this2.formatWeekdayNameShort(parseYMD(d.ymd)),
|
|
label: _this2.formatWeekdayName(parseYMD(d.ymd))
|
|
};
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal, oldVal) {
|
|
var selected = formatYMD(newVal) || '';
|
|
var old = formatYMD(oldVal) || '';
|
|
|
|
if (!datesEqual(selected, old)) {
|
|
this.activeYMD = selected || this.activeYMD;
|
|
this.selectedYMD = selected;
|
|
}
|
|
},
|
|
selectedYMD: function selectedYMD(newYMD, oldYMD) {
|
|
// TODO:
|
|
// Should we compare to `formatYMD(this.value)` and emit
|
|
// only if they are different?
|
|
if (newYMD !== oldYMD) {
|
|
this.$emit('input', this.valueAsDate ? parseYMD(newYMD) || null : newYMD || '');
|
|
}
|
|
},
|
|
context: function context(newVal, oldVal) {
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$emit('context', newVal);
|
|
}
|
|
},
|
|
hidden: function hidden(newVal) {
|
|
// Reset the active focused day when hidden
|
|
this.activeYMD = this.selectedYMD || formatYMD(this.value || this.constrainDate(this.initialDate || this.getToday())); // Enable/disable the live regions
|
|
|
|
this.setLive(!newVal);
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this3 = this;
|
|
|
|
this.$nextTick(function () {
|
|
_this3.$emit('context', _this3.context);
|
|
});
|
|
},
|
|
mounted: function mounted() {
|
|
this.setLive(true);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
this.setLive(true);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
this.setLive(false);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.setLive(false);
|
|
},
|
|
methods: {
|
|
// Public method(s)
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$refs.grid);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$refs.grid);
|
|
}
|
|
},
|
|
// Private methods
|
|
setLive: function setLive(on) {
|
|
var _this4 = this;
|
|
|
|
if (on) {
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this4.isLive = true;
|
|
});
|
|
});
|
|
} else {
|
|
this.isLive = false;
|
|
}
|
|
},
|
|
getToday: function getToday() {
|
|
return parseYMD(createDate());
|
|
},
|
|
constrainDate: function constrainDate$1(date) {
|
|
// Constrains a date between min and max
|
|
// returns a new `Date` object instance
|
|
return constrainDate(date, this.computedMin, this.computedMax);
|
|
},
|
|
emitSelected: function emitSelected(date) {
|
|
var _this5 = this;
|
|
|
|
// Performed in a `$nextTick()` to (probably) ensure
|
|
// the input event has emitted first
|
|
this.$nextTick(function () {
|
|
_this5.$emit('selected', formatYMD(date) || '', parseYMD(date) || null);
|
|
});
|
|
},
|
|
// Event handlers
|
|
setGridFocusFlag: function setGridFocusFlag(evt) {
|
|
// Sets the gridHasFocus flag to make date "button" look focused
|
|
this.gridHasFocus = !this.disabled && evt.type === 'focus';
|
|
},
|
|
onKeydownWrapper: function onKeydownWrapper(evt) {
|
|
// Calendar keyboard navigation
|
|
// Handles PAGEUP/PAGEDOWN/END/HOME/LEFT/UP/RIGHT/DOWN
|
|
// Focuses grid after updating
|
|
if (this.noKeyNav) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
var altKey = evt.altKey,
|
|
ctrlKey = evt.ctrlKey,
|
|
keyCode = evt.keyCode;
|
|
|
|
if (!arrayIncludes([CODE_PAGEUP, CODE_PAGEDOWN, CODE_END, CODE_HOME, CODE_LEFT, CODE_UP, CODE_RIGHT, CODE_DOWN], keyCode)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
stopEvent(evt);
|
|
var activeDate = createDate(this.activeDate);
|
|
var checkDate = createDate(this.activeDate);
|
|
var day = activeDate.getDate();
|
|
var constrainedToday = this.constrainDate(this.getToday());
|
|
var isRTL = this.isRTL;
|
|
|
|
if (keyCode === CODE_PAGEUP) {
|
|
// PAGEUP - Previous month/year
|
|
activeDate = (altKey ? ctrlKey ? oneDecadeAgo : oneYearAgo : oneMonthAgo)(activeDate); // We check the first day of month to be in rage
|
|
|
|
checkDate = createDate(activeDate);
|
|
checkDate.setDate(1);
|
|
} else if (keyCode === CODE_PAGEDOWN) {
|
|
// PAGEDOWN - Next month/year
|
|
activeDate = (altKey ? ctrlKey ? oneDecadeAhead : oneYearAhead : oneMonthAhead)(activeDate); // We check the last day of month to be in rage
|
|
|
|
checkDate = createDate(activeDate);
|
|
checkDate.setMonth(checkDate.getMonth() + 1);
|
|
checkDate.setDate(0);
|
|
} else if (keyCode === CODE_LEFT) {
|
|
// LEFT - Previous day (or next day for RTL)
|
|
activeDate.setDate(day + (isRTL ? 1 : -1));
|
|
activeDate = this.constrainDate(activeDate);
|
|
checkDate = activeDate;
|
|
} else if (keyCode === CODE_RIGHT) {
|
|
// RIGHT - Next day (or previous day for RTL)
|
|
activeDate.setDate(day + (isRTL ? -1 : 1));
|
|
activeDate = this.constrainDate(activeDate);
|
|
checkDate = activeDate;
|
|
} else if (keyCode === CODE_UP) {
|
|
// UP - Previous week
|
|
activeDate.setDate(day - 7);
|
|
activeDate = this.constrainDate(activeDate);
|
|
checkDate = activeDate;
|
|
} else if (keyCode === CODE_DOWN) {
|
|
// DOWN - Next week
|
|
activeDate.setDate(day + 7);
|
|
activeDate = this.constrainDate(activeDate);
|
|
checkDate = activeDate;
|
|
} else if (keyCode === CODE_HOME) {
|
|
// HOME - Today
|
|
activeDate = constrainedToday;
|
|
checkDate = activeDate;
|
|
} else if (keyCode === CODE_END) {
|
|
// END - Selected date, or today if no selected date
|
|
activeDate = parseYMD(this.selectedDate) || constrainedToday;
|
|
checkDate = activeDate;
|
|
}
|
|
|
|
if (!this.dateOutOfRange(checkDate) && !datesEqual(activeDate, this.activeDate)) {
|
|
// We only jump to date if within min/max
|
|
// We don't check for individual disabled dates though (via user function)
|
|
this.activeYMD = formatYMD(activeDate);
|
|
} // Ensure grid is focused
|
|
|
|
|
|
this.focus();
|
|
},
|
|
onKeydownGrid: function onKeydownGrid(evt) {
|
|
// Pressing enter/space on grid to select active date
|
|
var keyCode = evt.keyCode;
|
|
var activeDate = this.activeDate;
|
|
|
|
if (keyCode === CODE_ENTER || keyCode === CODE_SPACE) {
|
|
stopEvent(evt);
|
|
|
|
if (!this.disabled && !this.readonly && !this.dateDisabled(activeDate)) {
|
|
this.selectedYMD = formatYMD(activeDate);
|
|
this.emitSelected(activeDate);
|
|
} // Ensure grid is focused
|
|
|
|
|
|
this.focus();
|
|
}
|
|
},
|
|
onClickDay: function onClickDay(day) {
|
|
// Clicking on a date "button" to select it
|
|
var selectedDate = this.selectedDate,
|
|
activeDate = this.activeDate;
|
|
var clickedDate = parseYMD(day.ymd);
|
|
|
|
if (!this.disabled && !day.isDisabled && !this.dateDisabled(clickedDate)) {
|
|
if (!this.readonly) {
|
|
// If readonly mode, we don't set the selected date, just the active date
|
|
// If the clicked date is equal to the already selected date, we don't update the model
|
|
this.selectedYMD = formatYMD(datesEqual(clickedDate, selectedDate) ? selectedDate : clickedDate);
|
|
this.emitSelected(clickedDate);
|
|
}
|
|
|
|
this.activeYMD = formatYMD(datesEqual(clickedDate, activeDate) ? activeDate : createDate(clickedDate)); // Ensure grid is focused
|
|
|
|
this.focus();
|
|
}
|
|
},
|
|
gotoPrevDecade: function gotoPrevDecade() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneDecadeAgo(this.activeDate)));
|
|
},
|
|
gotoPrevYear: function gotoPrevYear() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneYearAgo(this.activeDate)));
|
|
},
|
|
gotoPrevMonth: function gotoPrevMonth() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneMonthAgo(this.activeDate)));
|
|
},
|
|
gotoCurrentMonth: function gotoCurrentMonth() {
|
|
// TODO: Maybe this goto date should be configurable?
|
|
this.activeYMD = formatYMD(this.constrainDate(this.getToday()));
|
|
},
|
|
gotoNextMonth: function gotoNextMonth() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneMonthAhead(this.activeDate)));
|
|
},
|
|
gotoNextYear: function gotoNextYear() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneYearAhead(this.activeDate)));
|
|
},
|
|
gotoNextDecade: function gotoNextDecade() {
|
|
this.activeYMD = formatYMD(this.constrainDate(oneDecadeAhead(this.activeDate)));
|
|
},
|
|
onHeaderClick: function onHeaderClick() {
|
|
if (!this.disabled) {
|
|
this.activeYMD = this.selectedYMD || formatYMD(this.getToday());
|
|
this.focus();
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this6 = this;
|
|
|
|
// If `hidden` prop is set, render just a placeholder node
|
|
if (this.hidden) {
|
|
return h();
|
|
}
|
|
|
|
var valueId = this.valueId,
|
|
widgetId = this.widgetId,
|
|
navId = this.navId,
|
|
gridId = this.gridId,
|
|
gridCaptionId = this.gridCaptionId,
|
|
gridHelpId = this.gridHelpId,
|
|
activeId = this.activeId,
|
|
disabled = this.disabled,
|
|
noKeyNav = this.noKeyNav,
|
|
isLive = this.isLive,
|
|
isRTL = this.isRTL,
|
|
activeYMD = this.activeYMD,
|
|
selectedYMD = this.selectedYMD,
|
|
safeId = this.safeId;
|
|
var hideDecadeNav = !this.showDecadeNav;
|
|
var todayYMD = formatYMD(this.getToday());
|
|
var highlightToday = !this.noHighlightToday; // Header showing current selected date
|
|
|
|
var $header = h('output', {
|
|
staticClass: 'form-control form-control-sm text-center',
|
|
class: {
|
|
'text-muted': disabled,
|
|
readonly: this.readonly || disabled
|
|
},
|
|
attrs: {
|
|
id: valueId,
|
|
for: gridId,
|
|
role: 'status',
|
|
tabindex: disabled ? null : '-1',
|
|
// Mainly for testing purposes, as we do not know
|
|
// the exact format `Intl` will format the date string
|
|
'data-selected': toString$1(selectedYMD),
|
|
// We wait until after mount to enable `aria-live`
|
|
// to prevent initial announcement on page render
|
|
'aria-live': isLive ? 'polite' : 'off',
|
|
'aria-atomic': isLive ? 'true' : null
|
|
},
|
|
on: {
|
|
// Transfer focus/click to focus grid
|
|
// and focus active date (or today if no selection)
|
|
click: this.onHeaderClick,
|
|
focus: this.onHeaderClick
|
|
}
|
|
}, this.selectedDate ? [// We use `bdi` elements here in case the label doesn't match the locale
|
|
// Although IE 11 does not deal with <BDI> at all (equivalent to a span)
|
|
h('bdi', {
|
|
staticClass: 'sr-only'
|
|
}, " (".concat(toString$1(this.labelSelected), ") ")), h('bdi', this.formatDateString(this.selectedDate))] : this.labelNoDateSelected || "\xA0" // ' '
|
|
);
|
|
$header = h('header', {
|
|
staticClass: 'b-calendar-header',
|
|
class: {
|
|
'sr-only': this.hideHeader
|
|
},
|
|
attrs: {
|
|
title: this.selectedDate ? this.labelSelectedDate || null : null
|
|
}
|
|
}, [$header]); // Content for the date navigation buttons
|
|
|
|
var navScope = {
|
|
isRTL: isRTL
|
|
};
|
|
var navProps = {
|
|
shiftV: 0.5
|
|
};
|
|
|
|
var navPrevProps = _objectSpread2(_objectSpread2({}, navProps), {}, {
|
|
flipH: isRTL
|
|
});
|
|
|
|
var navNextProps = _objectSpread2(_objectSpread2({}, navProps), {}, {
|
|
flipH: !isRTL
|
|
});
|
|
|
|
var $prevDecadeIcon = this.normalizeSlot('nav-prev-decade', navScope) || h(BIconChevronBarLeft, {
|
|
props: navPrevProps
|
|
});
|
|
var $prevYearIcon = this.normalizeSlot('nav-prev-year', navScope) || h(BIconChevronDoubleLeft, {
|
|
props: navPrevProps
|
|
});
|
|
var $prevMonthIcon = this.normalizeSlot('nav-prev-month', navScope) || h(BIconChevronLeft, {
|
|
props: navPrevProps
|
|
});
|
|
var $thisMonthIcon = this.normalizeSlot('nav-this-month', navScope) || h(BIconCircleFill, {
|
|
props: navProps
|
|
});
|
|
var $nextMonthIcon = this.normalizeSlot('nav-next-month', navScope) || h(BIconChevronLeft, {
|
|
props: navNextProps
|
|
});
|
|
var $nextYearIcon = this.normalizeSlot('nav-next-year', navScope) || h(BIconChevronDoubleLeft, {
|
|
props: navNextProps
|
|
});
|
|
var $nextDecadeIcon = this.normalizeSlot('nav-next-decade', navScope) || h(BIconChevronBarLeft, {
|
|
props: navNextProps
|
|
}); // Utility to create the date navigation buttons
|
|
|
|
var makeNavBtn = function makeNavBtn(content, label, handler, btnDisabled, shortcut) {
|
|
return h('button', {
|
|
staticClass: 'btn btn-sm border-0 flex-fill',
|
|
class: [_this6.computedNavButtonVariant, {
|
|
disabled: btnDisabled
|
|
}],
|
|
attrs: {
|
|
title: label || null,
|
|
type: 'button',
|
|
tabindex: noKeyNav ? '-1' : null,
|
|
'aria-label': label || null,
|
|
'aria-disabled': btnDisabled ? 'true' : null,
|
|
'aria-keyshortcuts': shortcut || null
|
|
},
|
|
on: btnDisabled ? {} : {
|
|
click: handler
|
|
}
|
|
}, [h('div', {
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
}, [content])]);
|
|
}; // Generate the date navigation buttons
|
|
|
|
|
|
var $nav = h('div', {
|
|
staticClass: 'b-calendar-nav d-flex',
|
|
attrs: {
|
|
id: navId,
|
|
role: 'group',
|
|
tabindex: noKeyNav ? '-1' : null,
|
|
'aria-hidden': disabled ? 'true' : null,
|
|
'aria-label': this.labelNav || null,
|
|
'aria-controls': gridId
|
|
}
|
|
}, [hideDecadeNav ? h() : makeNavBtn($prevDecadeIcon, this.labelPrevDecade, this.gotoPrevDecade, this.prevDecadeDisabled, 'Ctrl+Alt+PageDown'), makeNavBtn($prevYearIcon, this.labelPrevYear, this.gotoPrevYear, this.prevYearDisabled, 'Alt+PageDown'), makeNavBtn($prevMonthIcon, this.labelPrevMonth, this.gotoPrevMonth, this.prevMonthDisabled, 'PageDown'), makeNavBtn($thisMonthIcon, this.labelCurrentMonth, this.gotoCurrentMonth, this.thisMonthDisabled, 'Home'), makeNavBtn($nextMonthIcon, this.labelNextMonth, this.gotoNextMonth, this.nextMonthDisabled, 'PageUp'), makeNavBtn($nextYearIcon, this.labelNextYear, this.gotoNextYear, this.nextYearDisabled, 'Alt+PageUp'), hideDecadeNav ? h() : makeNavBtn($nextDecadeIcon, this.labelNextDecade, this.gotoNextDecade, this.nextDecadeDisabled, 'Ctrl+Alt+PageUp')]); // Caption for calendar grid
|
|
|
|
var $gridCaption = h('header', {
|
|
key: 'grid-caption',
|
|
staticClass: 'b-calendar-grid-caption text-center font-weight-bold',
|
|
class: {
|
|
'text-muted': disabled
|
|
},
|
|
attrs: {
|
|
id: gridCaptionId,
|
|
'aria-live': isLive ? 'polite' : null,
|
|
'aria-atomic': isLive ? 'true' : null
|
|
}
|
|
}, this.formatYearMonth(this.calendarFirstDay)); // Calendar weekday headings
|
|
|
|
var $gridWeekDays = h('div', {
|
|
staticClass: 'b-calendar-grid-weekdays row no-gutters border-bottom',
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
}, this.calendarHeadings.map(function (d, idx) {
|
|
return h('small', {
|
|
key: idx,
|
|
staticClass: 'col text-truncate',
|
|
class: {
|
|
'text-muted': disabled
|
|
},
|
|
attrs: {
|
|
title: d.label === d.text ? null : d.label,
|
|
'aria-label': d.label
|
|
}
|
|
}, d.text);
|
|
})); // Calendar day grid
|
|
|
|
var $gridBody = this.calendar.map(function (week) {
|
|
var $cells = week.map(function (day, dIndex) {
|
|
var _class;
|
|
|
|
var isSelected = day.ymd === selectedYMD;
|
|
var isActive = day.ymd === activeYMD;
|
|
var isToday = day.ymd === todayYMD;
|
|
var idCell = safeId("_cell-".concat(day.ymd, "_")); // "fake" button
|
|
|
|
var $btn = h('span', {
|
|
staticClass: 'btn border-0 rounded-circle text-nowrap',
|
|
// Should we add some classes to signify if today/selected/etc?
|
|
class: (_class = {
|
|
// Give the fake button a focus ring
|
|
focus: isActive && _this6.gridHasFocus,
|
|
// Styling
|
|
disabled: day.isDisabled || disabled,
|
|
active: isSelected
|
|
}, _defineProperty(_class, _this6.computedVariant, isSelected), _defineProperty(_class, _this6.computedTodayVariant, isToday && highlightToday && !isSelected && day.isThisMonth), _defineProperty(_class, 'btn-outline-light', !(isToday && highlightToday) && !isSelected && !isActive), _defineProperty(_class, 'btn-light', !(isToday && highlightToday) && !isSelected && isActive), _defineProperty(_class, 'text-muted', !day.isThisMonth && !isSelected), _defineProperty(_class, 'text-dark', !(isToday && highlightToday) && !isSelected && !isActive && day.isThisMonth), _defineProperty(_class, 'font-weight-bold', (isSelected || day.isThisMonth) && !day.isDisabled), _class),
|
|
on: {
|
|
click: function click() {
|
|
return _this6.onClickDay(day);
|
|
}
|
|
}
|
|
}, day.day);
|
|
return h('div', // Cell with button
|
|
{
|
|
key: dIndex,
|
|
staticClass: 'col p-0',
|
|
class: day.isDisabled ? 'bg-light' : day.info.class || '',
|
|
attrs: {
|
|
id: idCell,
|
|
role: 'button',
|
|
'data-date': day.ymd,
|
|
// Primarily for testing purposes
|
|
// Only days in the month are presented as buttons to screen readers
|
|
'aria-hidden': day.isThisMonth ? null : 'true',
|
|
'aria-disabled': day.isDisabled || disabled ? 'true' : null,
|
|
'aria-label': [day.label, isSelected ? "(".concat(_this6.labelSelected, ")") : null, isToday ? "(".concat(_this6.labelToday, ")") : null].filter(identity).join(' '),
|
|
// NVDA doesn't convey `aria-selected`, but does `aria-current`,
|
|
// ChromeVox doesn't convey `aria-current`, but does `aria-selected`,
|
|
// so we set both attributes for robustness
|
|
'aria-selected': isSelected ? 'true' : null,
|
|
'aria-current': isSelected ? 'date' : null
|
|
}
|
|
}, [$btn]);
|
|
}); // Return the week "row"
|
|
// We use the first day of the weeks YMD value as a
|
|
// key for efficient DOM patching / element re-use
|
|
|
|
return h('div', {
|
|
key: week[0].ymd,
|
|
staticClass: 'row no-gutters'
|
|
}, $cells);
|
|
});
|
|
$gridBody = h('div', {
|
|
// A key is only required on the body if we add in transition support
|
|
// key: this.activeYMD.slice(0, -3),
|
|
staticClass: 'b-calendar-grid-body',
|
|
style: disabled ? {
|
|
pointerEvents: 'none'
|
|
} : {}
|
|
}, $gridBody);
|
|
var $gridHelp = h('footer', {
|
|
staticClass: 'b-calendar-grid-help border-top small text-muted text-center bg-light',
|
|
attrs: {
|
|
id: gridHelpId
|
|
}
|
|
}, [h('div', {
|
|
staticClass: 'small'
|
|
}, this.labelHelp)]);
|
|
var $grid = h('div', {
|
|
ref: 'grid',
|
|
staticClass: 'b-calendar-grid form-control h-auto text-center',
|
|
attrs: {
|
|
id: gridId,
|
|
role: 'application',
|
|
tabindex: noKeyNav ? '-1' : disabled ? null : '0',
|
|
'data-month': activeYMD.slice(0, -3),
|
|
// `YYYY-MM`, mainly for testing
|
|
'aria-roledescription': this.labelCalendar || null,
|
|
'aria-labelledby': gridCaptionId,
|
|
'aria-describedby': gridHelpId,
|
|
// `aria-readonly` is not considered valid on `role="application"`
|
|
// https://www.w3.org/TR/wai-aria-1.1/#aria-readonly
|
|
// 'aria-readonly': this.readonly && !disabled ? 'true' : null,
|
|
'aria-disabled': disabled ? 'true' : null,
|
|
'aria-activedescendant': activeId
|
|
},
|
|
on: {
|
|
keydown: this.onKeydownGrid,
|
|
focus: this.setGridFocusFlag,
|
|
blur: this.setGridFocusFlag
|
|
}
|
|
}, [$gridCaption, $gridWeekDays, $gridBody, $gridHelp]); // Optional bottom slot
|
|
|
|
var $slot = this.normalizeSlot();
|
|
$slot = $slot ? h('footer', {
|
|
staticClass: 'b-calendar-footer'
|
|
}, $slot) : h();
|
|
var $widget = h('div', {
|
|
staticClass: 'b-calendar-inner',
|
|
style: this.block ? {} : {
|
|
width: this.width
|
|
},
|
|
attrs: {
|
|
id: widgetId,
|
|
dir: isRTL ? 'rtl' : 'ltr',
|
|
lang: this.computedLocale || null,
|
|
role: 'group',
|
|
'aria-disabled': disabled ? 'true' : null,
|
|
// If datepicker controls an input, this will specify the ID of the input
|
|
'aria-controls': this.ariaControls || null,
|
|
// This should be a prop (so it can be changed to Date picker, etc, localized
|
|
'aria-roledescription': this.roleDescription || null,
|
|
'aria-describedby': [// Should the attr (if present) go last?
|
|
// Or should this attr be a prop?
|
|
this.bvAttrs['aria-describedby'], valueId, gridHelpId].filter(identity).join(' ')
|
|
},
|
|
on: {
|
|
keydown: this.onKeydownWrapper
|
|
}
|
|
}, [$header, $nav, $grid, $slot]); // Wrap in an outer div that can be styled
|
|
|
|
return h('div', {
|
|
staticClass: 'b-calendar',
|
|
class: {
|
|
'd-block': this.block
|
|
}
|
|
}, [$widget]);
|
|
}
|
|
});
|
|
|
|
var CalendarPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BCalendar: BCalendar
|
|
}
|
|
});
|
|
|
|
var props$8 = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
bgVariant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
borderVariant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
textVariant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_CARD); // --- Mixin ---
|
|
|
|
var props$9 = makePropsConfigurable({
|
|
title: {
|
|
type: String // default: null
|
|
|
|
},
|
|
titleTag: {
|
|
type: String,
|
|
default: 'h4'
|
|
}
|
|
}, NAME_CARD_TITLE); // @vue/component
|
|
|
|
var BCardTitle = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_TITLE,
|
|
functional: true,
|
|
props: props$9,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.titleTag, a(data, {
|
|
staticClass: 'card-title'
|
|
}), children || toString$1(props.title));
|
|
}
|
|
});
|
|
|
|
var props$a = makePropsConfigurable({
|
|
subTitle: {
|
|
type: String // default: null
|
|
|
|
},
|
|
subTitleTag: {
|
|
type: String,
|
|
default: 'h6'
|
|
},
|
|
subTitleTextVariant: {
|
|
type: String,
|
|
default: 'muted'
|
|
}
|
|
}, NAME_CARD_SUB_TITLE); // @vue/component
|
|
|
|
var BCardSubTitle = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_SUB_TITLE,
|
|
functional: true,
|
|
props: props$a,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.subTitleTag, a(data, {
|
|
staticClass: 'card-subtitle',
|
|
class: [props.subTitleTextVariant ? "text-".concat(props.subTitleTextVariant) : null]
|
|
}), children || toString$1(props.subTitle));
|
|
}
|
|
});
|
|
|
|
var props$b = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, copyProps(props$8, prefixPropName.bind(null, 'body'))), {}, {
|
|
bodyClass: {
|
|
type: [String, Object, Array] // default: null
|
|
|
|
}
|
|
}, props$9), props$a), {}, {
|
|
overlay: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_CARD_BODY); // @vue/component
|
|
|
|
var BCardBody = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_BODY,
|
|
functional: true,
|
|
props: props$b,
|
|
render: function render(h, _ref) {
|
|
var _ref2;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var cardTitle = h();
|
|
var cardSubTitle = h();
|
|
var cardContent = children || [h()];
|
|
|
|
if (props.title) {
|
|
cardTitle = h(BCardTitle, {
|
|
props: pluckProps(props$9, props)
|
|
});
|
|
}
|
|
|
|
if (props.subTitle) {
|
|
cardSubTitle = h(BCardSubTitle, {
|
|
props: pluckProps(props$a, props),
|
|
class: ['mb-2']
|
|
});
|
|
}
|
|
|
|
return h(props.bodyTag, a(data, {
|
|
staticClass: 'card-body',
|
|
class: [(_ref2 = {
|
|
'card-img-overlay': props.overlay
|
|
}, _defineProperty(_ref2, "bg-".concat(props.bodyBgVariant), props.bodyBgVariant), _defineProperty(_ref2, "border-".concat(props.bodyBorderVariant), props.bodyBorderVariant), _defineProperty(_ref2, "text-".concat(props.bodyTextVariant), props.bodyTextVariant), _ref2), props.bodyClass || {}]
|
|
}), [cardTitle, cardSubTitle].concat(_toConsumableArray(cardContent)));
|
|
}
|
|
});
|
|
|
|
var props$c = makePropsConfigurable(_objectSpread2(_objectSpread2({}, copyProps(props$8, prefixPropName.bind(null, 'header'))), {}, {
|
|
header: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerHtml: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerClass: {
|
|
type: [String, Object, Array] // default: null
|
|
|
|
}
|
|
}), NAME_CARD_HEADER); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCardHeader = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_HEADER,
|
|
functional: true,
|
|
props: props$c,
|
|
render: function render(h, _ref) {
|
|
var _ref2;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var headerBgVariant = props.headerBgVariant,
|
|
headerBorderVariant = props.headerBorderVariant,
|
|
headerTextVariant = props.headerTextVariant;
|
|
return h(props.headerTag, a(data, {
|
|
staticClass: 'card-header',
|
|
class: [props.headerClass, (_ref2 = {}, _defineProperty(_ref2, "bg-".concat(headerBgVariant), headerBgVariant), _defineProperty(_ref2, "border-".concat(headerBorderVariant), headerBorderVariant), _defineProperty(_ref2, "text-".concat(headerTextVariant), headerTextVariant), _ref2)],
|
|
domProps: children ? {} : htmlOrText(props.headerHtml, props.header)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$d = makePropsConfigurable(_objectSpread2(_objectSpread2({}, copyProps(props$8, prefixPropName.bind(null, 'footer'))), {}, {
|
|
footer: {
|
|
type: String // default: null
|
|
|
|
},
|
|
footerHtml: {
|
|
type: String // default: null
|
|
|
|
},
|
|
footerClass: {
|
|
type: [String, Object, Array] // default: null
|
|
|
|
}
|
|
}), NAME_CARD_FOOTER); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCardFooter = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_FOOTER,
|
|
functional: true,
|
|
props: props$d,
|
|
render: function render(h, _ref) {
|
|
var _ref2;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var footerBgVariant = props.footerBgVariant,
|
|
footerBorderVariant = props.footerBorderVariant,
|
|
footerTextVariant = props.footerTextVariant;
|
|
return h(props.footerTag, a(data, {
|
|
staticClass: 'card-footer',
|
|
class: [props.footerClass, (_ref2 = {}, _defineProperty(_ref2, "bg-".concat(footerBgVariant), footerBgVariant), _defineProperty(_ref2, "border-".concat(footerBorderVariant), footerBorderVariant), _defineProperty(_ref2, "text-".concat(footerTextVariant), footerTextVariant), _ref2)],
|
|
domProps: children ? {} : htmlOrText(props.footerHtml, props.footer)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$e = makePropsConfigurable({
|
|
src: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
alt: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
top: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
bottom: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
start: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
left: {
|
|
// alias of 'start'
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
end: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
right: {
|
|
// alias of 'end'
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
height: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
width: {
|
|
type: [Number, String] // default: null
|
|
|
|
}
|
|
}, NAME_CARD_IMG); // @vue/component
|
|
|
|
var BCardImg = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_IMG,
|
|
functional: true,
|
|
props: props$e,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data;
|
|
var baseClass = 'card-img';
|
|
|
|
if (props.top) {
|
|
baseClass += '-top';
|
|
} else if (props.right || props.end) {
|
|
baseClass += '-right';
|
|
} else if (props.bottom) {
|
|
baseClass += '-bottom';
|
|
} else if (props.left || props.start) {
|
|
baseClass += '-left';
|
|
}
|
|
|
|
return h('img', a(data, {
|
|
class: [baseClass],
|
|
attrs: {
|
|
src: props.src || null,
|
|
alt: props.alt,
|
|
height: props.height || null,
|
|
width: props.width || null
|
|
}
|
|
}));
|
|
}
|
|
});
|
|
|
|
var cardImgProps = copyProps(props$e, prefixPropName.bind(null, 'img'));
|
|
cardImgProps.imgSrc.required = false;
|
|
var props$f = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$b), props$c), props$d), cardImgProps), props$8), {}, {
|
|
align: {
|
|
type: String // default: null
|
|
|
|
},
|
|
noBody: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_CARD); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCard = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD,
|
|
functional: true,
|
|
props: props$f,
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var imgSrc = props.imgSrc,
|
|
imgLeft = props.imgLeft,
|
|
imgRight = props.imgRight,
|
|
imgStart = props.imgStart,
|
|
imgEnd = props.imgEnd,
|
|
imgBottom = props.imgBottom,
|
|
header = props.header,
|
|
headerHtml = props.headerHtml,
|
|
footer = props.footer,
|
|
footerHtml = props.footerHtml,
|
|
align = props.align,
|
|
textVariant = props.textVariant,
|
|
bgVariant = props.bgVariant,
|
|
borderVariant = props.borderVariant;
|
|
var $scopedSlots = scopedSlots || {};
|
|
var $slots = slots();
|
|
var slotScope = {};
|
|
var $imgFirst = h();
|
|
var $imgLast = h();
|
|
|
|
if (imgSrc) {
|
|
var $img = h(BCardImg, {
|
|
props: pluckProps(cardImgProps, props, unprefixPropName.bind(null, 'img'))
|
|
});
|
|
|
|
if (imgBottom) {
|
|
$imgLast = $img;
|
|
} else {
|
|
$imgFirst = $img;
|
|
}
|
|
}
|
|
|
|
var $header = h();
|
|
var hasHeaderSlot = hasNormalizedSlot(SLOT_NAME_HEADER, $scopedSlots, $slots);
|
|
|
|
if (hasHeaderSlot || header || headerHtml) {
|
|
$header = h(BCardHeader, {
|
|
props: pluckProps(props$c, props),
|
|
domProps: hasHeaderSlot ? {} : htmlOrText(headerHtml, header)
|
|
}, normalizeSlot(SLOT_NAME_HEADER, slotScope, $scopedSlots, $slots));
|
|
}
|
|
|
|
var $content = normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots); // Wrap content in `<card-body>` when `noBody` prop set
|
|
|
|
if (!props.noBody) {
|
|
$content = h(BCardBody, {
|
|
props: pluckProps(props$b, props)
|
|
}, $content); // When the `overlap` prop is set we need to wrap the `<b-card-img>` and `<b-card-body>`
|
|
// into a relative positioned wrapper to don't distract a potential header or footer
|
|
|
|
if (props.overlay && imgSrc) {
|
|
$content = h('div', {
|
|
staticClass: 'position-relative'
|
|
}, [$imgFirst, $content, $imgLast]); // Reset image variables since they are already in the wrapper
|
|
|
|
$imgFirst = h();
|
|
$imgLast = h();
|
|
}
|
|
}
|
|
|
|
var $footer = h();
|
|
var hasFooterSlot = hasNormalizedSlot(SLOT_NAME_FOOTER, $scopedSlots, $slots);
|
|
|
|
if (hasFooterSlot || footer || footerHtml) {
|
|
$footer = h(BCardFooter, {
|
|
props: pluckProps(props$d, props),
|
|
domProps: hasHeaderSlot ? {} : htmlOrText(footerHtml, footer)
|
|
}, normalizeSlot(SLOT_NAME_FOOTER, slotScope, $scopedSlots, $slots));
|
|
}
|
|
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'card',
|
|
class: (_class = {
|
|
'flex-row': imgLeft || imgStart,
|
|
'flex-row-reverse': (imgRight || imgEnd) && !(imgLeft || imgStart)
|
|
}, _defineProperty(_class, "text-".concat(align), align), _defineProperty(_class, "bg-".concat(bgVariant), bgVariant), _defineProperty(_class, "border-".concat(borderVariant), borderVariant), _defineProperty(_class, "text-".concat(textVariant), textVariant), _class)
|
|
}), [$imgFirst, $header, $content, $footer, $imgLast]);
|
|
}
|
|
});
|
|
|
|
var OBSERVER_PROP_NAME = '__bv__visibility_observer';
|
|
|
|
var VisibilityObserver = /*#__PURE__*/function () {
|
|
function VisibilityObserver(el, options, vnode) {
|
|
_classCallCheck(this, VisibilityObserver);
|
|
|
|
this.el = el;
|
|
this.callback = options.callback;
|
|
this.margin = options.margin || 0;
|
|
this.once = options.once || false;
|
|
this.observer = null;
|
|
this.visible = undefined;
|
|
this.doneOnce = false; // Create the observer instance (if possible)
|
|
|
|
this.createObserver(vnode);
|
|
}
|
|
|
|
_createClass(VisibilityObserver, [{
|
|
key: "createObserver",
|
|
value: function createObserver(vnode) {
|
|
var _this = this;
|
|
|
|
// Remove any previous observer
|
|
if (this.observer) {
|
|
/* istanbul ignore next */
|
|
this.stop();
|
|
} // Should only be called once and `callback` prop should be a function
|
|
|
|
|
|
if (this.doneOnce || !isFunction(this.callback)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Create the observer instance
|
|
|
|
|
|
try {
|
|
// Future: Possibly add in other modifiers for left/right/top/bottom
|
|
// offsets, root element reference, and thresholds
|
|
this.observer = new IntersectionObserver(this.handler.bind(this), {
|
|
// `null` = 'viewport'
|
|
root: null,
|
|
// Pixels away from view port to consider "visible"
|
|
rootMargin: this.margin,
|
|
// Intersection ratio of el and root (as a value from 0 to 1)
|
|
threshold: 0
|
|
});
|
|
} catch (_unused) {
|
|
// No IntersectionObserver support, so just stop trying to observe
|
|
this.doneOnce = true;
|
|
this.observer = undefined;
|
|
this.callback(null);
|
|
return;
|
|
} // Start observing in a `$nextTick()` (to allow DOM to complete rendering)
|
|
|
|
/* istanbul ignore next: IntersectionObserver not supported in JSDOM */
|
|
|
|
|
|
vnode.context.$nextTick(function () {
|
|
requestAF(function () {
|
|
// Placed in an `if` just in case we were destroyed before
|
|
// this `requestAnimationFrame` runs
|
|
if (_this.observer) {
|
|
_this.observer.observe(_this.el);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
}, {
|
|
key: "handler",
|
|
value: function handler(entries) {
|
|
var entry = entries ? entries[0] : {};
|
|
var isIntersecting = Boolean(entry.isIntersecting || entry.intersectionRatio > 0.0);
|
|
|
|
if (isIntersecting !== this.visible) {
|
|
this.visible = isIntersecting;
|
|
this.callback(isIntersecting);
|
|
|
|
if (this.once && this.visible) {
|
|
this.doneOnce = true;
|
|
this.stop();
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: "stop",
|
|
value: function stop() {
|
|
/* istanbul ignore next */
|
|
this.observer && this.observer.disconnect();
|
|
this.observer = null;
|
|
}
|
|
}]);
|
|
|
|
return VisibilityObserver;
|
|
}();
|
|
|
|
var destroy = function destroy(el) {
|
|
var observer = el[OBSERVER_PROP_NAME];
|
|
|
|
if (observer && observer.stop) {
|
|
observer.stop();
|
|
}
|
|
|
|
delete el[OBSERVER_PROP_NAME];
|
|
};
|
|
|
|
var bind = function bind(el, _ref, vnode) {
|
|
var value = _ref.value,
|
|
modifiers = _ref.modifiers;
|
|
// `value` is the callback function
|
|
var options = {
|
|
margin: '0px',
|
|
once: false,
|
|
callback: value
|
|
}; // Parse modifiers
|
|
|
|
keys(modifiers).forEach(function (mod) {
|
|
/* istanbul ignore else: Until <b-img-lazy> is switched to use this directive */
|
|
if (RX_DIGITS.test(mod)) {
|
|
options.margin = "".concat(mod, "px");
|
|
} else if (mod.toLowerCase() === 'once') {
|
|
options.once = true;
|
|
}
|
|
}); // Destroy any previous observer
|
|
|
|
destroy(el); // Create new observer
|
|
|
|
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, vnode); // Store the current modifiers on the object (cloned)
|
|
|
|
el[OBSERVER_PROP_NAME]._prevModifiers = clone(modifiers);
|
|
}; // When the directive options may have been updated (or element)
|
|
|
|
|
|
var componentUpdated = function componentUpdated(el, _ref2, vnode) {
|
|
var value = _ref2.value,
|
|
oldValue = _ref2.oldValue,
|
|
modifiers = _ref2.modifiers;
|
|
// Compare value/oldValue and modifiers to see if anything has changed
|
|
// and if so, destroy old observer and create new observer
|
|
|
|
/* istanbul ignore next */
|
|
modifiers = clone(modifiers);
|
|
/* istanbul ignore next */
|
|
|
|
if (el && (value !== oldValue || !el[OBSERVER_PROP_NAME] || !looseEqual(modifiers, el[OBSERVER_PROP_NAME]._prevModifiers))) {
|
|
// Re-bind on element
|
|
bind(el, {
|
|
value: value,
|
|
modifiers: modifiers
|
|
}, vnode);
|
|
}
|
|
}; // When directive un-binds from element
|
|
|
|
|
|
var unbind = function unbind(el) {
|
|
// Remove the observer
|
|
destroy(el);
|
|
}; // Export the directive
|
|
|
|
|
|
var VBVisible = {
|
|
bind: bind,
|
|
componentUpdated: componentUpdated,
|
|
unbind: unbind
|
|
};
|
|
|
|
// Blank image with fill template
|
|
|
|
var BLANK_TEMPLATE = '<svg width="%{w}" height="%{h}" ' + 'xmlns="http://www.w3.org/2000/svg" ' + 'viewBox="0 0 %{w} %{h}" preserveAspectRatio="none">' + '<rect width="100%" height="100%" style="fill:%{f};"></rect>' + '</svg>';
|
|
var props$g = makePropsConfigurable({
|
|
src: {
|
|
type: String // default: null
|
|
|
|
},
|
|
srcset: {
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
sizes: {
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
alt: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
width: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
height: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
block: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fluid: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fluidGrow: {
|
|
// Gives fluid images class `w-100` to make them grow to fit container
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
rounded: {
|
|
// rounded can be:
|
|
// false: no rounding of corners
|
|
// true: slightly rounded corners
|
|
// 'top': top corners rounded
|
|
// 'right': right corners rounded
|
|
// 'bottom': bottom corners rounded
|
|
// 'left': left corners rounded
|
|
// 'circle': circle/oval
|
|
// '0': force rounding off
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
thumbnail: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
left: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
right: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
center: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
blank: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
blankColor: {
|
|
type: String,
|
|
default: 'transparent'
|
|
}
|
|
}, NAME_IMG); // --- Helper methods ---
|
|
|
|
var makeBlankImgSrc = function makeBlankImgSrc(width, height, color) {
|
|
var src = encodeURIComponent(BLANK_TEMPLATE.replace('%{w}', toString$1(width)).replace('%{h}', toString$1(height)).replace('%{f}', color));
|
|
return "data:image/svg+xml;charset=UTF-8,".concat(src);
|
|
}; // @vue/component
|
|
|
|
|
|
var BImg = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_IMG,
|
|
functional: true,
|
|
props: props$g,
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data;
|
|
var src = props.src;
|
|
var width = toInteger(props.width) || null;
|
|
var height = toInteger(props.height) || null;
|
|
var align = null;
|
|
var block = props.block;
|
|
var srcset = concat(props.srcset).filter(identity).join(',');
|
|
var sizes = concat(props.sizes).filter(identity).join(',');
|
|
|
|
if (props.blank) {
|
|
if (!height && width) {
|
|
height = width;
|
|
} else if (!width && height) {
|
|
width = height;
|
|
}
|
|
|
|
if (!width && !height) {
|
|
width = 1;
|
|
height = 1;
|
|
} // Make a blank SVG image
|
|
|
|
|
|
src = makeBlankImgSrc(width, height, props.blankColor || 'transparent'); // Disable srcset and sizes
|
|
|
|
srcset = null;
|
|
sizes = null;
|
|
}
|
|
|
|
if (props.left) {
|
|
align = 'float-left';
|
|
} else if (props.right) {
|
|
align = 'float-right';
|
|
} else if (props.center) {
|
|
align = 'mx-auto';
|
|
block = true;
|
|
}
|
|
|
|
return h('img', a(data, {
|
|
attrs: {
|
|
src: src,
|
|
alt: props.alt,
|
|
width: width ? toString$1(width) : null,
|
|
height: height ? toString$1(height) : null,
|
|
srcset: srcset || null,
|
|
sizes: sizes || null
|
|
},
|
|
class: (_class = {
|
|
'img-thumbnail': props.thumbnail,
|
|
'img-fluid': props.fluid || props.fluidGrow,
|
|
'w-100': props.fluidGrow,
|
|
rounded: props.rounded === '' || props.rounded === true
|
|
}, _defineProperty(_class, "rounded-".concat(props.rounded), isString(props.rounded) && props.rounded !== ''), _defineProperty(_class, align, align), _defineProperty(_class, 'd-block', block), _class)
|
|
}));
|
|
}
|
|
});
|
|
|
|
var props$h = makePropsConfigurable(_objectSpread2(_objectSpread2({}, omit(props$g, ['blank'])), {}, {
|
|
blankSrc: {
|
|
// If null, a blank image is generated
|
|
type: String,
|
|
default: null
|
|
},
|
|
blankColor: {
|
|
type: String,
|
|
default: 'transparent'
|
|
},
|
|
blankWidth: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
blankHeight: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
show: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
offset: {
|
|
// Distance away from viewport (in pixels) before being
|
|
// considered "visible"
|
|
type: [Number, String],
|
|
default: 360
|
|
}
|
|
}), NAME_IMG_LAZY); // @vue/component
|
|
|
|
var BImgLazy = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_IMG_LAZY,
|
|
directives: {
|
|
bVisible: VBVisible
|
|
},
|
|
props: props$h,
|
|
data: function data() {
|
|
return {
|
|
isShown: this.show
|
|
};
|
|
},
|
|
computed: {
|
|
computedSrc: function computedSrc() {
|
|
return !this.blankSrc || this.isShown ? this.src : this.blankSrc;
|
|
},
|
|
computedBlank: function computedBlank() {
|
|
return !(this.isShown || this.blankSrc);
|
|
},
|
|
computedWidth: function computedWidth() {
|
|
return this.isShown ? this.width : this.blankWidth || this.width;
|
|
},
|
|
computedHeight: function computedHeight() {
|
|
return this.isShown ? this.height : this.blankHeight || this.height;
|
|
},
|
|
computedSrcset: function computedSrcset() {
|
|
var srcset = concat(this.srcset).filter(identity).join(',');
|
|
return !this.blankSrc || this.isShown ? srcset : null;
|
|
},
|
|
computedSizes: function computedSizes() {
|
|
var sizes = concat(this.sizes).filter(identity).join(',');
|
|
return !this.blankSrc || this.isShown ? sizes : null;
|
|
}
|
|
},
|
|
watch: {
|
|
show: function show(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
// If IntersectionObserver support is not available, image is always shown
|
|
var visible = hasIntersectionObserverSupport ? newVal : true;
|
|
this.isShown = visible;
|
|
|
|
if (visible !== newVal) {
|
|
// Ensure the show prop is synced (when no IntersectionObserver)
|
|
this.$nextTick(this.updateShowProp);
|
|
}
|
|
}
|
|
},
|
|
isShown: function isShown(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
// Update synched show prop
|
|
this.updateShowProp();
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
// If IntersectionObserver is not available, image is always shown
|
|
this.isShown = hasIntersectionObserverSupport ? this.show : true;
|
|
},
|
|
methods: {
|
|
updateShowProp: function updateShowProp() {
|
|
this.$emit('update:show', this.isShown);
|
|
},
|
|
doShow: function doShow(visible) {
|
|
// If IntersectionObserver is not supported, the callback
|
|
// will be called with `null` rather than `true` or `false`
|
|
if ((visible || visible === null) && !this.isShown) {
|
|
this.isShown = true;
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var directives = [];
|
|
|
|
if (!this.isShown) {
|
|
var _modifiers;
|
|
|
|
// We only add the visible directive if we are not shown
|
|
directives.push({
|
|
// Visible directive will silently do nothing if
|
|
// IntersectionObserver is not supported
|
|
name: 'b-visible',
|
|
// Value expects a callback (passed one arg of `visible` = `true` or `false`)
|
|
value: this.doShow,
|
|
modifiers: (_modifiers = {}, _defineProperty(_modifiers, "".concat(toInteger(this.offset, 0)), true), _defineProperty(_modifiers, "once", true), _modifiers)
|
|
});
|
|
}
|
|
|
|
return h(BImg, {
|
|
directives: directives,
|
|
props: {
|
|
// Computed value props
|
|
src: this.computedSrc,
|
|
blank: this.computedBlank,
|
|
width: this.computedWidth,
|
|
height: this.computedHeight,
|
|
srcset: this.computedSrcset || null,
|
|
sizes: this.computedSizes || null,
|
|
// Passthrough props
|
|
alt: this.alt,
|
|
blankColor: this.blankColor,
|
|
fluid: this.fluid,
|
|
fluidGrow: this.fluidGrow,
|
|
block: this.block,
|
|
thumbnail: this.thumbnail,
|
|
rounded: this.rounded,
|
|
left: this.left,
|
|
right: this.right,
|
|
center: this.center
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// The `omit()` util creates a new object, so we can just pass the original props
|
|
|
|
var lazyProps = omit(props$h, ['left', 'right', 'center', 'block', 'rounded', 'thumbnail', 'fluid', 'fluidGrow']);
|
|
var props$i = makePropsConfigurable(_objectSpread2(_objectSpread2({}, lazyProps), {}, {
|
|
top: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
bottom: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
start: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
left: {
|
|
// alias of 'start'
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
end: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
right: {
|
|
// alias of 'end'
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_CARD_IMG_LAZY); // @vue/component
|
|
|
|
var BCardImgLazy = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_IMG_LAZY,
|
|
functional: true,
|
|
props: props$i,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data;
|
|
var baseClass = 'card-img';
|
|
|
|
if (props.top) {
|
|
baseClass += '-top';
|
|
} else if (props.right || props.end) {
|
|
baseClass += '-right';
|
|
} else if (props.bottom) {
|
|
baseClass += '-bottom';
|
|
} else if (props.left || props.start) {
|
|
baseClass += '-left';
|
|
} // False out the left/center/right props before passing to b-img-lazy
|
|
|
|
|
|
var lazyProps = _objectSpread2(_objectSpread2({}, props), {}, {
|
|
left: false,
|
|
right: false,
|
|
center: false
|
|
});
|
|
|
|
return h(BImgLazy, a(data, {
|
|
class: [baseClass],
|
|
props: lazyProps
|
|
}));
|
|
}
|
|
});
|
|
|
|
var props$j = makePropsConfigurable({
|
|
textTag: {
|
|
type: String,
|
|
default: 'p'
|
|
}
|
|
}, NAME_CARD_TEXT); // @vue/component
|
|
|
|
var BCardText = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_TEXT,
|
|
functional: true,
|
|
props: props$j,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.textTag, a(data, {
|
|
staticClass: 'card-text'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$k = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
deck: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
columns: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_CARD_GROUP); // @vue/component
|
|
|
|
var BCardGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CARD_GROUP,
|
|
functional: true,
|
|
props: props$k,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
class: props.deck ? 'card-deck' : props.columns ? 'card-columns' : 'card-group'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var CardPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BCard: BCard,
|
|
BCardHeader: BCardHeader,
|
|
BCardBody: BCardBody,
|
|
BCardTitle: BCardTitle,
|
|
BCardSubTitle: BCardSubTitle,
|
|
BCardFooter: BCardFooter,
|
|
BCardImg: BCardImg,
|
|
BCardImgLazy: BCardImgLazy,
|
|
BCardText: BCardText,
|
|
BCardGroup: BCardGroup
|
|
}
|
|
});
|
|
|
|
var EVENT_OPTIONS_PASSIVE = {
|
|
passive: true
|
|
};
|
|
var EVENT_OPTIONS_NO_CAPTURE = {
|
|
passive: true,
|
|
capture: false
|
|
};
|
|
|
|
var noop = function noop() {};
|
|
|
|
/**
|
|
* Observe a DOM element changes, falls back to eventListener mode
|
|
* @param {Element} el The DOM element to observe
|
|
* @param {Function} callback callback to be called on change
|
|
* @param {object} [options={childList: true, subtree: true}] observe options
|
|
* @see https://stackoverflow.com/questions/3219758
|
|
*/
|
|
|
|
var observeDom = function observeDom(el, callback, options)
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
{
|
|
// Handle cases where we might be passed a Vue instance
|
|
el = el ? el.$el || el : null; // Early exit when we have no element
|
|
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
|
|
if (!isElement(el)) {
|
|
return null;
|
|
} // Exit and throw a warning when `MutationObserver` isn't available
|
|
|
|
|
|
if (warnNoMutationObserverSupport('observeDom')) {
|
|
return null;
|
|
} // Define a new observer
|
|
|
|
|
|
var obs = new MutationObs(function (mutations) {
|
|
var changed = false; // A mutation can contain several change records, so we loop
|
|
// through them to see what has changed
|
|
// We break out of the loop early if any "significant" change
|
|
// has been detected
|
|
|
|
for (var i = 0; i < mutations.length && !changed; i++) {
|
|
// The mutation record
|
|
var mutation = mutations[i]; // Mutation type
|
|
|
|
var type = mutation.type; // DOM node (could be any DOM node type - HTMLElement, Text, comment, etc.)
|
|
|
|
var target = mutation.target; // Detect whether a change happened based on type and target
|
|
|
|
if (type === 'characterData' && target.nodeType === Node.TEXT_NODE) {
|
|
// We ignore nodes that are not TEXT (i.e. comments, etc.)
|
|
// as they don't change layout
|
|
changed = true;
|
|
} else if (type === 'attributes') {
|
|
changed = true;
|
|
} else if (type === 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
|
|
// This includes HTMLElement and text nodes being
|
|
// added/removed/re-arranged
|
|
changed = true;
|
|
}
|
|
} // We only call the callback if a change that could affect
|
|
// layout/size truly happened
|
|
|
|
|
|
if (changed) {
|
|
callback();
|
|
}
|
|
}); // Have the observer observe foo for changes in children, etc
|
|
|
|
obs.observe(el, _objectSpread2({
|
|
childList: true,
|
|
subtree: true
|
|
}, options)); // We return a reference to the observer so that `obs.disconnect()`
|
|
// can be called if necessary
|
|
// To reduce overhead when the root element is hidden
|
|
|
|
return obs;
|
|
};
|
|
|
|
var DIRECTION = {
|
|
next: {
|
|
dirClass: 'carousel-item-left',
|
|
overlayClass: 'carousel-item-next'
|
|
},
|
|
prev: {
|
|
dirClass: 'carousel-item-right',
|
|
overlayClass: 'carousel-item-prev'
|
|
}
|
|
}; // Fallback Transition duration (with a little buffer) in ms
|
|
|
|
var TRANS_DURATION = 600 + 50; // Time for mouse compat events to fire after touch
|
|
|
|
var TOUCH_EVENT_COMPAT_WAIT = 500; // Number of pixels to consider touch move a swipe
|
|
|
|
var SWIPE_THRESHOLD = 40; // PointerEvent pointer types
|
|
|
|
var PointerType = {
|
|
TOUCH: 'touch',
|
|
PEN: 'pen'
|
|
}; // Transition Event names
|
|
|
|
var TransitionEndEvents = {
|
|
WebkitTransition: 'webkitTransitionEnd',
|
|
MozTransition: 'transitionend',
|
|
OTransition: 'otransitionend oTransitionEnd',
|
|
transition: 'transitionend'
|
|
}; // Return the browser specific transitionEnd event name
|
|
|
|
var getTransitionEndEvent = function getTransitionEndEvent(el) {
|
|
for (var name in TransitionEndEvents) {
|
|
if (!isUndefined(el.style[name])) {
|
|
return TransitionEndEvents[name];
|
|
}
|
|
} // Fallback
|
|
|
|
/* istanbul ignore next */
|
|
|
|
|
|
return null;
|
|
}; // @vue/component
|
|
|
|
|
|
var BCarousel = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CAROUSEL,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvCarousel: this
|
|
};
|
|
},
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable({
|
|
labelPrev: {
|
|
type: String,
|
|
default: 'Previous slide'
|
|
},
|
|
labelNext: {
|
|
type: String,
|
|
default: 'Next slide'
|
|
},
|
|
labelGotoSlide: {
|
|
type: String,
|
|
default: 'Goto slide'
|
|
},
|
|
labelIndicators: {
|
|
type: String,
|
|
default: 'Select a slide to display'
|
|
},
|
|
interval: {
|
|
type: Number,
|
|
default: 5000
|
|
},
|
|
indicators: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
controls: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noAnimation: {
|
|
// Disable slide/fade animation
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fade: {
|
|
// Enable cross-fade animation instead of slide animation
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noWrap: {
|
|
// Disable wrapping/looping when start/end is reached
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noTouch: {
|
|
// Sniffed by carousel-slide
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noHoverPause: {
|
|
// Disable pause on hover
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
imgWidth: {
|
|
// Sniffed by carousel-slide
|
|
type: [Number, String] // default: undefined
|
|
|
|
},
|
|
imgHeight: {
|
|
// Sniffed by carousel-slide
|
|
type: [Number, String] // default: undefined
|
|
|
|
},
|
|
background: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
value: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
}, NAME_CAROUSEL),
|
|
data: function data() {
|
|
return {
|
|
index: this.value || 0,
|
|
isSliding: false,
|
|
transitionEndEvent: null,
|
|
slides: [],
|
|
direction: null,
|
|
isPaused: !(toInteger(this.interval, 0) > 0),
|
|
// Touch event handling values
|
|
touchStartX: 0,
|
|
touchDeltaX: 0
|
|
};
|
|
},
|
|
computed: {
|
|
numSlides: function numSlides() {
|
|
return this.slides.length;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.setSlide(toInteger(newVal, 0));
|
|
}
|
|
},
|
|
interval: function interval(newVal, oldVal) {
|
|
if (newVal === oldVal) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
if (!newVal) {
|
|
// Pausing slide show
|
|
this.pause(false);
|
|
} else {
|
|
// Restarting or Changing interval
|
|
this.pause(true);
|
|
this.start(false);
|
|
}
|
|
},
|
|
isPaused: function isPaused(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.$emit(newVal ? 'paused' : 'unpaused');
|
|
}
|
|
},
|
|
index: function index(to, from) {
|
|
if (to === from || this.isSliding) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
this.doSlide(to, from);
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_interval = null;
|
|
this.$_animationTimeout = null;
|
|
this.$_touchTimeout = null;
|
|
this.$_observer = null; // Set initial paused state
|
|
|
|
this.isPaused = !(toInteger(this.interval, 0) > 0);
|
|
},
|
|
mounted: function mounted() {
|
|
// Cache current browser transitionend event name
|
|
this.transitionEndEvent = getTransitionEndEvent(this.$el) || null; // Get all slides
|
|
|
|
this.updateSlides(); // Observe child changes so we can update slide list
|
|
|
|
this.setObserver(true);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.clearInterval();
|
|
this.clearAnimationTimeout();
|
|
this.clearTouchTimeout();
|
|
this.setObserver(false);
|
|
},
|
|
methods: {
|
|
clearInterval: function (_clearInterval) {
|
|
function clearInterval() {
|
|
return _clearInterval.apply(this, arguments);
|
|
}
|
|
|
|
clearInterval.toString = function () {
|
|
return _clearInterval.toString();
|
|
};
|
|
|
|
return clearInterval;
|
|
}(function () {
|
|
clearInterval(this.$_interval);
|
|
this.$_interval = null;
|
|
}),
|
|
clearAnimationTimeout: function clearAnimationTimeout() {
|
|
clearTimeout(this.$_animationTimeout);
|
|
this.$_animationTimeout = null;
|
|
},
|
|
clearTouchTimeout: function clearTouchTimeout() {
|
|
clearTimeout(this.$_touchTimeout);
|
|
this.$_touchTimeout = null;
|
|
},
|
|
setObserver: function setObserver() {
|
|
var on = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
this.$_observer && this.$_observer.disconnect();
|
|
this.$_observer = null;
|
|
|
|
if (on) {
|
|
this.$_observer = observeDom(this.$refs.inner, this.updateSlides.bind(this), {
|
|
subtree: false,
|
|
childList: true,
|
|
attributes: true,
|
|
attributeFilter: ['id']
|
|
});
|
|
}
|
|
},
|
|
// Set slide
|
|
setSlide: function setSlide(slide) {
|
|
var _this = this;
|
|
|
|
var direction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
|
|
// Don't animate when page is not visible
|
|
|
|
/* istanbul ignore if: difficult to test */
|
|
if (isBrowser && document.visibilityState && document.hidden) {
|
|
return;
|
|
}
|
|
|
|
var noWrap = this.noWrap;
|
|
var numSlides = this.numSlides; // Make sure we have an integer (you never know!)
|
|
|
|
slide = mathFloor(slide); // Don't do anything if nothing to slide to
|
|
|
|
if (numSlides === 0) {
|
|
return;
|
|
} // Don't change slide while transitioning, wait until transition is done
|
|
|
|
|
|
if (this.isSliding) {
|
|
// Schedule slide after sliding complete
|
|
this.$once('sliding-end', function () {
|
|
// Wrap in `requestAF()` to allow the slide to properly finish to avoid glitching
|
|
requestAF(function () {
|
|
return _this.setSlide(slide, direction);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.direction = direction; // Set new slide index
|
|
// Wrap around if necessary (if no-wrap not enabled)
|
|
|
|
this.index = slide >= numSlides ? noWrap ? numSlides - 1 : 0 : slide < 0 ? noWrap ? 0 : numSlides - 1 : slide; // Ensure the v-model is synched up if no-wrap is enabled
|
|
// and user tried to slide pass either ends
|
|
|
|
if (noWrap && this.index !== slide && this.index !== this.value) {
|
|
this.$emit('input', this.index);
|
|
}
|
|
},
|
|
// Previous slide
|
|
prev: function prev() {
|
|
this.setSlide(this.index - 1, 'prev');
|
|
},
|
|
// Next slide
|
|
next: function next() {
|
|
this.setSlide(this.index + 1, 'next');
|
|
},
|
|
// Pause auto rotation
|
|
pause: function pause(evt) {
|
|
if (!evt) {
|
|
this.isPaused = true;
|
|
}
|
|
|
|
this.clearInterval();
|
|
},
|
|
// Start auto rotate slides
|
|
start: function start(evt) {
|
|
if (!evt) {
|
|
this.isPaused = false;
|
|
}
|
|
/* istanbul ignore next: most likely will never happen, but just in case */
|
|
|
|
|
|
this.clearInterval(); // Don't start if no interval, or less than 2 slides
|
|
|
|
if (this.interval && this.numSlides > 1) {
|
|
this.$_interval = setInterval(this.next, mathMax(1000, this.interval));
|
|
}
|
|
},
|
|
// Restart auto rotate slides when focus/hover leaves the carousel
|
|
|
|
/* istanbul ignore next */
|
|
restart: function restart() {
|
|
if (!this.$el.contains(getActiveElement())) {
|
|
this.start();
|
|
}
|
|
},
|
|
doSlide: function doSlide(to, from) {
|
|
var _this2 = this;
|
|
|
|
var isCycling = Boolean(this.interval); // Determine sliding direction
|
|
|
|
var direction = this.calcDirection(this.direction, from, to);
|
|
var overlayClass = direction.overlayClass;
|
|
var dirClass = direction.dirClass; // Determine current and next slides
|
|
|
|
var currentSlide = this.slides[from];
|
|
var nextSlide = this.slides[to]; // Don't do anything if there aren't any slides to slide to
|
|
|
|
if (!currentSlide || !nextSlide) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Start animating
|
|
|
|
|
|
this.isSliding = true;
|
|
|
|
if (isCycling) {
|
|
this.pause(false);
|
|
}
|
|
|
|
this.$emit('sliding-start', to); // Update v-model
|
|
|
|
this.$emit('input', this.index);
|
|
|
|
if (this.noAnimation) {
|
|
addClass(nextSlide, 'active');
|
|
removeClass(currentSlide, 'active');
|
|
this.isSliding = false; // Notify ourselves that we're done sliding (slid)
|
|
|
|
this.$nextTick(function () {
|
|
return _this2.$emit('sliding-end', to);
|
|
});
|
|
} else {
|
|
addClass(nextSlide, overlayClass); // Trigger a reflow of next slide
|
|
|
|
reflow(nextSlide);
|
|
addClass(currentSlide, dirClass);
|
|
addClass(nextSlide, dirClass); // Transition End handler
|
|
|
|
var called = false;
|
|
/* istanbul ignore next: difficult to test */
|
|
|
|
var onceTransEnd = function onceTransEnd() {
|
|
if (called) {
|
|
return;
|
|
}
|
|
|
|
called = true;
|
|
/* istanbul ignore if: transition events cant be tested in JSDOM */
|
|
|
|
if (_this2.transitionEndEvent) {
|
|
var events = _this2.transitionEndEvent.split(/\s+/);
|
|
|
|
events.forEach(function (evt) {
|
|
return eventOff(nextSlide, evt, onceTransEnd, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
}
|
|
|
|
_this2.clearAnimationTimeout();
|
|
|
|
removeClass(nextSlide, dirClass);
|
|
removeClass(nextSlide, overlayClass);
|
|
addClass(nextSlide, 'active');
|
|
removeClass(currentSlide, 'active');
|
|
removeClass(currentSlide, dirClass);
|
|
removeClass(currentSlide, overlayClass);
|
|
setAttr(currentSlide, 'aria-current', 'false');
|
|
setAttr(nextSlide, 'aria-current', 'true');
|
|
setAttr(currentSlide, 'aria-hidden', 'true');
|
|
setAttr(nextSlide, 'aria-hidden', 'false');
|
|
_this2.isSliding = false;
|
|
_this2.direction = null; // Notify ourselves that we're done sliding (slid)
|
|
|
|
_this2.$nextTick(function () {
|
|
return _this2.$emit('sliding-end', to);
|
|
});
|
|
}; // Set up transitionend handler
|
|
|
|
/* istanbul ignore if: transition events cant be tested in JSDOM */
|
|
|
|
|
|
if (this.transitionEndEvent) {
|
|
var events = this.transitionEndEvent.split(/\s+/);
|
|
events.forEach(function (event) {
|
|
return eventOn(nextSlide, event, onceTransEnd, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
} // Fallback to setTimeout()
|
|
|
|
|
|
this.$_animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION);
|
|
}
|
|
|
|
if (isCycling) {
|
|
this.start(false);
|
|
}
|
|
},
|
|
// Update slide list
|
|
updateSlides: function updateSlides() {
|
|
this.pause(true); // Get all slides as DOM elements
|
|
|
|
this.slides = selectAll('.carousel-item', this.$refs.inner);
|
|
var numSlides = this.slides.length; // Keep slide number in range
|
|
|
|
var index = mathMax(0, mathMin(mathFloor(this.index), numSlides - 1));
|
|
this.slides.forEach(function (slide, idx) {
|
|
var n = idx + 1;
|
|
|
|
if (idx === index) {
|
|
addClass(slide, 'active');
|
|
setAttr(slide, 'aria-current', 'true');
|
|
} else {
|
|
removeClass(slide, 'active');
|
|
setAttr(slide, 'aria-current', 'false');
|
|
}
|
|
|
|
setAttr(slide, 'aria-posinset', String(n));
|
|
setAttr(slide, 'aria-setsize', String(numSlides));
|
|
}); // Set slide as active
|
|
|
|
this.setSlide(index);
|
|
this.start(this.isPaused);
|
|
},
|
|
calcDirection: function calcDirection() {
|
|
var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
var curIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
var nextIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
|
|
if (!direction) {
|
|
return nextIndex > curIndex ? DIRECTION.next : DIRECTION.prev;
|
|
}
|
|
|
|
return DIRECTION[direction];
|
|
},
|
|
handleClick: function handleClick(evt, fn) {
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (evt.type === 'click' || keyCode === CODE_SPACE || keyCode === CODE_ENTER) {
|
|
stopEvent(evt);
|
|
fn();
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support touch events */
|
|
handleSwipe: function handleSwipe() {
|
|
var absDeltaX = mathAbs(this.touchDeltaX);
|
|
|
|
if (absDeltaX <= SWIPE_THRESHOLD) {
|
|
return;
|
|
}
|
|
|
|
var direction = absDeltaX / this.touchDeltaX; // Reset touch delta X
|
|
// https://github.com/twbs/bootstrap/pull/28558
|
|
|
|
this.touchDeltaX = 0;
|
|
|
|
if (direction > 0) {
|
|
// Swipe left
|
|
this.prev();
|
|
} else if (direction < 0) {
|
|
// Swipe right
|
|
this.next();
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support touch events */
|
|
touchStart: function touchStart(evt) {
|
|
if (hasPointerEventSupport && PointerType[evt.pointerType.toUpperCase()]) {
|
|
this.touchStartX = evt.clientX;
|
|
} else if (!hasPointerEventSupport) {
|
|
this.touchStartX = evt.touches[0].clientX;
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support touch events */
|
|
touchMove: function touchMove(evt) {
|
|
// Ensure swiping with one touch and not pinching
|
|
if (evt.touches && evt.touches.length > 1) {
|
|
this.touchDeltaX = 0;
|
|
} else {
|
|
this.touchDeltaX = evt.touches[0].clientX - this.touchStartX;
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support touch events */
|
|
touchEnd: function touchEnd(evt) {
|
|
if (hasPointerEventSupport && PointerType[evt.pointerType.toUpperCase()]) {
|
|
this.touchDeltaX = evt.clientX - this.touchStartX;
|
|
}
|
|
|
|
this.handleSwipe(); // If it's a touch-enabled device, mouseenter/leave are fired as
|
|
// part of the mouse compatibility events on first tap - the carousel
|
|
// would stop cycling until user tapped out of it;
|
|
// here, we listen for touchend, explicitly pause the carousel
|
|
// (as if it's the second time we tap on it, mouseenter compat event
|
|
// is NOT fired) and after a timeout (to allow for mouse compatibility
|
|
// events to fire) we explicitly restart cycling
|
|
|
|
this.pause(false);
|
|
this.clearTouchTimeout();
|
|
this.$_touchTimeout = setTimeout(this.start, TOUCH_EVENT_COMPAT_WAIT + mathMax(1000, this.interval));
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this3 = this;
|
|
|
|
// Wrapper for slides
|
|
var inner = h('div', {
|
|
ref: 'inner',
|
|
class: ['carousel-inner'],
|
|
attrs: {
|
|
id: this.safeId('__BV_inner_'),
|
|
role: 'list'
|
|
}
|
|
}, [this.normalizeSlot()]); // Prev and next controls
|
|
|
|
var controls = h();
|
|
|
|
if (this.controls) {
|
|
var prevHandler = function prevHandler(evt) {
|
|
/* istanbul ignore next */
|
|
if (!_this3.isSliding) {
|
|
_this3.handleClick(evt, _this3.prev);
|
|
} else {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
}
|
|
};
|
|
|
|
var nextHandler = function nextHandler(evt) {
|
|
/* istanbul ignore next */
|
|
if (!_this3.isSliding) {
|
|
_this3.handleClick(evt, _this3.next);
|
|
} else {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
}
|
|
};
|
|
|
|
controls = [h('a', {
|
|
class: ['carousel-control-prev'],
|
|
attrs: {
|
|
href: '#',
|
|
role: 'button',
|
|
'aria-controls': this.safeId('__BV_inner_'),
|
|
'aria-disabled': this.isSliding ? 'true' : null
|
|
},
|
|
on: {
|
|
click: prevHandler,
|
|
keydown: prevHandler
|
|
}
|
|
}, [h('span', {
|
|
class: ['carousel-control-prev-icon'],
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
}), h('span', {
|
|
class: ['sr-only']
|
|
}, [this.labelPrev])]), h('a', {
|
|
class: ['carousel-control-next'],
|
|
attrs: {
|
|
href: '#',
|
|
role: 'button',
|
|
'aria-controls': this.safeId('__BV_inner_'),
|
|
'aria-disabled': this.isSliding ? 'true' : null
|
|
},
|
|
on: {
|
|
click: nextHandler,
|
|
keydown: nextHandler
|
|
}
|
|
}, [h('span', {
|
|
class: ['carousel-control-next-icon'],
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
}), h('span', {
|
|
class: ['sr-only']
|
|
}, [this.labelNext])])];
|
|
} // Indicators
|
|
|
|
|
|
var indicators = h('ol', {
|
|
class: ['carousel-indicators'],
|
|
directives: [{
|
|
name: 'show',
|
|
rawName: 'v-show',
|
|
value: this.indicators,
|
|
expression: 'indicators'
|
|
}],
|
|
attrs: {
|
|
id: this.safeId('__BV_indicators_'),
|
|
'aria-hidden': this.indicators ? 'false' : 'true',
|
|
'aria-label': this.labelIndicators,
|
|
'aria-owns': this.safeId('__BV_inner_')
|
|
}
|
|
}, this.slides.map(function (slide, n) {
|
|
return h('li', {
|
|
key: "slide_".concat(n),
|
|
class: {
|
|
active: n === _this3.index
|
|
},
|
|
attrs: {
|
|
role: 'button',
|
|
id: _this3.safeId("__BV_indicator_".concat(n + 1, "_")),
|
|
tabindex: _this3.indicators ? '0' : '-1',
|
|
'aria-current': n === _this3.index ? 'true' : 'false',
|
|
'aria-label': "".concat(_this3.labelGotoSlide, " ").concat(n + 1),
|
|
'aria-describedby': _this3.slides[n].id || null,
|
|
'aria-controls': _this3.safeId('__BV_inner_')
|
|
},
|
|
on: {
|
|
click: function click(evt) {
|
|
_this3.handleClick(evt, function () {
|
|
_this3.setSlide(n);
|
|
});
|
|
},
|
|
keydown: function keydown(evt) {
|
|
_this3.handleClick(evt, function () {
|
|
_this3.setSlide(n);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}));
|
|
var on = {
|
|
mouseenter: this.noHoverPause ? noop : this.pause,
|
|
mouseleave: this.noHoverPause ? noop : this.restart,
|
|
focusin: this.pause,
|
|
focusout: this.restart,
|
|
keydown: function keydown(evt) {
|
|
if (/input|textarea/i.test(evt.target.tagName)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (keyCode === CODE_LEFT || keyCode === CODE_RIGHT) {
|
|
stopEvent(evt);
|
|
|
|
_this3[keyCode === CODE_LEFT ? 'prev' : 'next']();
|
|
}
|
|
}
|
|
}; // Touch support event handlers for environment
|
|
|
|
if (!this.noTouch && hasTouchSupport) {
|
|
// Attach appropriate listeners (prepend event name with '&' for passive mode)
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support touch events */
|
|
if (hasPointerEventSupport) {
|
|
on['&pointerdown'] = this.touchStart;
|
|
on['&pointerup'] = this.touchEnd;
|
|
} else {
|
|
on['&touchstart'] = this.touchStart;
|
|
on['&touchmove'] = this.touchMove;
|
|
on['&touchend'] = this.touchEnd;
|
|
}
|
|
} // Return the carousel
|
|
|
|
|
|
return h('div', {
|
|
staticClass: 'carousel',
|
|
class: {
|
|
slide: !this.noAnimation,
|
|
'carousel-fade': !this.noAnimation && this.fade,
|
|
'pointer-event': !this.noTouch && hasTouchSupport && hasPointerEventSupport
|
|
},
|
|
style: {
|
|
background: this.background
|
|
},
|
|
attrs: {
|
|
role: 'region',
|
|
id: this.safeId(),
|
|
'aria-busy': this.isSliding ? 'true' : 'false'
|
|
},
|
|
on: on
|
|
}, [inner, controls, indicators]);
|
|
}
|
|
});
|
|
|
|
var imgProps = {
|
|
imgSrc: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
imgAlt: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
imgWidth: {
|
|
type: [Number, String] // default: undefined
|
|
|
|
},
|
|
imgHeight: {
|
|
type: [Number, String] // default: undefined
|
|
|
|
},
|
|
imgBlank: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
imgBlankColor: {
|
|
type: String,
|
|
default: 'transparent'
|
|
}
|
|
};
|
|
var props$l = makePropsConfigurable(_objectSpread2(_objectSpread2({}, imgProps), {}, {
|
|
contentVisibleUp: {
|
|
type: String
|
|
},
|
|
contentTag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
caption: {
|
|
type: String
|
|
},
|
|
captionHtml: {
|
|
type: String
|
|
},
|
|
captionTag: {
|
|
type: String,
|
|
default: 'h3'
|
|
},
|
|
text: {
|
|
type: String
|
|
},
|
|
textHtml: {
|
|
type: String
|
|
},
|
|
textTag: {
|
|
type: String,
|
|
default: 'p'
|
|
},
|
|
background: {
|
|
type: String
|
|
}
|
|
}), NAME_CAROUSEL_SLIDE); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCarouselSlide = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CAROUSEL_SLIDE,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
inject: {
|
|
bvCarousel: {
|
|
default: function _default() {
|
|
return {
|
|
// Explicitly disable touch if not a child of carousel
|
|
noTouch: true
|
|
};
|
|
}
|
|
}
|
|
},
|
|
props: props$l,
|
|
computed: {
|
|
contentClasses: function contentClasses() {
|
|
return [this.contentVisibleUp ? 'd-none' : '', this.contentVisibleUp ? "d-".concat(this.contentVisibleUp, "-block") : ''];
|
|
},
|
|
computedWidth: function computedWidth() {
|
|
// Use local width, or try parent width
|
|
return this.imgWidth || this.bvCarousel.imgWidth || null;
|
|
},
|
|
computedHeight: function computedHeight() {
|
|
// Use local height, or try parent height
|
|
return this.imgHeight || this.bvCarousel.imgHeight || null;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $img = this.normalizeSlot('img');
|
|
|
|
if (!$img && (this.imgSrc || this.imgBlank)) {
|
|
var on = {}; // Touch support event handler
|
|
|
|
/* istanbul ignore if: difficult to test in JSDOM */
|
|
|
|
if (!this.bvCarousel.noTouch && hasTouchSupport) {
|
|
on.dragstart = function (evt) {
|
|
return stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
};
|
|
}
|
|
|
|
$img = h(BImg, {
|
|
props: _objectSpread2(_objectSpread2({}, pluckProps(imgProps, this.$props, unprefixPropName.bind(null, 'img'))), {}, {
|
|
width: this.computedWidth,
|
|
height: this.computedHeight,
|
|
fluidGrow: true,
|
|
block: true
|
|
}),
|
|
on: on
|
|
});
|
|
}
|
|
|
|
var $contentChildren = [// Caption
|
|
this.caption || this.captionHtml ? h(this.captionTag, {
|
|
domProps: htmlOrText(this.captionHtml, this.caption)
|
|
}) : false, // Text
|
|
this.text || this.textHtml ? h(this.textTag, {
|
|
domProps: htmlOrText(this.textHtml, this.text)
|
|
}) : false, // Children
|
|
this.normalizeSlot() || false];
|
|
var $content = h();
|
|
|
|
if ($contentChildren.some(Boolean)) {
|
|
$content = h(this.contentTag, {
|
|
staticClass: 'carousel-caption',
|
|
class: this.contentClasses
|
|
}, $contentChildren.map(function ($child) {
|
|
return $child || h();
|
|
}));
|
|
}
|
|
|
|
return h('div', {
|
|
staticClass: 'carousel-item',
|
|
style: {
|
|
background: this.background || this.bvCarousel.background || null
|
|
},
|
|
attrs: {
|
|
id: this.safeId(),
|
|
role: 'listitem'
|
|
}
|
|
}, [$img, $content]);
|
|
}
|
|
});
|
|
|
|
var CarouselPlugin =
|
|
/*#__PURE*/
|
|
pluginFactory({
|
|
components: {
|
|
BCarousel: BCarousel,
|
|
BCarouselSlide: BCarouselSlide
|
|
}
|
|
});
|
|
|
|
// Generic collapse transion helper component
|
|
|
|
var onEnter = function onEnter(el) {
|
|
setStyle(el, 'height', 0); // In a `requestAF()` for `appear` to work
|
|
|
|
requestAF(function () {
|
|
reflow(el);
|
|
setStyle(el, 'height', "".concat(el.scrollHeight, "px"));
|
|
});
|
|
};
|
|
|
|
var onAfterEnter = function onAfterEnter(el) {
|
|
removeStyle(el, 'height');
|
|
};
|
|
|
|
var onLeave = function onLeave(el) {
|
|
setStyle(el, 'height', 'auto');
|
|
setStyle(el, 'display', 'block');
|
|
setStyle(el, 'height', "".concat(getBCR(el).height, "px"));
|
|
reflow(el);
|
|
setStyle(el, 'height', 0);
|
|
};
|
|
|
|
var onAfterLeave = function onAfterLeave(el) {
|
|
removeStyle(el, 'height');
|
|
}; // Default transition props
|
|
// `appear` will use the enter classes
|
|
|
|
|
|
var TRANSITION_PROPS = {
|
|
css: true,
|
|
enterClass: '',
|
|
enterActiveClass: 'collapsing',
|
|
enterToClass: 'collapse show',
|
|
leaveClass: 'collapse show',
|
|
leaveActiveClass: 'collapsing',
|
|
leaveToClass: 'collapse'
|
|
}; // Default transition handlers
|
|
// `appear` will use the enter handlers
|
|
|
|
var TRANSITION_HANDLERS = {
|
|
enter: onEnter,
|
|
afterEnter: onAfterEnter,
|
|
leave: onLeave,
|
|
afterLeave: onAfterLeave
|
|
}; // @vue/component
|
|
|
|
var BVCollapse = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_COLLAPSE_HELPER,
|
|
functional: true,
|
|
props: {
|
|
appear: {
|
|
// If `true` (and `visible` is `true` on mount), animate initially visible
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h('transition', // We merge in the `appear` prop last
|
|
a(data, {
|
|
props: TRANSITION_PROPS,
|
|
on: TRANSITION_HANDLERS
|
|
}, {
|
|
props: props
|
|
}), // Note: `<transition>` supports a single root element only
|
|
children);
|
|
}
|
|
});
|
|
|
|
// @vue/component
|
|
var listenOnRootMixin = {
|
|
methods: {
|
|
/**
|
|
* Safely register event listeners on the root Vue node
|
|
* While Vue automatically removes listeners for individual components,
|
|
* when a component registers a listener on root and is destroyed,
|
|
* this orphans a callback because the node is gone,
|
|
* but the root does not clear the callback
|
|
*
|
|
* When registering a `$root` listener, it also registers a listener on
|
|
* the component's `beforeDestroy()` hook to automatically remove the
|
|
* event listener from the `$root` instance
|
|
*
|
|
* @param {string} event
|
|
* @param {function} callback
|
|
*/
|
|
listenOnRoot: function listenOnRoot(event, callback) {
|
|
var _this = this;
|
|
|
|
this.$root.$on(event, callback);
|
|
this.$on('hook:beforeDestroy', function () {
|
|
_this.$root.$off(event, callback);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Safely register a `$once()` event listener on the root Vue node
|
|
* While Vue automatically removes listeners for individual components,
|
|
* when a component registers a listener on root and is destroyed,
|
|
* this orphans a callback because the node is gone,
|
|
* but the root does not clear the callback
|
|
*
|
|
* When registering a $root listener, it also registers a listener on
|
|
* the component's `beforeDestroy` hook to automatically remove the
|
|
* event listener from the $root instance.
|
|
*
|
|
* @param {string} event
|
|
* @param {function} callback
|
|
*/
|
|
listenOnRootOnce: function listenOnRootOnce(event, callback) {
|
|
var _this2 = this;
|
|
|
|
this.$root.$once(event, callback);
|
|
this.$on('hook:beforeDestroy', function () {
|
|
_this2.$root.$off(event, callback);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Convenience method for calling `vm.$emit()` on `vm.$root`
|
|
*
|
|
* @param {string} event
|
|
* @param {*} args
|
|
*/
|
|
emitOnRoot: function emitOnRoot(event) {
|
|
var _this$$root;
|
|
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
args[_key - 1] = arguments[_key];
|
|
}
|
|
|
|
(_this$$root = this.$root).$emit.apply(_this$$root, [event].concat(args));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Classes to apply to trigger element
|
|
|
|
var CLASS_BV_TOGGLE_COLLAPSED = 'collapsed';
|
|
var CLASS_BV_TOGGLE_NOT_COLLAPSED = 'not-collapsed'; // Property key for handler storage
|
|
|
|
var BV_BASE = '__BV_toggle'; // Root event listener property (Function)
|
|
|
|
var BV_TOGGLE_ROOT_HANDLER = "".concat(BV_BASE, "_HANDLER__"); // Trigger element click handler property (Function)
|
|
|
|
var BV_TOGGLE_CLICK_HANDLER = "".concat(BV_BASE, "_CLICK__"); // Target visibility state property (Boolean)
|
|
|
|
var BV_TOGGLE_STATE = "".concat(BV_BASE, "_STATE__"); // Target ID list property (Array)
|
|
|
|
var BV_TOGGLE_TARGETS = "".concat(BV_BASE, "_TARGETS__"); // Commonly used strings
|
|
|
|
var STRING_FALSE = 'false';
|
|
var STRING_TRUE = 'true'; // Commonly used attribute names
|
|
|
|
var ATTR_ARIA_CONTROLS = 'aria-controls';
|
|
var ATTR_ARIA_EXPANDED = 'aria-expanded';
|
|
var ATTR_ROLE = 'role';
|
|
var ATTR_TABINDEX = 'tabindex'; // Commonly used style properties
|
|
|
|
var STYLE_OVERFLOW_ANCHOR = 'overflow-anchor'; // Emitted control event for collapse (emitted to collapse)
|
|
|
|
var EVENT_TOGGLE = 'bv::toggle::collapse'; // Listen to event for toggle state update (emitted by collapse)
|
|
|
|
var EVENT_STATE = 'bv::collapse::state'; // Private event emitted on `$root` to ensure the toggle state is always synced
|
|
// Gets emitted even if the state of b-collapse has not changed
|
|
// This event is NOT to be documented as people should not be using it
|
|
|
|
var EVENT_STATE_SYNC = 'bv::collapse::sync::state'; // Private event we send to collapse to request state update sync event
|
|
|
|
var EVENT_STATE_REQUEST = 'bv::request::collapse::state';
|
|
var KEYDOWN_KEY_CODES = [CODE_ENTER, CODE_SPACE]; // --- Helper methods ---
|
|
|
|
var isNonStandardTag$1 = function isNonStandardTag(el) {
|
|
return !arrayIncludes(['button', 'a'], el.tagName.toLowerCase());
|
|
};
|
|
|
|
var getTargets = function getTargets(_ref, el) {
|
|
var modifiers = _ref.modifiers,
|
|
arg = _ref.arg,
|
|
value = _ref.value;
|
|
// Any modifiers are considered target IDs
|
|
var targets = keys(modifiers || {}); // If value is a string, split out individual targets (if space delimited)
|
|
|
|
value = isString(value) ? value.split(RX_SPACE_SPLIT) : value; // Support target ID as link href (`href="#id"`)
|
|
|
|
if (isTag(el.tagName, 'a')) {
|
|
var href = getAttr(el, 'href') || '';
|
|
|
|
if (RX_HASH_ID.test(href)) {
|
|
targets.push(href.replace(RX_HASH, ''));
|
|
}
|
|
} // Add ID from `arg` (if provided), and support value
|
|
// as a single string ID or an array of string IDs
|
|
// If `value` is not an array or string, then it gets filtered out
|
|
|
|
|
|
concat(arg, value).forEach(function (t) {
|
|
return isString(t) && targets.push(t);
|
|
}); // Return only unique and truthy target IDs
|
|
|
|
return targets.filter(function (t, index, arr) {
|
|
return t && arr.indexOf(t) === index;
|
|
});
|
|
};
|
|
|
|
var removeClickListener = function removeClickListener(el) {
|
|
var handler = el[BV_TOGGLE_CLICK_HANDLER];
|
|
|
|
if (handler) {
|
|
eventOff(el, 'click', handler, EVENT_OPTIONS_PASSIVE);
|
|
eventOff(el, 'keydown', handler, EVENT_OPTIONS_PASSIVE);
|
|
}
|
|
|
|
el[BV_TOGGLE_CLICK_HANDLER] = null;
|
|
};
|
|
|
|
var addClickListener = function addClickListener(el, vnode) {
|
|
removeClickListener(el);
|
|
|
|
if (vnode.context) {
|
|
var handler = function handler(evt) {
|
|
if (!(evt.type === 'keydown' && !arrayIncludes(KEYDOWN_KEY_CODES, evt.keyCode)) && !isDisabled(el)) {
|
|
var targets = el[BV_TOGGLE_TARGETS] || [];
|
|
targets.forEach(function (target) {
|
|
vnode.context.$root.$emit(EVENT_TOGGLE, target);
|
|
});
|
|
}
|
|
};
|
|
|
|
el[BV_TOGGLE_CLICK_HANDLER] = handler;
|
|
eventOn(el, 'click', handler, EVENT_OPTIONS_PASSIVE);
|
|
|
|
if (isNonStandardTag$1(el)) {
|
|
eventOn(el, 'keydown', handler, EVENT_OPTIONS_PASSIVE);
|
|
}
|
|
}
|
|
};
|
|
|
|
var removeRootListeners = function removeRootListeners(el, vnode) {
|
|
if (el[BV_TOGGLE_ROOT_HANDLER] && vnode.context) {
|
|
vnode.context.$root.$off([EVENT_STATE, EVENT_STATE_SYNC], el[BV_TOGGLE_ROOT_HANDLER]);
|
|
}
|
|
|
|
el[BV_TOGGLE_ROOT_HANDLER] = null;
|
|
};
|
|
|
|
var addRootListeners = function addRootListeners(el, vnode) {
|
|
removeRootListeners(el, vnode);
|
|
|
|
if (vnode.context) {
|
|
var handler = function handler(id, state) {
|
|
// `state` will be `true` if target is expanded
|
|
if (arrayIncludes(el[BV_TOGGLE_TARGETS] || [], id)) {
|
|
// Set/Clear 'collapsed' visibility class state
|
|
el[BV_TOGGLE_STATE] = state; // Set `aria-expanded` and class state on trigger element
|
|
|
|
setToggleState(el, state);
|
|
}
|
|
};
|
|
|
|
el[BV_TOGGLE_ROOT_HANDLER] = handler; // Listen for toggle state changes (public) and sync (private)
|
|
|
|
vnode.context.$root.$on([EVENT_STATE, EVENT_STATE_SYNC], handler);
|
|
}
|
|
};
|
|
|
|
var setToggleState = function setToggleState(el, state) {
|
|
// State refers to the visibility of the collapse/sidebar
|
|
if (state) {
|
|
removeClass(el, CLASS_BV_TOGGLE_COLLAPSED);
|
|
addClass(el, CLASS_BV_TOGGLE_NOT_COLLAPSED);
|
|
setAttr(el, ATTR_ARIA_EXPANDED, STRING_TRUE);
|
|
} else {
|
|
removeClass(el, CLASS_BV_TOGGLE_NOT_COLLAPSED);
|
|
addClass(el, CLASS_BV_TOGGLE_COLLAPSED);
|
|
setAttr(el, ATTR_ARIA_EXPANDED, STRING_FALSE);
|
|
}
|
|
}; // Reset and remove a property from the provided element
|
|
|
|
|
|
var resetProp = function resetProp(el, prop) {
|
|
el[prop] = null;
|
|
delete el[prop];
|
|
}; // Handle directive updates
|
|
|
|
|
|
var handleUpdate = function handleUpdate(el, binding, vnode) {
|
|
/* istanbul ignore next: should never happen */
|
|
if (!isBrowser || !vnode.context) {
|
|
return;
|
|
} // If element is not a button or link, we add `role="button"`
|
|
// and `tabindex="0"` for accessibility reasons
|
|
|
|
|
|
if (isNonStandardTag$1(el)) {
|
|
if (!hasAttr(el, ATTR_ROLE)) {
|
|
setAttr(el, ATTR_ROLE, 'button');
|
|
}
|
|
|
|
if (!hasAttr(el, ATTR_TABINDEX)) {
|
|
setAttr(el, ATTR_TABINDEX, '0');
|
|
}
|
|
} // Ensure the collapse class and `aria-*` attributes persist
|
|
// after element is updated (either by parent re-rendering
|
|
// or changes to this element or its contents)
|
|
|
|
|
|
setToggleState(el, el[BV_TOGGLE_STATE]); // Parse list of target IDs
|
|
|
|
var targets = getTargets(binding, el); // Ensure the `aria-controls` hasn't been overwritten
|
|
// or removed when vnode updates
|
|
// Also ensure to set `overflow-anchor` to `none` to prevent
|
|
// the browser's scroll anchoring behavior
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (targets.length > 0) {
|
|
setAttr(el, ATTR_ARIA_CONTROLS, targets.join(' '));
|
|
setStyle(el, STYLE_OVERFLOW_ANCHOR, 'none');
|
|
} else {
|
|
removeAttr(el, ATTR_ARIA_CONTROLS);
|
|
removeStyle(el, STYLE_OVERFLOW_ANCHOR);
|
|
} // Add/Update our click listener(s)
|
|
// Wrap in a `requestAF()` to allow any previous
|
|
// click handling to occur first
|
|
|
|
|
|
requestAF(function () {
|
|
addClickListener(el, vnode);
|
|
}); // If targets array has changed, update
|
|
|
|
if (!looseEqual(targets, el[BV_TOGGLE_TARGETS])) {
|
|
// Update targets array to element storage
|
|
el[BV_TOGGLE_TARGETS] = targets; // Ensure `aria-controls` is up to date
|
|
// Request a state update from targets so that we can
|
|
// ensure expanded state is correct (in most cases)
|
|
|
|
targets.forEach(function (target) {
|
|
vnode.context.$root.$emit(EVENT_STATE_REQUEST, target);
|
|
});
|
|
}
|
|
};
|
|
/*
|
|
* Export our directive
|
|
*/
|
|
|
|
|
|
var VBToggle = {
|
|
bind: function bind(el, binding, vnode) {
|
|
// State is initially collapsed until we receive a state event
|
|
el[BV_TOGGLE_STATE] = false; // Assume no targets initially
|
|
|
|
el[BV_TOGGLE_TARGETS] = []; // Add our root listeners
|
|
|
|
addRootListeners(el, vnode); // Initial update of trigger
|
|
|
|
handleUpdate(el, binding, vnode);
|
|
},
|
|
componentUpdated: handleUpdate,
|
|
updated: handleUpdate,
|
|
unbind: function unbind(el, binding, vnode) {
|
|
removeClickListener(el); // Remove our $root listener
|
|
|
|
removeRootListeners(el, vnode); // Reset custom props
|
|
|
|
resetProp(el, BV_TOGGLE_ROOT_HANDLER);
|
|
resetProp(el, BV_TOGGLE_CLICK_HANDLER);
|
|
resetProp(el, BV_TOGGLE_STATE);
|
|
resetProp(el, BV_TOGGLE_TARGETS); // Reset classes/attrs/styles
|
|
|
|
removeClass(el, CLASS_BV_TOGGLE_COLLAPSED);
|
|
removeClass(el, CLASS_BV_TOGGLE_NOT_COLLAPSED);
|
|
removeAttr(el, ATTR_ARIA_EXPANDED);
|
|
removeAttr(el, ATTR_ARIA_CONTROLS);
|
|
removeAttr(el, ATTR_ROLE);
|
|
removeStyle(el, STYLE_OVERFLOW_ANCHOR);
|
|
}
|
|
};
|
|
|
|
// Accordion event name we emit on `$root`
|
|
|
|
var EVENT_ACCORDION = 'bv::collapse::accordion'; // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BCollapse = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_COLLAPSE,
|
|
mixins: [idMixin, listenOnRootMixin, normalizeSlotMixin],
|
|
model: {
|
|
prop: 'visible',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable({
|
|
isNav: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
accordion: {
|
|
type: String // default: null
|
|
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
appear: {
|
|
// If `true` (and `visible` is `true` on mount), animate initially visible
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_COLLAPSE),
|
|
data: function data() {
|
|
return {
|
|
show: this.visible,
|
|
transitioning: false
|
|
};
|
|
},
|
|
computed: {
|
|
classObject: function classObject() {
|
|
return {
|
|
'navbar-collapse': this.isNav,
|
|
collapse: !this.transitioning,
|
|
show: this.show && !this.transitioning
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
visible: function visible(newVal) {
|
|
if (newVal !== this.show) {
|
|
this.show = newVal;
|
|
}
|
|
},
|
|
show: function show(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.emitState();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
this.show = this.visible;
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
this.show = this.visible; // Listen for toggle events to open/close us
|
|
|
|
this.listenOnRoot(EVENT_TOGGLE, this.handleToggleEvt); // Listen to other collapses for accordion events
|
|
|
|
this.listenOnRoot(EVENT_ACCORDION, this.handleAccordionEvt);
|
|
|
|
if (this.isNav) {
|
|
// Set up handlers
|
|
this.setWindowEvents(true);
|
|
this.handleResize();
|
|
}
|
|
|
|
this.$nextTick(function () {
|
|
_this.emitState();
|
|
}); // Listen for "Sync state" requests from `v-b-toggle`
|
|
|
|
this.listenOnRoot(EVENT_STATE_REQUEST, function (id) {
|
|
if (id === _this.safeId()) {
|
|
_this.$nextTick(_this.emitSync);
|
|
}
|
|
});
|
|
},
|
|
updated: function updated() {
|
|
// Emit a private event every time this component updates to ensure
|
|
// the toggle button is in sync with the collapse's state
|
|
// It is emitted regardless if the visible state changes
|
|
this.emitSync();
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
if (this.isNav) {
|
|
this.setWindowEvents(false);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
if (this.isNav) {
|
|
this.setWindowEvents(true);
|
|
}
|
|
|
|
this.emitSync();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
// Trigger state emit if needed
|
|
this.show = false;
|
|
|
|
if (this.isNav && isBrowser) {
|
|
this.setWindowEvents(false);
|
|
}
|
|
},
|
|
methods: {
|
|
setWindowEvents: function setWindowEvents(on) {
|
|
eventOnOff(on, window, 'resize', this.handleResize, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOnOff(on, window, 'orientationchange', this.handleResize, EVENT_OPTIONS_NO_CAPTURE);
|
|
},
|
|
toggle: function toggle() {
|
|
this.show = !this.show;
|
|
},
|
|
onEnter: function onEnter() {
|
|
this.transitioning = true; // This should be moved out so we can add cancellable events
|
|
|
|
this.$emit('show');
|
|
},
|
|
onAfterEnter: function onAfterEnter() {
|
|
this.transitioning = false;
|
|
this.$emit('shown');
|
|
},
|
|
onLeave: function onLeave() {
|
|
this.transitioning = true; // This should be moved out so we can add cancellable events
|
|
|
|
this.$emit('hide');
|
|
},
|
|
onAfterLeave: function onAfterLeave() {
|
|
this.transitioning = false;
|
|
this.$emit('hidden');
|
|
},
|
|
emitState: function emitState() {
|
|
this.$emit('input', this.show); // Let `v-b-toggle` know the state of this collapse
|
|
|
|
this.emitOnRoot(EVENT_STATE, this.safeId(), this.show);
|
|
|
|
if (this.accordion && this.show) {
|
|
// Tell the other collapses in this accordion to close
|
|
this.emitOnRoot(EVENT_ACCORDION, this.safeId(), this.accordion);
|
|
}
|
|
},
|
|
emitSync: function emitSync() {
|
|
// Emit a private event every time this component updates to ensure
|
|
// the toggle button is in sync with the collapse's state
|
|
// It is emitted regardless if the visible state changes
|
|
this.emitOnRoot(EVENT_STATE_SYNC, this.safeId(), this.show);
|
|
},
|
|
checkDisplayBlock: function checkDisplayBlock() {
|
|
// Check to see if the collapse has `display: block !important` set
|
|
// We can't set `display: none` directly on `this.$el`, as it would
|
|
// trigger a new transition to start (or cancel a current one)
|
|
var restore = hasClass(this.$el, 'show');
|
|
removeClass(this.$el, 'show');
|
|
var isBlock = getCS(this.$el).display === 'block';
|
|
|
|
if (restore) {
|
|
addClass(this.$el, 'show');
|
|
}
|
|
|
|
return isBlock;
|
|
},
|
|
clickHandler: function clickHandler(evt) {
|
|
// If we are in a nav/navbar, close the collapse when non-disabled link clicked
|
|
var el = evt.target;
|
|
|
|
if (!this.isNav || !el || getCS(this.$el).display !== 'block') {
|
|
/* istanbul ignore next: can't test getComputedStyle in JSDOM */
|
|
return;
|
|
}
|
|
|
|
if (matches(el, '.nav-link,.dropdown-item') || closest('.nav-link,.dropdown-item', el)) {
|
|
if (!this.checkDisplayBlock()) {
|
|
// Only close the collapse if it is not forced to be `display: block !important`
|
|
this.show = false;
|
|
}
|
|
}
|
|
},
|
|
handleToggleEvt: function handleToggleEvt(target) {
|
|
if (target !== this.safeId()) {
|
|
return;
|
|
}
|
|
|
|
this.toggle();
|
|
},
|
|
handleAccordionEvt: function handleAccordionEvt(openedId, accordion) {
|
|
if (!this.accordion || accordion !== this.accordion) {
|
|
return;
|
|
}
|
|
|
|
if (openedId === this.safeId()) {
|
|
// Open this collapse if not shown
|
|
if (!this.show) {
|
|
this.toggle();
|
|
}
|
|
} else {
|
|
// Close this collapse if shown
|
|
if (this.show) {
|
|
this.toggle();
|
|
}
|
|
}
|
|
},
|
|
handleResize: function handleResize() {
|
|
// Handler for orientation/resize to set collapsed state in nav/navbar
|
|
this.show = getCS(this.$el).display === 'block';
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this2 = this;
|
|
|
|
var scope = {
|
|
visible: this.show,
|
|
close: function close() {
|
|
return _this2.show = false;
|
|
}
|
|
};
|
|
var content = h(this.tag, {
|
|
class: this.classObject,
|
|
directives: [{
|
|
name: 'show',
|
|
value: this.show
|
|
}],
|
|
attrs: {
|
|
id: this.safeId()
|
|
},
|
|
on: {
|
|
click: this.clickHandler
|
|
}
|
|
}, [this.normalizeSlot(SLOT_NAME_DEFAULT, scope)]);
|
|
return h(BVCollapse, {
|
|
props: {
|
|
appear: this.appear
|
|
},
|
|
on: {
|
|
enter: this.onEnter,
|
|
afterEnter: this.onAfterEnter,
|
|
leave: this.onLeave,
|
|
afterLeave: this.onAfterLeave
|
|
}
|
|
}, [content]);
|
|
}
|
|
});
|
|
|
|
var VBTogglePlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBToggle: VBToggle
|
|
}
|
|
});
|
|
|
|
var CollapsePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BCollapse: BCollapse
|
|
},
|
|
plugins: {
|
|
VBTogglePlugin: VBTogglePlugin
|
|
}
|
|
});
|
|
|
|
/**!
|
|
* @fileOverview Kickass library to create and place poppers near their reference elements.
|
|
* @version 1.16.1
|
|
* @license
|
|
* Copyright (c) 2016 Federico Zivolo and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
var isBrowser$1 = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';
|
|
|
|
var timeoutDuration = function () {
|
|
var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
|
|
for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
|
|
if (isBrowser$1 && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}();
|
|
|
|
function microtaskDebounce(fn) {
|
|
var called = false;
|
|
return function () {
|
|
if (called) {
|
|
return;
|
|
}
|
|
called = true;
|
|
window.Promise.resolve().then(function () {
|
|
called = false;
|
|
fn();
|
|
});
|
|
};
|
|
}
|
|
|
|
function taskDebounce(fn) {
|
|
var scheduled = false;
|
|
return function () {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
setTimeout(function () {
|
|
scheduled = false;
|
|
fn();
|
|
}, timeoutDuration);
|
|
}
|
|
};
|
|
}
|
|
|
|
var supportsMicroTasks = isBrowser$1 && window.Promise;
|
|
|
|
/**
|
|
* Create a debounced version of a method, that's asynchronously deferred
|
|
* but called in the minimum time possible.
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Function} fn
|
|
* @returns {Function}
|
|
*/
|
|
var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
|
|
|
|
/**
|
|
* Check if the given variable is a function
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Any} functionToCheck - variable to check
|
|
* @returns {Boolean} answer to: is a function?
|
|
*/
|
|
function isFunction$1(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
/**
|
|
* Get CSS computed property of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Eement} element
|
|
* @argument {String} property
|
|
*/
|
|
function getStyleComputedProperty(element, property) {
|
|
if (element.nodeType !== 1) {
|
|
return [];
|
|
}
|
|
// NOTE: 1 DOM access here
|
|
var window = element.ownerDocument.defaultView;
|
|
var css = window.getComputedStyle(element, null);
|
|
return property ? css[property] : css;
|
|
}
|
|
|
|
/**
|
|
* Returns the parentNode or the host of the element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} parent
|
|
*/
|
|
function getParentNode(element) {
|
|
if (element.nodeName === 'HTML') {
|
|
return element;
|
|
}
|
|
return element.parentNode || element.host;
|
|
}
|
|
|
|
/**
|
|
* Returns the scrolling parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} scroll parent
|
|
*/
|
|
function getScrollParent(element) {
|
|
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
|
if (!element) {
|
|
return document.body;
|
|
}
|
|
|
|
switch (element.nodeName) {
|
|
case 'HTML':
|
|
case 'BODY':
|
|
return element.ownerDocument.body;
|
|
case '#document':
|
|
return element.body;
|
|
}
|
|
|
|
// Firefox want us to check `-x` and `-y` variations as well
|
|
|
|
var _getStyleComputedProp = getStyleComputedProperty(element),
|
|
overflow = _getStyleComputedProp.overflow,
|
|
overflowX = _getStyleComputedProp.overflowX,
|
|
overflowY = _getStyleComputedProp.overflowY;
|
|
|
|
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
|
return element;
|
|
}
|
|
|
|
return getScrollParent(getParentNode(element));
|
|
}
|
|
|
|
/**
|
|
* Returns the reference node of the reference object, or the reference object itself.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Element|Object} reference - the reference element (the popper will be relative to this)
|
|
* @returns {Element} parent
|
|
*/
|
|
function getReferenceNode(reference) {
|
|
return reference && reference.referenceNode ? reference.referenceNode : reference;
|
|
}
|
|
|
|
var isIE11 = isBrowser$1 && !!(window.MSInputMethodContext && document.documentMode);
|
|
var isIE10 = isBrowser$1 && /MSIE 10/.test(navigator.userAgent);
|
|
|
|
/**
|
|
* Determines if the browser is Internet Explorer
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Number} version to check
|
|
* @returns {Boolean} isIE
|
|
*/
|
|
function isIE$1(version) {
|
|
if (version === 11) {
|
|
return isIE11;
|
|
}
|
|
if (version === 10) {
|
|
return isIE10;
|
|
}
|
|
return isIE11 || isIE10;
|
|
}
|
|
|
|
/**
|
|
* Returns the offset parent of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} offset parent
|
|
*/
|
|
function getOffsetParent(element) {
|
|
if (!element) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
var noOffsetParent = isIE$1(10) ? document.body : null;
|
|
|
|
// NOTE: 1 DOM access here
|
|
var offsetParent = element.offsetParent || null;
|
|
// Skip hidden elements which don't have an offsetParent
|
|
while (offsetParent === noOffsetParent && element.nextElementSibling) {
|
|
offsetParent = (element = element.nextElementSibling).offsetParent;
|
|
}
|
|
|
|
var nodeName = offsetParent && offsetParent.nodeName;
|
|
|
|
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return element ? element.ownerDocument.documentElement : document.documentElement;
|
|
}
|
|
|
|
// .offsetParent will return the closest TH, TD or TABLE in case
|
|
// no offsetParent is present, I hate this job...
|
|
if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
|
|
return getOffsetParent(offsetParent);
|
|
}
|
|
|
|
return offsetParent;
|
|
}
|
|
|
|
function isOffsetContainer(element) {
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY') {
|
|
return false;
|
|
}
|
|
return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
|
|
}
|
|
|
|
/**
|
|
* Finds the root node (document, shadowDOM root) of the given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} node
|
|
* @returns {Element} root node
|
|
*/
|
|
function getRoot(node) {
|
|
if (node.parentNode !== null) {
|
|
return getRoot(node.parentNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Finds the offset parent common to the two provided nodes
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element1
|
|
* @argument {Element} element2
|
|
* @returns {Element} common offset parent
|
|
*/
|
|
function findCommonOffsetParent(element1, element2) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
|
|
return document.documentElement;
|
|
}
|
|
|
|
// Here we make sure to give as "start" the element that comes first in the DOM
|
|
var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
var start = order ? element1 : element2;
|
|
var end = order ? element2 : element1;
|
|
|
|
// Get common ancestor container
|
|
var range = document.createRange();
|
|
range.setStart(start, 0);
|
|
range.setEnd(end, 0);
|
|
var commonAncestorContainer = range.commonAncestorContainer;
|
|
|
|
// Both nodes are inside #document
|
|
|
|
if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
|
|
if (isOffsetContainer(commonAncestorContainer)) {
|
|
return commonAncestorContainer;
|
|
}
|
|
|
|
return getOffsetParent(commonAncestorContainer);
|
|
}
|
|
|
|
// one of the nodes is inside shadowDOM, find which one
|
|
var element1root = getRoot(element1);
|
|
if (element1root.host) {
|
|
return findCommonOffsetParent(element1root.host, element2);
|
|
} else {
|
|
return findCommonOffsetParent(element1, getRoot(element2).host);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the scroll value of the given element in the given side (top and left)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {String} side `top` or `left`
|
|
* @returns {number} amount of scrolled pixels
|
|
*/
|
|
function getScroll(element) {
|
|
var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
|
|
|
|
var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
|
|
var nodeName = element.nodeName;
|
|
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
var html = element.ownerDocument.documentElement;
|
|
var scrollingElement = element.ownerDocument.scrollingElement || html;
|
|
return scrollingElement[upperSide];
|
|
}
|
|
|
|
return element[upperSide];
|
|
}
|
|
|
|
/*
|
|
* Sum or subtract the element scroll values (left and top) from a given rect object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} rect - Rect object you want to change
|
|
* @param {HTMLElement} element - The element from the function reads the scroll values
|
|
* @param {Boolean} subtract - set to true if you want to subtract the scroll values
|
|
* @return {Object} rect - The modifier rect object
|
|
*/
|
|
function includeScroll(rect, element) {
|
|
var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
var modifier = subtract ? -1 : 1;
|
|
rect.top += scrollTop * modifier;
|
|
rect.bottom += scrollTop * modifier;
|
|
rect.left += scrollLeft * modifier;
|
|
rect.right += scrollLeft * modifier;
|
|
return rect;
|
|
}
|
|
|
|
/*
|
|
* Helper to detect borders of a given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {CSSStyleDeclaration} styles
|
|
* Result of `getStyleComputedProperty` on the given element
|
|
* @param {String} axis - `x` or `y`
|
|
* @return {number} borders - The borders size of the given axis
|
|
*/
|
|
|
|
function getBordersSize(styles, axis) {
|
|
var sideA = axis === 'x' ? 'Left' : 'Top';
|
|
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
|
|
|
|
return parseFloat(styles['border' + sideA + 'Width']) + parseFloat(styles['border' + sideB + 'Width']);
|
|
}
|
|
|
|
function getSize(axis, body, html, computedStyle) {
|
|
return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE$1(10) ? parseInt(html['offset' + axis]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')]) : 0);
|
|
}
|
|
|
|
function getWindowSizes(document) {
|
|
var body = document.body;
|
|
var html = document.documentElement;
|
|
var computedStyle = isIE$1(10) && getComputedStyle(html);
|
|
|
|
return {
|
|
height: getSize('Height', body, html, computedStyle),
|
|
width: getSize('Width', body, html, computedStyle)
|
|
};
|
|
}
|
|
|
|
var classCallCheck = function (instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
|
|
var createClass = function () {
|
|
function defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
return function (Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
var defineProperty$1 = function (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;
|
|
};
|
|
|
|
var _extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Given element offsets, generate an output similar to getBoundingClientRect
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} offsets
|
|
* @returns {Object} ClientRect like output
|
|
*/
|
|
function getClientRect(offsets) {
|
|
return _extends({}, offsets, {
|
|
right: offsets.left + offsets.width,
|
|
bottom: offsets.top + offsets.height
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get bounding client rect of given element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} element
|
|
* @return {Object} client rect
|
|
*/
|
|
function getBoundingClientRect(element) {
|
|
var rect = {};
|
|
|
|
// IE10 10 FIX: Please, don't ask, the element isn't
|
|
// considered in DOM in some circumstances...
|
|
// This isn't reproducible in IE10 compatibility mode of IE11
|
|
try {
|
|
if (isIE$1(10)) {
|
|
rect = element.getBoundingClientRect();
|
|
var scrollTop = getScroll(element, 'top');
|
|
var scrollLeft = getScroll(element, 'left');
|
|
rect.top += scrollTop;
|
|
rect.left += scrollLeft;
|
|
rect.bottom += scrollTop;
|
|
rect.right += scrollLeft;
|
|
} else {
|
|
rect = element.getBoundingClientRect();
|
|
}
|
|
} catch (e) {}
|
|
|
|
var result = {
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width: rect.right - rect.left,
|
|
height: rect.bottom - rect.top
|
|
};
|
|
|
|
// subtract scrollbar size from sizes
|
|
var sizes = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {};
|
|
var width = sizes.width || element.clientWidth || result.width;
|
|
var height = sizes.height || element.clientHeight || result.height;
|
|
|
|
var horizScrollbar = element.offsetWidth - width;
|
|
var vertScrollbar = element.offsetHeight - height;
|
|
|
|
// if an hypothetical scrollbar is detected, we must be sure it's not a `border`
|
|
// we make this check conditional for performance reasons
|
|
if (horizScrollbar || vertScrollbar) {
|
|
var styles = getStyleComputedProperty(element);
|
|
horizScrollbar -= getBordersSize(styles, 'x');
|
|
vertScrollbar -= getBordersSize(styles, 'y');
|
|
|
|
result.width -= horizScrollbar;
|
|
result.height -= vertScrollbar;
|
|
}
|
|
|
|
return getClientRect(result);
|
|
}
|
|
|
|
function getOffsetRectRelativeToArbitraryNode(children, parent) {
|
|
var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
var isIE10 = isIE$1(10);
|
|
var isHTML = parent.nodeName === 'HTML';
|
|
var childrenRect = getBoundingClientRect(children);
|
|
var parentRect = getBoundingClientRect(parent);
|
|
var scrollParent = getScrollParent(children);
|
|
|
|
var styles = getStyleComputedProperty(parent);
|
|
var borderTopWidth = parseFloat(styles.borderTopWidth);
|
|
var borderLeftWidth = parseFloat(styles.borderLeftWidth);
|
|
|
|
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
|
|
if (fixedPosition && isHTML) {
|
|
parentRect.top = Math.max(parentRect.top, 0);
|
|
parentRect.left = Math.max(parentRect.left, 0);
|
|
}
|
|
var offsets = getClientRect({
|
|
top: childrenRect.top - parentRect.top - borderTopWidth,
|
|
left: childrenRect.left - parentRect.left - borderLeftWidth,
|
|
width: childrenRect.width,
|
|
height: childrenRect.height
|
|
});
|
|
offsets.marginTop = 0;
|
|
offsets.marginLeft = 0;
|
|
|
|
// Subtract margins of documentElement in case it's being used as parent
|
|
// we do this only on HTML because it's the only element that behaves
|
|
// differently when margins are applied to it. The margins are included in
|
|
// the box of the documentElement, in the other cases not.
|
|
if (!isIE10 && isHTML) {
|
|
var marginTop = parseFloat(styles.marginTop);
|
|
var marginLeft = parseFloat(styles.marginLeft);
|
|
|
|
offsets.top -= borderTopWidth - marginTop;
|
|
offsets.bottom -= borderTopWidth - marginTop;
|
|
offsets.left -= borderLeftWidth - marginLeft;
|
|
offsets.right -= borderLeftWidth - marginLeft;
|
|
|
|
// Attach marginTop and marginLeft because in some circumstances we may need them
|
|
offsets.marginTop = marginTop;
|
|
offsets.marginLeft = marginLeft;
|
|
}
|
|
|
|
if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
|
|
offsets = includeScroll(offsets, parent);
|
|
}
|
|
|
|
return offsets;
|
|
}
|
|
|
|
function getViewportOffsetRectRelativeToArtbitraryNode(element) {
|
|
var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var html = element.ownerDocument.documentElement;
|
|
var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
|
|
var width = Math.max(html.clientWidth, window.innerWidth || 0);
|
|
var height = Math.max(html.clientHeight, window.innerHeight || 0);
|
|
|
|
var scrollTop = !excludeScroll ? getScroll(html) : 0;
|
|
var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
|
|
|
|
var offset = {
|
|
top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
|
|
left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
return getClientRect(offset);
|
|
}
|
|
|
|
/**
|
|
* Check if the given element is fixed or is inside a fixed parent
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @argument {Element} customContainer
|
|
* @returns {Boolean} answer to "isFixed?"
|
|
*/
|
|
function isFixed(element) {
|
|
var nodeName = element.nodeName;
|
|
if (nodeName === 'BODY' || nodeName === 'HTML') {
|
|
return false;
|
|
}
|
|
if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
|
return true;
|
|
}
|
|
var parentNode = getParentNode(element);
|
|
if (!parentNode) {
|
|
return false;
|
|
}
|
|
return isFixed(parentNode);
|
|
}
|
|
|
|
/**
|
|
* Finds the first parent of an element that has a transformed property defined
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Element} first transformed parent or documentElement
|
|
*/
|
|
|
|
function getFixedPositionOffsetParent(element) {
|
|
// This check is needed to avoid errors in case one of the elements isn't defined for any reason
|
|
if (!element || !element.parentElement || isIE$1()) {
|
|
return document.documentElement;
|
|
}
|
|
var el = element.parentElement;
|
|
while (el && getStyleComputedProperty(el, 'transform') === 'none') {
|
|
el = el.parentElement;
|
|
}
|
|
return el || document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Computed the boundaries limits and return them
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {HTMLElement} popper
|
|
* @param {HTMLElement} reference
|
|
* @param {number} padding
|
|
* @param {HTMLElement} boundariesElement - Element used to define the boundaries
|
|
* @param {Boolean} fixedPosition - Is in fixed position mode
|
|
* @returns {Object} Coordinates of the boundaries
|
|
*/
|
|
function getBoundaries(popper, reference, padding, boundariesElement) {
|
|
var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
|
|
// NOTE: 1 DOM access here
|
|
|
|
var boundaries = { top: 0, left: 0 };
|
|
var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference));
|
|
|
|
// Handle viewport case
|
|
if (boundariesElement === 'viewport') {
|
|
boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
|
|
} else {
|
|
// Handle other cases based on DOM element used as boundaries
|
|
var boundariesNode = void 0;
|
|
if (boundariesElement === 'scrollParent') {
|
|
boundariesNode = getScrollParent(getParentNode(reference));
|
|
if (boundariesNode.nodeName === 'BODY') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
}
|
|
} else if (boundariesElement === 'window') {
|
|
boundariesNode = popper.ownerDocument.documentElement;
|
|
} else {
|
|
boundariesNode = boundariesElement;
|
|
}
|
|
|
|
var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
|
|
|
|
// In case of HTML, we need a different computation
|
|
if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
|
|
var _getWindowSizes = getWindowSizes(popper.ownerDocument),
|
|
height = _getWindowSizes.height,
|
|
width = _getWindowSizes.width;
|
|
|
|
boundaries.top += offsets.top - offsets.marginTop;
|
|
boundaries.bottom = height + offsets.top;
|
|
boundaries.left += offsets.left - offsets.marginLeft;
|
|
boundaries.right = width + offsets.left;
|
|
} else {
|
|
// for all the other DOM elements, this one is good
|
|
boundaries = offsets;
|
|
}
|
|
}
|
|
|
|
// Add paddings
|
|
padding = padding || 0;
|
|
var isPaddingNumber = typeof padding === 'number';
|
|
boundaries.left += isPaddingNumber ? padding : padding.left || 0;
|
|
boundaries.top += isPaddingNumber ? padding : padding.top || 0;
|
|
boundaries.right -= isPaddingNumber ? padding : padding.right || 0;
|
|
boundaries.bottom -= isPaddingNumber ? padding : padding.bottom || 0;
|
|
|
|
return boundaries;
|
|
}
|
|
|
|
function getArea(_ref) {
|
|
var width = _ref.width,
|
|
height = _ref.height;
|
|
|
|
return width * height;
|
|
}
|
|
|
|
/**
|
|
* Utility used to transform the `auto` placement to the placement with more
|
|
* available space.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) {
|
|
var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
|
|
|
|
if (placement.indexOf('auto') === -1) {
|
|
return placement;
|
|
}
|
|
|
|
var boundaries = getBoundaries(popper, reference, padding, boundariesElement);
|
|
|
|
var rects = {
|
|
top: {
|
|
width: boundaries.width,
|
|
height: refRect.top - boundaries.top
|
|
},
|
|
right: {
|
|
width: boundaries.right - refRect.right,
|
|
height: boundaries.height
|
|
},
|
|
bottom: {
|
|
width: boundaries.width,
|
|
height: boundaries.bottom - refRect.bottom
|
|
},
|
|
left: {
|
|
width: refRect.left - boundaries.left,
|
|
height: boundaries.height
|
|
}
|
|
};
|
|
|
|
var sortedAreas = Object.keys(rects).map(function (key) {
|
|
return _extends({
|
|
key: key
|
|
}, rects[key], {
|
|
area: getArea(rects[key])
|
|
});
|
|
}).sort(function (a, b) {
|
|
return b.area - a.area;
|
|
});
|
|
|
|
var filteredAreas = sortedAreas.filter(function (_ref2) {
|
|
var width = _ref2.width,
|
|
height = _ref2.height;
|
|
return width >= popper.clientWidth && height >= popper.clientHeight;
|
|
});
|
|
|
|
var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
|
|
|
|
var variation = placement.split('-')[1];
|
|
|
|
return computedPlacement + (variation ? '-' + variation : '');
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the reference element
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} state
|
|
* @param {Element} popper - the popper element
|
|
* @param {Element} reference - the reference element (the popper will be relative to this)
|
|
* @param {Element} fixedPosition - is in fixed position mode
|
|
* @returns {Object} An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getReferenceOffsets(state, popper, reference) {
|
|
var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference));
|
|
return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
|
|
}
|
|
|
|
/**
|
|
* Get the outer sizes of the given element (offset size + margins)
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element
|
|
* @returns {Object} object containing width and height properties
|
|
*/
|
|
function getOuterSizes(element) {
|
|
var window = element.ownerDocument.defaultView;
|
|
var styles = window.getComputedStyle(element);
|
|
var x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0);
|
|
var y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0);
|
|
var result = {
|
|
width: element.offsetWidth + y,
|
|
height: element.offsetHeight + x
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement
|
|
* @returns {String} flipped placement
|
|
*/
|
|
function getOppositePlacement(placement) {
|
|
var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
|
return placement.replace(/left|right|bottom|top/g, function (matched) {
|
|
return hash[matched];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get offsets to the popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Object} position - CSS position the Popper will get applied
|
|
* @param {HTMLElement} popper - the popper element
|
|
* @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
|
|
* @param {String} placement - one of the valid placement options
|
|
* @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
|
|
*/
|
|
function getPopperOffsets(popper, referenceOffsets, placement) {
|
|
placement = placement.split('-')[0];
|
|
|
|
// Get popper node sizes
|
|
var popperRect = getOuterSizes(popper);
|
|
|
|
// Add position, width and height to our offsets object
|
|
var popperOffsets = {
|
|
width: popperRect.width,
|
|
height: popperRect.height
|
|
};
|
|
|
|
// depending by the popper placement we have to compute its offsets slightly differently
|
|
var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
|
|
var mainSide = isHoriz ? 'top' : 'left';
|
|
var secondarySide = isHoriz ? 'left' : 'top';
|
|
var measurement = isHoriz ? 'height' : 'width';
|
|
var secondaryMeasurement = !isHoriz ? 'height' : 'width';
|
|
|
|
popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
|
|
if (placement === secondarySide) {
|
|
popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
|
|
} else {
|
|
popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
|
|
}
|
|
|
|
return popperOffsets;
|
|
}
|
|
|
|
/**
|
|
* Mimics the `find` method of Array
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function find(arr, check) {
|
|
// use native find if supported
|
|
if (Array.prototype.find) {
|
|
return arr.find(check);
|
|
}
|
|
|
|
// use `filter` to obtain the same behavior of `find`
|
|
return arr.filter(check)[0];
|
|
}
|
|
|
|
/**
|
|
* Return the index of the matching object
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Array} arr
|
|
* @argument prop
|
|
* @argument value
|
|
* @returns index or -1
|
|
*/
|
|
function findIndex(arr, prop, value) {
|
|
// use native findIndex if supported
|
|
if (Array.prototype.findIndex) {
|
|
return arr.findIndex(function (cur) {
|
|
return cur[prop] === value;
|
|
});
|
|
}
|
|
|
|
// use `find` + `indexOf` if `findIndex` isn't supported
|
|
var match = find(arr, function (obj) {
|
|
return obj[prop] === value;
|
|
});
|
|
return arr.indexOf(match);
|
|
}
|
|
|
|
/**
|
|
* Loop trough the list of modifiers and run them in order,
|
|
* each of them will then edit the data object.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {dataObject} data
|
|
* @param {Array} modifiers
|
|
* @param {String} ends - Optional modifier name used as stopper
|
|
* @returns {dataObject}
|
|
*/
|
|
function runModifiers(modifiers, data, ends) {
|
|
var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
|
|
|
|
modifiersToRun.forEach(function (modifier) {
|
|
if (modifier['function']) {
|
|
// eslint-disable-line dot-notation
|
|
console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
|
|
}
|
|
var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
|
|
if (modifier.enabled && isFunction$1(fn)) {
|
|
// Add properties to offsets to make them a complete clientRect object
|
|
// we do this before each modifier to make sure the previous one doesn't
|
|
// mess with these values
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
data.offsets.reference = getClientRect(data.offsets.reference);
|
|
|
|
data = fn(data, modifier);
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Updates the position of the popper, computing the new offsets and applying
|
|
* the new style.<br />
|
|
* Prefer `scheduleUpdate` over `update` because of performance reasons.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function update() {
|
|
// if popper is destroyed, don't perform any further update
|
|
if (this.state.isDestroyed) {
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
instance: this,
|
|
styles: {},
|
|
arrowStyles: {},
|
|
attributes: {},
|
|
flipped: false,
|
|
offsets: {}
|
|
};
|
|
|
|
// compute reference element offsets
|
|
data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding);
|
|
|
|
// store the computed placement inside `originalPlacement`
|
|
data.originalPlacement = data.placement;
|
|
|
|
data.positionFixed = this.options.positionFixed;
|
|
|
|
// compute the popper offsets
|
|
data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement);
|
|
|
|
data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute';
|
|
|
|
// run the modifiers
|
|
data = runModifiers(this.modifiers, data);
|
|
|
|
// the first `update` will call `onCreate` callback
|
|
// the other ones will call `onUpdate` callback
|
|
if (!this.state.isCreated) {
|
|
this.state.isCreated = true;
|
|
this.options.onCreate(data);
|
|
} else {
|
|
this.options.onUpdate(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier is enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierEnabled(modifiers, modifierName) {
|
|
return modifiers.some(function (_ref) {
|
|
var name = _ref.name,
|
|
enabled = _ref.enabled;
|
|
return enabled && name === modifierName;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the prefixed supported property name
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} property (camelCase)
|
|
* @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
|
|
*/
|
|
function getSupportedPropertyName(property) {
|
|
var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
|
|
var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
|
|
|
|
for (var i = 0; i < prefixes.length; i++) {
|
|
var prefix = prefixes[i];
|
|
var toCheck = prefix ? '' + prefix + upperProp : property;
|
|
if (typeof document.body.style[toCheck] !== 'undefined') {
|
|
return toCheck;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Destroys the popper.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function destroy$1() {
|
|
this.state.isDestroyed = true;
|
|
|
|
// touch DOM only if `applyStyle` modifier is enabled
|
|
if (isModifierEnabled(this.modifiers, 'applyStyle')) {
|
|
this.popper.removeAttribute('x-placement');
|
|
this.popper.style.position = '';
|
|
this.popper.style.top = '';
|
|
this.popper.style.left = '';
|
|
this.popper.style.right = '';
|
|
this.popper.style.bottom = '';
|
|
this.popper.style.willChange = '';
|
|
this.popper.style[getSupportedPropertyName('transform')] = '';
|
|
}
|
|
|
|
this.disableEventListeners();
|
|
|
|
// remove the popper if user explicitly asked for the deletion on destroy
|
|
// do not use `remove` because IE11 doesn't support it
|
|
if (this.options.removeOnDestroy) {
|
|
this.popper.parentNode.removeChild(this.popper);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the window associated with the element
|
|
* @argument {Element} element
|
|
* @returns {Window}
|
|
*/
|
|
function getWindow(element) {
|
|
var ownerDocument = element.ownerDocument;
|
|
return ownerDocument ? ownerDocument.defaultView : window;
|
|
}
|
|
|
|
function attachToScrollParents(scrollParent, event, callback, scrollParents) {
|
|
var isBody = scrollParent.nodeName === 'BODY';
|
|
var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
|
|
target.addEventListener(event, callback, { passive: true });
|
|
|
|
if (!isBody) {
|
|
attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
|
|
}
|
|
scrollParents.push(target);
|
|
}
|
|
|
|
/**
|
|
* Setup needed event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function setupEventListeners(reference, options, state, updateBound) {
|
|
// Resize event listener on window
|
|
state.updateBound = updateBound;
|
|
getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
|
|
|
|
// Scroll event listener on scroll parents
|
|
var scrollElement = getScrollParent(reference);
|
|
attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
|
|
state.scrollElement = scrollElement;
|
|
state.eventsEnabled = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will add resize/scroll events and start recalculating
|
|
* position of the popper element when they are triggered.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function enableEventListeners() {
|
|
if (!this.state.eventsEnabled) {
|
|
this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove event listeners used to update the popper position
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @private
|
|
*/
|
|
function removeEventListeners(reference, state) {
|
|
// Remove resize event listener on window
|
|
getWindow(reference).removeEventListener('resize', state.updateBound);
|
|
|
|
// Remove scroll event listener on scroll parents
|
|
state.scrollParents.forEach(function (target) {
|
|
target.removeEventListener('scroll', state.updateBound);
|
|
});
|
|
|
|
// Reset state
|
|
state.updateBound = null;
|
|
state.scrollParents = [];
|
|
state.scrollElement = null;
|
|
state.eventsEnabled = false;
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* It will remove resize/scroll events and won't recalculate popper position
|
|
* when they are triggered. It also won't trigger `onUpdate` callback anymore,
|
|
* unless you call `update` method manually.
|
|
* @method
|
|
* @memberof Popper
|
|
*/
|
|
function disableEventListeners() {
|
|
if (this.state.eventsEnabled) {
|
|
cancelAnimationFrame(this.scheduleUpdate);
|
|
this.state = removeEventListeners(this.reference, this.state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells if a given input is a number
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {*} input to check
|
|
* @return {Boolean}
|
|
*/
|
|
function isNumeric$1(n) {
|
|
return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
/**
|
|
* Set the style to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the style to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setStyles(element, styles) {
|
|
Object.keys(styles).forEach(function (prop) {
|
|
var unit = '';
|
|
// add unit if the value is numeric and is one of the following
|
|
if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric$1(styles[prop])) {
|
|
unit = 'px';
|
|
}
|
|
element.style[prop] = styles[prop] + unit;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the attributes to the given popper
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {Element} element - Element to apply the attributes to
|
|
* @argument {Object} styles
|
|
* Object with a list of properties and values which will be applied to the element
|
|
*/
|
|
function setAttributes(element, attributes) {
|
|
Object.keys(attributes).forEach(function (prop) {
|
|
var value = attributes[prop];
|
|
if (value !== false) {
|
|
element.setAttribute(prop, attributes[prop]);
|
|
} else {
|
|
element.removeAttribute(prop);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} data.styles - List of style properties - values to apply to popper element
|
|
* @argument {Object} data.attributes - List of attribute properties - values to apply to popper element
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The same data object
|
|
*/
|
|
function applyStyle(data) {
|
|
// any property present in `data.styles` will be applied to the popper,
|
|
// in this way we can make the 3rd party modifiers add custom styles to it
|
|
// Be aware, modifiers could override the properties defined in the previous
|
|
// lines of this modifier!
|
|
setStyles(data.instance.popper, data.styles);
|
|
|
|
// any property present in `data.attributes` will be applied to the popper,
|
|
// they will be set as HTML attributes of the element
|
|
setAttributes(data.instance.popper, data.attributes);
|
|
|
|
// if arrowElement is defined and arrowStyles has some properties
|
|
if (data.arrowElement && Object.keys(data.arrowStyles).length) {
|
|
setStyles(data.arrowElement, data.arrowStyles);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Set the x-placement attribute before everything else because it could be used
|
|
* to add margins to the popper margins needs to be calculated to get the
|
|
* correct popper offsets.
|
|
* @method
|
|
* @memberof Popper.modifiers
|
|
* @param {HTMLElement} reference - The reference element used to position the popper
|
|
* @param {HTMLElement} popper - The HTML element used as popper
|
|
* @param {Object} options - Popper.js options
|
|
*/
|
|
function applyStyleOnLoad(reference, popper, options, modifierOptions, state) {
|
|
// compute reference element offsets
|
|
var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed);
|
|
|
|
// compute auto placement, store placement inside the data object,
|
|
// modifiers will be able to edit `placement` if needed
|
|
// and refer to originalPlacement to know the original value
|
|
var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding);
|
|
|
|
popper.setAttribute('x-placement', placement);
|
|
|
|
// Apply `position` to popper before anything else because
|
|
// without the position applied we can't guarantee correct computations
|
|
setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' });
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Popper.Utils
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Boolean} shouldRound - If the offsets should be rounded at all
|
|
* @returns {Object} The popper's position offsets rounded
|
|
*
|
|
* The tale of pixel-perfect positioning. It's still not 100% perfect, but as
|
|
* good as it can be within reason.
|
|
* Discussion here: https://github.com/FezVrasta/popper.js/pull/715
|
|
*
|
|
* Low DPI screens cause a popper to be blurry if not using full pixels (Safari
|
|
* as well on High DPI screens).
|
|
*
|
|
* Firefox prefers no rounding for positioning and does not have blurriness on
|
|
* high DPI screens.
|
|
*
|
|
* Only horizontal placement and left/right values need to be considered.
|
|
*/
|
|
function getRoundedOffsets(data, shouldRound) {
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
var round = Math.round,
|
|
floor = Math.floor;
|
|
|
|
var noRound = function noRound(v) {
|
|
return v;
|
|
};
|
|
|
|
var referenceWidth = round(reference.width);
|
|
var popperWidth = round(popper.width);
|
|
|
|
var isVertical = ['left', 'right'].indexOf(data.placement) !== -1;
|
|
var isVariation = data.placement.indexOf('-') !== -1;
|
|
var sameWidthParity = referenceWidth % 2 === popperWidth % 2;
|
|
var bothOddWidth = referenceWidth % 2 === 1 && popperWidth % 2 === 1;
|
|
|
|
var horizontalToInteger = !shouldRound ? noRound : isVertical || isVariation || sameWidthParity ? round : floor;
|
|
var verticalToInteger = !shouldRound ? noRound : round;
|
|
|
|
return {
|
|
left: horizontalToInteger(bothOddWidth && !isVariation && shouldRound ? popper.left - 1 : popper.left),
|
|
top: verticalToInteger(popper.top),
|
|
bottom: verticalToInteger(popper.bottom),
|
|
right: horizontalToInteger(popper.right)
|
|
};
|
|
}
|
|
|
|
var isFirefox = isBrowser$1 && /Firefox/i.test(navigator.userAgent);
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function computeStyle(data, options) {
|
|
var x = options.x,
|
|
y = options.y;
|
|
var popper = data.offsets.popper;
|
|
|
|
// Remove this legacy support in Popper.js v2
|
|
|
|
var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'applyStyle';
|
|
}).gpuAcceleration;
|
|
if (legacyGpuAccelerationOption !== undefined) {
|
|
console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');
|
|
}
|
|
var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration;
|
|
|
|
var offsetParent = getOffsetParent(data.instance.popper);
|
|
var offsetParentRect = getBoundingClientRect(offsetParent);
|
|
|
|
// Styles
|
|
var styles = {
|
|
position: popper.position
|
|
};
|
|
|
|
var offsets = getRoundedOffsets(data, window.devicePixelRatio < 2 || !isFirefox);
|
|
|
|
var sideA = x === 'bottom' ? 'top' : 'bottom';
|
|
var sideB = y === 'right' ? 'left' : 'right';
|
|
|
|
// if gpuAcceleration is set to `true` and transform is supported,
|
|
// we use `translate3d` to apply the position to the popper we
|
|
// automatically use the supported prefixed version if needed
|
|
var prefixedProperty = getSupportedPropertyName('transform');
|
|
|
|
// now, let's make a step back and look at this code closely (wtf?)
|
|
// If the content of the popper grows once it's been positioned, it
|
|
// may happen that the popper gets misplaced because of the new content
|
|
// overflowing its reference element
|
|
// To avoid this problem, we provide two options (x and y), which allow
|
|
// the consumer to define the offset origin.
|
|
// If we position a popper on top of a reference element, we can set
|
|
// `x` to `top` to make the popper grow towards its top instead of
|
|
// its bottom.
|
|
var left = void 0,
|
|
top = void 0;
|
|
if (sideA === 'bottom') {
|
|
// when offsetParent is <html> the positioning is relative to the bottom of the screen (excluding the scrollbar)
|
|
// and not the bottom of the html element
|
|
if (offsetParent.nodeName === 'HTML') {
|
|
top = -offsetParent.clientHeight + offsets.bottom;
|
|
} else {
|
|
top = -offsetParentRect.height + offsets.bottom;
|
|
}
|
|
} else {
|
|
top = offsets.top;
|
|
}
|
|
if (sideB === 'right') {
|
|
if (offsetParent.nodeName === 'HTML') {
|
|
left = -offsetParent.clientWidth + offsets.right;
|
|
} else {
|
|
left = -offsetParentRect.width + offsets.right;
|
|
}
|
|
} else {
|
|
left = offsets.left;
|
|
}
|
|
if (gpuAcceleration && prefixedProperty) {
|
|
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
|
|
styles[sideA] = 0;
|
|
styles[sideB] = 0;
|
|
styles.willChange = 'transform';
|
|
} else {
|
|
// othwerise, we use the standard `top`, `left`, `bottom` and `right` properties
|
|
var invertTop = sideA === 'bottom' ? -1 : 1;
|
|
var invertLeft = sideB === 'right' ? -1 : 1;
|
|
styles[sideA] = top * invertTop;
|
|
styles[sideB] = left * invertLeft;
|
|
styles.willChange = sideA + ', ' + sideB;
|
|
}
|
|
|
|
// Attributes
|
|
var attributes = {
|
|
'x-placement': data.placement
|
|
};
|
|
|
|
// Update `data` attributes, styles and arrowStyles
|
|
data.attributes = _extends({}, attributes, data.attributes);
|
|
data.styles = _extends({}, styles, data.styles);
|
|
data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Helper used to know if the given modifier depends from another one.<br />
|
|
* It checks if the needed modifier is listed and enabled.
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @param {Array} modifiers - list of modifiers
|
|
* @param {String} requestingName - name of requesting modifier
|
|
* @param {String} requestedName - name of requested modifier
|
|
* @returns {Boolean}
|
|
*/
|
|
function isModifierRequired(modifiers, requestingName, requestedName) {
|
|
var requesting = find(modifiers, function (_ref) {
|
|
var name = _ref.name;
|
|
return name === requestingName;
|
|
});
|
|
|
|
var isRequired = !!requesting && modifiers.some(function (modifier) {
|
|
return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
|
|
});
|
|
|
|
if (!isRequired) {
|
|
var _requesting = '`' + requestingName + '`';
|
|
var requested = '`' + requestedName + '`';
|
|
console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!');
|
|
}
|
|
return isRequired;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function arrow(data, options) {
|
|
var _data$offsets$arrow;
|
|
|
|
// arrow depends on keepTogether in order to work
|
|
if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
|
|
return data;
|
|
}
|
|
|
|
var arrowElement = options.element;
|
|
|
|
// if arrowElement is a string, suppose it's a CSS selector
|
|
if (typeof arrowElement === 'string') {
|
|
arrowElement = data.instance.popper.querySelector(arrowElement);
|
|
|
|
// if arrowElement is not found, don't run the modifier
|
|
if (!arrowElement) {
|
|
return data;
|
|
}
|
|
} else {
|
|
// if the arrowElement isn't a query selector we must check that the
|
|
// provided DOM node is child of its popper node
|
|
if (!data.instance.popper.contains(arrowElement)) {
|
|
console.warn('WARNING: `arrow.element` must be child of its popper element!');
|
|
return data;
|
|
}
|
|
}
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
|
|
|
var len = isVertical ? 'height' : 'width';
|
|
var sideCapitalized = isVertical ? 'Top' : 'Left';
|
|
var side = sideCapitalized.toLowerCase();
|
|
var altSide = isVertical ? 'left' : 'top';
|
|
var opSide = isVertical ? 'bottom' : 'right';
|
|
var arrowElementSize = getOuterSizes(arrowElement)[len];
|
|
|
|
//
|
|
// extends keepTogether behavior making sure the popper and its
|
|
// reference have enough pixels in conjunction
|
|
//
|
|
|
|
// top/left side
|
|
if (reference[opSide] - arrowElementSize < popper[side]) {
|
|
data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize);
|
|
}
|
|
// bottom/right side
|
|
if (reference[side] + arrowElementSize > popper[opSide]) {
|
|
data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide];
|
|
}
|
|
data.offsets.popper = getClientRect(data.offsets.popper);
|
|
|
|
// compute center of the popper
|
|
var center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
|
|
|
|
// Compute the sideValue using the updated popper offsets
|
|
// take popper margin in account because we don't have this info available
|
|
var css = getStyleComputedProperty(data.instance.popper);
|
|
var popperMarginSide = parseFloat(css['margin' + sideCapitalized]);
|
|
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width']);
|
|
var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
|
|
|
|
// prevent arrowElement from being placed not contiguously to its popper
|
|
sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
|
|
|
|
data.arrowElement = arrowElement;
|
|
data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty$1(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty$1(_data$offsets$arrow, altSide, ''), _data$offsets$arrow);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the opposite placement variation of the given one
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement variation
|
|
* @returns {String} flipped placement variation
|
|
*/
|
|
function getOppositeVariation(variation) {
|
|
if (variation === 'end') {
|
|
return 'start';
|
|
} else if (variation === 'start') {
|
|
return 'end';
|
|
}
|
|
return variation;
|
|
}
|
|
|
|
/**
|
|
* List of accepted placements to use as values of the `placement` option.<br />
|
|
* Valid placements are:
|
|
* - `auto`
|
|
* - `top`
|
|
* - `right`
|
|
* - `bottom`
|
|
* - `left`
|
|
*
|
|
* Each placement can have a variation from this list:
|
|
* - `-start`
|
|
* - `-end`
|
|
*
|
|
* Variations are interpreted easily if you think of them as the left to right
|
|
* written languages. Horizontally (`top` and `bottom`), `start` is left and `end`
|
|
* is right.<br />
|
|
* Vertically (`left` and `right`), `start` is top and `end` is bottom.
|
|
*
|
|
* Some valid examples are:
|
|
* - `top-end` (on top of reference, right aligned)
|
|
* - `right-start` (on right of reference, top aligned)
|
|
* - `bottom` (on bottom, centered)
|
|
* - `auto-end` (on the side with more space available, alignment depends by placement)
|
|
*
|
|
* @static
|
|
* @type {Array}
|
|
* @enum {String}
|
|
* @readonly
|
|
* @method placements
|
|
* @memberof Popper
|
|
*/
|
|
var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start'];
|
|
|
|
// Get rid of `auto` `auto-start` and `auto-end`
|
|
var validPlacements = placements.slice(3);
|
|
|
|
/**
|
|
* Given an initial placement, returns all the subsequent placements
|
|
* clockwise (or counter-clockwise).
|
|
*
|
|
* @method
|
|
* @memberof Popper.Utils
|
|
* @argument {String} placement - A valid placement (it accepts variations)
|
|
* @argument {Boolean} counter - Set to true to walk the placements counterclockwise
|
|
* @returns {Array} placements including their variations
|
|
*/
|
|
function clockwise(placement) {
|
|
var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
var index = validPlacements.indexOf(placement);
|
|
var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index));
|
|
return counter ? arr.reverse() : arr;
|
|
}
|
|
|
|
var BEHAVIORS = {
|
|
FLIP: 'flip',
|
|
CLOCKWISE: 'clockwise',
|
|
COUNTERCLOCKWISE: 'counterclockwise'
|
|
};
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function flip(data, options) {
|
|
// if `inner` modifier is enabled, we can't use the `flip` modifier
|
|
if (isModifierEnabled(data.instance.modifiers, 'inner')) {
|
|
return data;
|
|
}
|
|
|
|
if (data.flipped && data.placement === data.originalPlacement) {
|
|
// seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
|
|
return data;
|
|
}
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed);
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var placementOpposite = getOppositePlacement(placement);
|
|
var variation = data.placement.split('-')[1] || '';
|
|
|
|
var flipOrder = [];
|
|
|
|
switch (options.behavior) {
|
|
case BEHAVIORS.FLIP:
|
|
flipOrder = [placement, placementOpposite];
|
|
break;
|
|
case BEHAVIORS.CLOCKWISE:
|
|
flipOrder = clockwise(placement);
|
|
break;
|
|
case BEHAVIORS.COUNTERCLOCKWISE:
|
|
flipOrder = clockwise(placement, true);
|
|
break;
|
|
default:
|
|
flipOrder = options.behavior;
|
|
}
|
|
|
|
flipOrder.forEach(function (step, index) {
|
|
if (placement !== step || flipOrder.length === index + 1) {
|
|
return data;
|
|
}
|
|
|
|
placement = data.placement.split('-')[0];
|
|
placementOpposite = getOppositePlacement(placement);
|
|
|
|
var popperOffsets = data.offsets.popper;
|
|
var refOffsets = data.offsets.reference;
|
|
|
|
// using floor because the reference offsets may contain decimals we are not going to consider here
|
|
var floor = Math.floor;
|
|
var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom);
|
|
|
|
var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left);
|
|
var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right);
|
|
var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top);
|
|
var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom);
|
|
|
|
var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom;
|
|
|
|
// flip the variation if required
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
|
|
// flips variation if reference element overflows boundaries
|
|
var flippedVariationByRef = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom);
|
|
|
|
// flips variation if popper content overflows boundaries
|
|
var flippedVariationByContent = !!options.flipVariationsByContent && (isVertical && variation === 'start' && overflowsRight || isVertical && variation === 'end' && overflowsLeft || !isVertical && variation === 'start' && overflowsBottom || !isVertical && variation === 'end' && overflowsTop);
|
|
|
|
var flippedVariation = flippedVariationByRef || flippedVariationByContent;
|
|
|
|
if (overlapsRef || overflowsBoundaries || flippedVariation) {
|
|
// this boolean to detect any flip loop
|
|
data.flipped = true;
|
|
|
|
if (overlapsRef || overflowsBoundaries) {
|
|
placement = flipOrder[index + 1];
|
|
}
|
|
|
|
if (flippedVariation) {
|
|
variation = getOppositeVariation(variation);
|
|
}
|
|
|
|
data.placement = placement + (variation ? '-' + variation : '');
|
|
|
|
// this object contains `position`, we want to preserve it along with
|
|
// any additional property we may add in the future
|
|
data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement));
|
|
|
|
data = runModifiers(data.instance.modifiers, data, 'flip');
|
|
}
|
|
});
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function keepTogether(data) {
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var placement = data.placement.split('-')[0];
|
|
var floor = Math.floor;
|
|
var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
|
|
var side = isVertical ? 'right' : 'bottom';
|
|
var opSide = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
if (popper[side] < floor(reference[opSide])) {
|
|
data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement];
|
|
}
|
|
if (popper[opSide] > floor(reference[side])) {
|
|
data.offsets.popper[opSide] = floor(reference[side]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Converts a string containing value + unit into a px value number
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} str - Value + unit string
|
|
* @argument {String} measurement - `height` or `width`
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @returns {Number|String}
|
|
* Value in pixels, or original string if no values were extracted
|
|
*/
|
|
function toValue(str, measurement, popperOffsets, referenceOffsets) {
|
|
// separate value from unit
|
|
var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/);
|
|
var value = +split[1];
|
|
var unit = split[2];
|
|
|
|
// If it's not a number it's an operator, I guess
|
|
if (!value) {
|
|
return str;
|
|
}
|
|
|
|
if (unit.indexOf('%') === 0) {
|
|
var element = void 0;
|
|
switch (unit) {
|
|
case '%p':
|
|
element = popperOffsets;
|
|
break;
|
|
case '%':
|
|
case '%r':
|
|
default:
|
|
element = referenceOffsets;
|
|
}
|
|
|
|
var rect = getClientRect(element);
|
|
return rect[measurement] / 100 * value;
|
|
} else if (unit === 'vh' || unit === 'vw') {
|
|
// if is a vh or vw, we calculate the size based on the viewport
|
|
var size = void 0;
|
|
if (unit === 'vh') {
|
|
size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
|
} else {
|
|
size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
|
}
|
|
return size / 100 * value;
|
|
} else {
|
|
// if is an explicit pixel unit, we get rid of the unit and keep the value
|
|
// if is an implicit unit, it's px, and we return just the value
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse an `offset` string to extrapolate `x` and `y` numeric offsets.
|
|
* @function
|
|
* @memberof {modifiers~offset}
|
|
* @private
|
|
* @argument {String} offset
|
|
* @argument {Object} popperOffsets
|
|
* @argument {Object} referenceOffsets
|
|
* @argument {String} basePlacement
|
|
* @returns {Array} a two cells array with x and y offsets in numbers
|
|
*/
|
|
function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) {
|
|
var offsets = [0, 0];
|
|
|
|
// Use height if placement is left or right and index is 0 otherwise use width
|
|
// in this way the first offset will use an axis and the second one
|
|
// will use the other one
|
|
var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1;
|
|
|
|
// Split the offset string to obtain a list of values and operands
|
|
// The regex addresses values with the plus or minus sign in front (+10, -20, etc)
|
|
var fragments = offset.split(/(\+|\-)/).map(function (frag) {
|
|
return frag.trim();
|
|
});
|
|
|
|
// Detect if the offset string contains a pair of values or a single one
|
|
// they could be separated by comma or space
|
|
var divider = fragments.indexOf(find(fragments, function (frag) {
|
|
return frag.search(/,|\s/) !== -1;
|
|
}));
|
|
|
|
if (fragments[divider] && fragments[divider].indexOf(',') === -1) {
|
|
console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');
|
|
}
|
|
|
|
// If divider is found, we divide the list of values and operands to divide
|
|
// them by ofset X and Y.
|
|
var splitRegex = /\s*,\s*|\s+/;
|
|
var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments];
|
|
|
|
// Convert the values with units to absolute pixels to allow our computations
|
|
ops = ops.map(function (op, index) {
|
|
// Most of the units rely on the orientation of the popper
|
|
var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width';
|
|
var mergeWithPrevious = false;
|
|
return op
|
|
// This aggregates any `+` or `-` sign that aren't considered operators
|
|
// e.g.: 10 + +5 => [10, +, +5]
|
|
.reduce(function (a, b) {
|
|
if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) {
|
|
a[a.length - 1] = b;
|
|
mergeWithPrevious = true;
|
|
return a;
|
|
} else if (mergeWithPrevious) {
|
|
a[a.length - 1] += b;
|
|
mergeWithPrevious = false;
|
|
return a;
|
|
} else {
|
|
return a.concat(b);
|
|
}
|
|
}, [])
|
|
// Here we convert the string values into number values (in px)
|
|
.map(function (str) {
|
|
return toValue(str, measurement, popperOffsets, referenceOffsets);
|
|
});
|
|
});
|
|
|
|
// Loop trough the offsets arrays and execute the operations
|
|
ops.forEach(function (op, index) {
|
|
op.forEach(function (frag, index2) {
|
|
if (isNumeric$1(frag)) {
|
|
offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1);
|
|
}
|
|
});
|
|
});
|
|
return offsets;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @argument {Number|String} options.offset=0
|
|
* The offset value as described in the modifier description
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function offset$1(data, _ref) {
|
|
var offset = _ref.offset;
|
|
var placement = data.placement,
|
|
_data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var basePlacement = placement.split('-')[0];
|
|
|
|
var offsets = void 0;
|
|
if (isNumeric$1(+offset)) {
|
|
offsets = [+offset, 0];
|
|
} else {
|
|
offsets = parseOffset(offset, popper, reference, basePlacement);
|
|
}
|
|
|
|
if (basePlacement === 'left') {
|
|
popper.top += offsets[0];
|
|
popper.left -= offsets[1];
|
|
} else if (basePlacement === 'right') {
|
|
popper.top += offsets[0];
|
|
popper.left += offsets[1];
|
|
} else if (basePlacement === 'top') {
|
|
popper.left += offsets[0];
|
|
popper.top -= offsets[1];
|
|
} else if (basePlacement === 'bottom') {
|
|
popper.left += offsets[0];
|
|
popper.top += offsets[1];
|
|
}
|
|
|
|
data.popper = popper;
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function preventOverflow(data, options) {
|
|
var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
|
|
|
|
// If offsetParent is the reference element, we really want to
|
|
// go one step up and use the next offsetParent as reference to
|
|
// avoid to make this modifier completely useless and look like broken
|
|
if (data.instance.reference === boundariesElement) {
|
|
boundariesElement = getOffsetParent(boundariesElement);
|
|
}
|
|
|
|
// NOTE: DOM access here
|
|
// resets the popper's position so that the document size can be calculated excluding
|
|
// the size of the popper element itself
|
|
var transformProp = getSupportedPropertyName('transform');
|
|
var popperStyles = data.instance.popper.style; // assignment to help minification
|
|
var top = popperStyles.top,
|
|
left = popperStyles.left,
|
|
transform = popperStyles[transformProp];
|
|
|
|
popperStyles.top = '';
|
|
popperStyles.left = '';
|
|
popperStyles[transformProp] = '';
|
|
|
|
var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed);
|
|
|
|
// NOTE: DOM access here
|
|
// restores the original style properties after the offsets have been computed
|
|
popperStyles.top = top;
|
|
popperStyles.left = left;
|
|
popperStyles[transformProp] = transform;
|
|
|
|
options.boundaries = boundaries;
|
|
|
|
var order = options.priority;
|
|
var popper = data.offsets.popper;
|
|
|
|
var check = {
|
|
primary: function primary(placement) {
|
|
var value = popper[placement];
|
|
if (popper[placement] < boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.max(popper[placement], boundaries[placement]);
|
|
}
|
|
return defineProperty$1({}, placement, value);
|
|
},
|
|
secondary: function secondary(placement) {
|
|
var mainSide = placement === 'right' ? 'left' : 'top';
|
|
var value = popper[mainSide];
|
|
if (popper[placement] > boundaries[placement] && !options.escapeWithReference) {
|
|
value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
|
|
}
|
|
return defineProperty$1({}, mainSide, value);
|
|
}
|
|
};
|
|
|
|
order.forEach(function (placement) {
|
|
var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary';
|
|
popper = _extends({}, popper, check[side](placement));
|
|
});
|
|
|
|
data.offsets.popper = popper;
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function shift(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var shiftvariation = placement.split('-')[1];
|
|
|
|
// if shift shiftvariation is specified, run the modifier
|
|
if (shiftvariation) {
|
|
var _data$offsets = data.offsets,
|
|
reference = _data$offsets.reference,
|
|
popper = _data$offsets.popper;
|
|
|
|
var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1;
|
|
var side = isVertical ? 'left' : 'top';
|
|
var measurement = isVertical ? 'width' : 'height';
|
|
|
|
var shiftOffsets = {
|
|
start: defineProperty$1({}, side, reference[side]),
|
|
end: defineProperty$1({}, side, reference[side] + reference[measurement] - popper[measurement])
|
|
};
|
|
|
|
data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by update method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function hide(data) {
|
|
if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) {
|
|
return data;
|
|
}
|
|
|
|
var refRect = data.offsets.reference;
|
|
var bound = find(data.instance.modifiers, function (modifier) {
|
|
return modifier.name === 'preventOverflow';
|
|
}).boundaries;
|
|
|
|
if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === true) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = true;
|
|
data.attributes['x-out-of-boundaries'] = '';
|
|
} else {
|
|
// Avoid unnecessary DOM access if visibility hasn't changed
|
|
if (data.hide === false) {
|
|
return data;
|
|
}
|
|
|
|
data.hide = false;
|
|
data.attributes['x-out-of-boundaries'] = false;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @function
|
|
* @memberof Modifiers
|
|
* @argument {Object} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {Object} The data object, properly modified
|
|
*/
|
|
function inner(data) {
|
|
var placement = data.placement;
|
|
var basePlacement = placement.split('-')[0];
|
|
var _data$offsets = data.offsets,
|
|
popper = _data$offsets.popper,
|
|
reference = _data$offsets.reference;
|
|
|
|
var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1;
|
|
|
|
var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1;
|
|
|
|
popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0);
|
|
|
|
data.placement = getOppositePlacement(placement);
|
|
data.offsets.popper = getClientRect(popper);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Modifier function, each modifier can have a function of this type assigned
|
|
* to its `fn` property.<br />
|
|
* These functions will be called on each update, this means that you must
|
|
* make sure they are performant enough to avoid performance bottlenecks.
|
|
*
|
|
* @function ModifierFn
|
|
* @argument {dataObject} data - The data object generated by `update` method
|
|
* @argument {Object} options - Modifiers configuration and options
|
|
* @returns {dataObject} The data object, properly modified
|
|
*/
|
|
|
|
/**
|
|
* Modifiers are plugins used to alter the behavior of your poppers.<br />
|
|
* Popper.js uses a set of 9 modifiers to provide all the basic functionalities
|
|
* needed by the library.
|
|
*
|
|
* Usually you don't want to override the `order`, `fn` and `onLoad` props.
|
|
* All the other properties are configurations that could be tweaked.
|
|
* @namespace modifiers
|
|
*/
|
|
var modifiers = {
|
|
/**
|
|
* Modifier used to shift the popper on the start or end of its reference
|
|
* element.<br />
|
|
* It will read the variation of the `placement` property.<br />
|
|
* It can be one either `-end` or `-start`.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
shift: {
|
|
/** @prop {number} order=100 - Index used to define the order of execution */
|
|
order: 100,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: shift
|
|
},
|
|
|
|
/**
|
|
* The `offset` modifier can shift your popper on both its axis.
|
|
*
|
|
* It accepts the following units:
|
|
* - `px` or unit-less, interpreted as pixels
|
|
* - `%` or `%r`, percentage relative to the length of the reference element
|
|
* - `%p`, percentage relative to the length of the popper element
|
|
* - `vw`, CSS viewport width unit
|
|
* - `vh`, CSS viewport height unit
|
|
*
|
|
* For length is intended the main axis relative to the placement of the popper.<br />
|
|
* This means that if the placement is `top` or `bottom`, the length will be the
|
|
* `width`. In case of `left` or `right`, it will be the `height`.
|
|
*
|
|
* You can provide a single value (as `Number` or `String`), or a pair of values
|
|
* as `String` divided by a comma or one (or more) white spaces.<br />
|
|
* The latter is a deprecated method because it leads to confusion and will be
|
|
* removed in v2.<br />
|
|
* Additionally, it accepts additions and subtractions between different units.
|
|
* Note that multiplications and divisions aren't supported.
|
|
*
|
|
* Valid examples are:
|
|
* ```
|
|
* 10
|
|
* '10%'
|
|
* '10, 10'
|
|
* '10%, 10'
|
|
* '10 + 10%'
|
|
* '10 - 5vh + 3%'
|
|
* '-10px + 5vh, 5px - 6%'
|
|
* ```
|
|
* > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap
|
|
* > with their reference element, unfortunately, you will have to disable the `flip` modifier.
|
|
* > You can read more on this at this [issue](https://github.com/FezVrasta/popper.js/issues/373).
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
offset: {
|
|
/** @prop {number} order=200 - Index used to define the order of execution */
|
|
order: 200,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: offset$1,
|
|
/** @prop {Number|String} offset=0
|
|
* The offset value as described in the modifier description
|
|
*/
|
|
offset: 0
|
|
},
|
|
|
|
/**
|
|
* Modifier used to prevent the popper from being positioned outside the boundary.
|
|
*
|
|
* A scenario exists where the reference itself is not within the boundaries.<br />
|
|
* We can say it has "escaped the boundaries" — or just "escaped".<br />
|
|
* In this case we need to decide whether the popper should either:
|
|
*
|
|
* - detach from the reference and remain "trapped" in the boundaries, or
|
|
* - if it should ignore the boundary and "escape with its reference"
|
|
*
|
|
* When `escapeWithReference` is set to`true` and reference is completely
|
|
* outside its boundaries, the popper will overflow (or completely leave)
|
|
* the boundaries in order to remain attached to the edge of the reference.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
preventOverflow: {
|
|
/** @prop {number} order=300 - Index used to define the order of execution */
|
|
order: 300,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: preventOverflow,
|
|
/**
|
|
* @prop {Array} [priority=['left','right','top','bottom']]
|
|
* Popper will try to prevent overflow following these priorities by default,
|
|
* then, it could overflow on the left and on top of the `boundariesElement`
|
|
*/
|
|
priority: ['left', 'right', 'top', 'bottom'],
|
|
/**
|
|
* @prop {number} padding=5
|
|
* Amount of pixel used to define a minimum distance between the boundaries
|
|
* and the popper. This makes sure the popper always has a little padding
|
|
* between the edges of its container
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='scrollParent'
|
|
* Boundaries used by the modifier. Can be `scrollParent`, `window`,
|
|
* `viewport` or any DOM element.
|
|
*/
|
|
boundariesElement: 'scrollParent'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make sure the reference and its popper stay near each other
|
|
* without leaving any gap between the two. Especially useful when the arrow is
|
|
* enabled and you want to ensure that it points to its reference element.
|
|
* It cares only about the first axis. You can still have poppers with margin
|
|
* between the popper and its reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
keepTogether: {
|
|
/** @prop {number} order=400 - Index used to define the order of execution */
|
|
order: 400,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: keepTogether
|
|
},
|
|
|
|
/**
|
|
* This modifier is used to move the `arrowElement` of the popper to make
|
|
* sure it is positioned between the reference element and its popper element.
|
|
* It will read the outer size of the `arrowElement` node to detect how many
|
|
* pixels of conjunction are needed.
|
|
*
|
|
* It has no effect if no `arrowElement` is provided.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
arrow: {
|
|
/** @prop {number} order=500 - Index used to define the order of execution */
|
|
order: 500,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: arrow,
|
|
/** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */
|
|
element: '[x-arrow]'
|
|
},
|
|
|
|
/**
|
|
* Modifier used to flip the popper's placement when it starts to overlap its
|
|
* reference element.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
*
|
|
* **NOTE:** this modifier will interrupt the current update cycle and will
|
|
* restart it if it detects the need to flip the placement.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
flip: {
|
|
/** @prop {number} order=600 - Index used to define the order of execution */
|
|
order: 600,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: flip,
|
|
/**
|
|
* @prop {String|Array} behavior='flip'
|
|
* The behavior used to change the popper's placement. It can be one of
|
|
* `flip`, `clockwise`, `counterclockwise` or an array with a list of valid
|
|
* placements (with optional variations)
|
|
*/
|
|
behavior: 'flip',
|
|
/**
|
|
* @prop {number} padding=5
|
|
* The popper will flip if it hits the edges of the `boundariesElement`
|
|
*/
|
|
padding: 5,
|
|
/**
|
|
* @prop {String|HTMLElement} boundariesElement='viewport'
|
|
* The element which will define the boundaries of the popper position.
|
|
* The popper will never be placed outside of the defined boundaries
|
|
* (except if `keepTogether` is enabled)
|
|
*/
|
|
boundariesElement: 'viewport',
|
|
/**
|
|
* @prop {Boolean} flipVariations=false
|
|
* The popper will switch placement variation between `-start` and `-end` when
|
|
* the reference element overlaps its boundaries.
|
|
*
|
|
* The original placement should have a set variation.
|
|
*/
|
|
flipVariations: false,
|
|
/**
|
|
* @prop {Boolean} flipVariationsByContent=false
|
|
* The popper will switch placement variation between `-start` and `-end` when
|
|
* the popper element overlaps its reference boundaries.
|
|
*
|
|
* The original placement should have a set variation.
|
|
*/
|
|
flipVariationsByContent: false
|
|
},
|
|
|
|
/**
|
|
* Modifier used to make the popper flow toward the inner of the reference element.
|
|
* By default, when this modifier is disabled, the popper will be placed outside
|
|
* the reference element.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
inner: {
|
|
/** @prop {number} order=700 - Index used to define the order of execution */
|
|
order: 700,
|
|
/** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */
|
|
enabled: false,
|
|
/** @prop {ModifierFn} */
|
|
fn: inner
|
|
},
|
|
|
|
/**
|
|
* Modifier used to hide the popper when its reference element is outside of the
|
|
* popper boundaries. It will set a `x-out-of-boundaries` attribute which can
|
|
* be used to hide with a CSS selector the popper when its reference is
|
|
* out of boundaries.
|
|
*
|
|
* Requires the `preventOverflow` modifier before it in order to work.
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
hide: {
|
|
/** @prop {number} order=800 - Index used to define the order of execution */
|
|
order: 800,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: hide
|
|
},
|
|
|
|
/**
|
|
* Computes the style that will be applied to the popper element to gets
|
|
* properly positioned.
|
|
*
|
|
* Note that this modifier will not touch the DOM, it just prepares the styles
|
|
* so that `applyStyle` modifier can apply it. This separation is useful
|
|
* in case you need to replace `applyStyle` with a custom implementation.
|
|
*
|
|
* This modifier has `850` as `order` value to maintain backward compatibility
|
|
* with previous versions of Popper.js. Expect the modifiers ordering method
|
|
* to change in future major versions of the library.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
computeStyle: {
|
|
/** @prop {number} order=850 - Index used to define the order of execution */
|
|
order: 850,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: computeStyle,
|
|
/**
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3D transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties
|
|
*/
|
|
gpuAcceleration: true,
|
|
/**
|
|
* @prop {string} [x='bottom']
|
|
* Where to anchor the X axis (`bottom` or `top`). AKA X offset origin.
|
|
* Change this if your popper should grow in a direction different from `bottom`
|
|
*/
|
|
x: 'bottom',
|
|
/**
|
|
* @prop {string} [x='left']
|
|
* Where to anchor the Y axis (`left` or `right`). AKA Y offset origin.
|
|
* Change this if your popper should grow in a direction different from `right`
|
|
*/
|
|
y: 'right'
|
|
},
|
|
|
|
/**
|
|
* Applies the computed styles to the popper element.
|
|
*
|
|
* All the DOM manipulations are limited to this modifier. This is useful in case
|
|
* you want to integrate Popper.js inside a framework or view library and you
|
|
* want to delegate all the DOM manipulations to it.
|
|
*
|
|
* Note that if you disable this modifier, you must make sure the popper element
|
|
* has its position set to `absolute` before Popper.js can do its work!
|
|
*
|
|
* Just disable this modifier and define your own to achieve the desired effect.
|
|
*
|
|
* @memberof modifiers
|
|
* @inner
|
|
*/
|
|
applyStyle: {
|
|
/** @prop {number} order=900 - Index used to define the order of execution */
|
|
order: 900,
|
|
/** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */
|
|
enabled: true,
|
|
/** @prop {ModifierFn} */
|
|
fn: applyStyle,
|
|
/** @prop {Function} */
|
|
onLoad: applyStyleOnLoad,
|
|
/**
|
|
* @deprecated since version 1.10.0, the property moved to `computeStyle` modifier
|
|
* @prop {Boolean} gpuAcceleration=true
|
|
* If true, it uses the CSS 3D transformation to position the popper.
|
|
* Otherwise, it will use the `top` and `left` properties
|
|
*/
|
|
gpuAcceleration: undefined
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The `dataObject` is an object containing all the information used by Popper.js.
|
|
* This object is passed to modifiers and to the `onCreate` and `onUpdate` callbacks.
|
|
* @name dataObject
|
|
* @property {Object} data.instance The Popper.js instance
|
|
* @property {String} data.placement Placement applied to popper
|
|
* @property {String} data.originalPlacement Placement originally defined on init
|
|
* @property {Boolean} data.flipped True if popper has been flipped by flip modifier
|
|
* @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper
|
|
* @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier
|
|
* @property {Object} data.styles Any CSS property defined here will be applied to the popper. It expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow. It expects the JavaScript nomenclature (eg. `marginBottom`)
|
|
* @property {Object} data.boundaries Offsets of the popper boundaries
|
|
* @property {Object} data.offsets The measurements of popper, reference and arrow elements
|
|
* @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values
|
|
* @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0
|
|
*/
|
|
|
|
/**
|
|
* Default options provided to Popper.js constructor.<br />
|
|
* These can be overridden using the `options` argument of Popper.js.<br />
|
|
* To override an option, simply pass an object with the same
|
|
* structure of the `options` object, as the 3rd argument. For example:
|
|
* ```
|
|
* new Popper(ref, pop, {
|
|
* modifiers: {
|
|
* preventOverflow: { enabled: false }
|
|
* }
|
|
* })
|
|
* ```
|
|
* @type {Object}
|
|
* @static
|
|
* @memberof Popper
|
|
*/
|
|
var Defaults = {
|
|
/**
|
|
* Popper's placement.
|
|
* @prop {Popper.placements} placement='bottom'
|
|
*/
|
|
placement: 'bottom',
|
|
|
|
/**
|
|
* Set this to true if you want popper to position it self in 'fixed' mode
|
|
* @prop {Boolean} positionFixed=false
|
|
*/
|
|
positionFixed: false,
|
|
|
|
/**
|
|
* Whether events (resize, scroll) are initially enabled.
|
|
* @prop {Boolean} eventsEnabled=true
|
|
*/
|
|
eventsEnabled: true,
|
|
|
|
/**
|
|
* Set to true if you want to automatically remove the popper when
|
|
* you call the `destroy` method.
|
|
* @prop {Boolean} removeOnDestroy=false
|
|
*/
|
|
removeOnDestroy: false,
|
|
|
|
/**
|
|
* Callback called when the popper is created.<br />
|
|
* By default, it is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onCreate}
|
|
*/
|
|
onCreate: function onCreate() {},
|
|
|
|
/**
|
|
* Callback called when the popper is updated. This callback is not called
|
|
* on the initialization/creation of the popper, but only on subsequent
|
|
* updates.<br />
|
|
* By default, it is set to no-op.<br />
|
|
* Access Popper.js instance with `data.instance`.
|
|
* @prop {onUpdate}
|
|
*/
|
|
onUpdate: function onUpdate() {},
|
|
|
|
/**
|
|
* List of modifiers used to modify the offsets before they are applied to the popper.
|
|
* They provide most of the functionalities of Popper.js.
|
|
* @prop {modifiers}
|
|
*/
|
|
modifiers: modifiers
|
|
};
|
|
|
|
/**
|
|
* @callback onCreate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
/**
|
|
* @callback onUpdate
|
|
* @param {dataObject} data
|
|
*/
|
|
|
|
// Utils
|
|
// Methods
|
|
var Popper = function () {
|
|
/**
|
|
* Creates a new Popper.js instance.
|
|
* @class Popper
|
|
* @param {Element|referenceObject} reference - The reference element used to position the popper
|
|
* @param {Element} popper - The HTML / XML element used as the popper
|
|
* @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults)
|
|
* @return {Object} instance - The generated Popper.js instance
|
|
*/
|
|
function Popper(reference, popper) {
|
|
var _this = this;
|
|
|
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
classCallCheck(this, Popper);
|
|
|
|
this.scheduleUpdate = function () {
|
|
return requestAnimationFrame(_this.update);
|
|
};
|
|
|
|
// make update() debounced, so that it only runs at most once-per-tick
|
|
this.update = debounce(this.update.bind(this));
|
|
|
|
// with {} we create a new object with the options inside it
|
|
this.options = _extends({}, Popper.Defaults, options);
|
|
|
|
// init state
|
|
this.state = {
|
|
isDestroyed: false,
|
|
isCreated: false,
|
|
scrollParents: []
|
|
};
|
|
|
|
// get reference and popper elements (allow jQuery wrappers)
|
|
this.reference = reference && reference.jquery ? reference[0] : reference;
|
|
this.popper = popper && popper.jquery ? popper[0] : popper;
|
|
|
|
// Deep merge modifiers options
|
|
this.options.modifiers = {};
|
|
Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) {
|
|
_this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {});
|
|
});
|
|
|
|
// Refactoring modifiers' list (Object => Array)
|
|
this.modifiers = Object.keys(this.options.modifiers).map(function (name) {
|
|
return _extends({
|
|
name: name
|
|
}, _this.options.modifiers[name]);
|
|
})
|
|
// sort the modifiers by order
|
|
.sort(function (a, b) {
|
|
return a.order - b.order;
|
|
});
|
|
|
|
// modifiers have the ability to execute arbitrary code when Popper.js get inited
|
|
// such code is executed in the same order of its modifier
|
|
// they could add new properties to their options configuration
|
|
// BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`!
|
|
this.modifiers.forEach(function (modifierOptions) {
|
|
if (modifierOptions.enabled && isFunction$1(modifierOptions.onLoad)) {
|
|
modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state);
|
|
}
|
|
});
|
|
|
|
// fire the first update to position the popper in the right place
|
|
this.update();
|
|
|
|
var eventsEnabled = this.options.eventsEnabled;
|
|
if (eventsEnabled) {
|
|
// setup event listeners, they will take care of update the position in specific situations
|
|
this.enableEventListeners();
|
|
}
|
|
|
|
this.state.eventsEnabled = eventsEnabled;
|
|
}
|
|
|
|
// We can't use class properties because they don't get listed in the
|
|
// class prototype and break stuff like Sinon stubs
|
|
|
|
|
|
createClass(Popper, [{
|
|
key: 'update',
|
|
value: function update$$1() {
|
|
return update.call(this);
|
|
}
|
|
}, {
|
|
key: 'destroy',
|
|
value: function destroy$$1() {
|
|
return destroy$1.call(this);
|
|
}
|
|
}, {
|
|
key: 'enableEventListeners',
|
|
value: function enableEventListeners$$1() {
|
|
return enableEventListeners.call(this);
|
|
}
|
|
}, {
|
|
key: 'disableEventListeners',
|
|
value: function disableEventListeners$$1() {
|
|
return disableEventListeners.call(this);
|
|
}
|
|
|
|
/**
|
|
* Schedules an update. It will run on the next UI update available.
|
|
* @method scheduleUpdate
|
|
* @memberof Popper
|
|
*/
|
|
|
|
|
|
/**
|
|
* Collection of utilities useful when writing custom modifiers.
|
|
* Starting from version 1.7, this method is available only if you
|
|
* include `popper-utils.js` before `popper.js`.
|
|
*
|
|
* **DEPRECATION**: This way to access PopperUtils is deprecated
|
|
* and will be removed in v2! Use the PopperUtils module directly instead.
|
|
* Due to the high instability of the methods contained in Utils, we can't
|
|
* guarantee them to follow semver. Use them at your own risk!
|
|
* @static
|
|
* @private
|
|
* @type {Object}
|
|
* @deprecated since version 1.8
|
|
* @member Utils
|
|
* @memberof Popper
|
|
*/
|
|
|
|
}]);
|
|
return Popper;
|
|
}();
|
|
|
|
/**
|
|
* The `referenceObject` is an object that provides an interface compatible with Popper.js
|
|
* and lets you use it as replacement of a real DOM node.<br />
|
|
* You can use this method to position a popper relatively to a set of coordinates
|
|
* in case you don't have a DOM node to use as reference.
|
|
*
|
|
* ```
|
|
* new Popper(referenceObject, popperNode);
|
|
* ```
|
|
*
|
|
* NB: This feature isn't supported in Internet Explorer 10.
|
|
* @name referenceObject
|
|
* @property {Function} data.getBoundingClientRect
|
|
* A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method.
|
|
* @property {number} data.clientWidth
|
|
* An ES6 getter that will return the width of the virtual reference element.
|
|
* @property {number} data.clientHeight
|
|
* An ES6 getter that will return the height of the virtual reference element.
|
|
*/
|
|
|
|
|
|
Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils;
|
|
Popper.placements = placements;
|
|
Popper.Defaults = Defaults;
|
|
|
|
var PLACEMENT_TOP_START = 'top-start';
|
|
var PLACEMENT_TOP_END = 'top-end';
|
|
var PLACEMENT_BOTTOM_START = 'bottom-start';
|
|
var PLACEMENT_BOTTOM_END = 'bottom-end';
|
|
var PLACEMENT_RIGHT_START = 'right-start';
|
|
var PLACEMENT_LEFT_START = 'left-start';
|
|
|
|
var BvEvent = /*#__PURE__*/function () {
|
|
function BvEvent(type) {
|
|
var eventInit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
_classCallCheck(this, BvEvent);
|
|
|
|
// Start by emulating native Event constructor
|
|
if (!type) {
|
|
/* istanbul ignore next */
|
|
throw new TypeError("Failed to construct '".concat(this.constructor.name, "'. 1 argument required, ").concat(arguments.length, " given."));
|
|
} // Merge defaults first, the eventInit, and the type last
|
|
// so it can't be overwritten
|
|
|
|
|
|
assign(this, BvEvent.Defaults, this.constructor.Defaults, eventInit, {
|
|
type: type
|
|
}); // Freeze some props as readonly, but leave them enumerable
|
|
|
|
defineProperties(this, {
|
|
type: readonlyDescriptor(),
|
|
cancelable: readonlyDescriptor(),
|
|
nativeEvent: readonlyDescriptor(),
|
|
target: readonlyDescriptor(),
|
|
relatedTarget: readonlyDescriptor(),
|
|
vueTarget: readonlyDescriptor(),
|
|
componentId: readonlyDescriptor()
|
|
}); // Create a private variable using closure scoping
|
|
|
|
var defaultPrevented = false; // Recreate preventDefault method. One way setter
|
|
|
|
this.preventDefault = function preventDefault() {
|
|
if (this.cancelable) {
|
|
defaultPrevented = true;
|
|
}
|
|
}; // Create `defaultPrevented` publicly accessible prop that
|
|
// can only be altered by the preventDefault method
|
|
|
|
|
|
defineProperty(this, 'defaultPrevented', {
|
|
enumerable: true,
|
|
get: function get() {
|
|
return defaultPrevented;
|
|
}
|
|
});
|
|
}
|
|
|
|
_createClass(BvEvent, null, [{
|
|
key: "Defaults",
|
|
get: function get() {
|
|
return {
|
|
type: '',
|
|
cancelable: true,
|
|
nativeEvent: null,
|
|
target: null,
|
|
relatedTarget: null,
|
|
vueTarget: null,
|
|
componentId: null
|
|
};
|
|
}
|
|
}]);
|
|
|
|
return BvEvent;
|
|
}(); // Named Exports
|
|
|
|
var clickOutMixin = {
|
|
data: function data() {
|
|
return {
|
|
listenForClickOut: false
|
|
};
|
|
},
|
|
watch: {
|
|
listenForClickOut: function listenForClickOut(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
eventOff(this.clickOutElement, this.clickOutEventName, this._clickOutHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
|
|
if (newValue) {
|
|
eventOn(this.clickOutElement, this.clickOutEventName, this._clickOutHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
beforeCreate: function beforeCreate() {
|
|
// Declare non-reactive properties
|
|
this.clickOutElement = null;
|
|
this.clickOutEventName = null;
|
|
},
|
|
mounted: function mounted() {
|
|
if (!this.clickOutElement) {
|
|
this.clickOutElement = document;
|
|
}
|
|
|
|
if (!this.clickOutEventName) {
|
|
this.clickOutEventName = 'click';
|
|
}
|
|
|
|
if (this.listenForClickOut) {
|
|
eventOn(this.clickOutElement, this.clickOutEventName, this._clickOutHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
eventOff(this.clickOutElement, this.clickOutEventName, this._clickOutHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
},
|
|
methods: {
|
|
isClickOut: function isClickOut(evt) {
|
|
return !contains(this.$el, evt.target);
|
|
},
|
|
_clickOutHandler: function _clickOutHandler(evt) {
|
|
if (this.clickOutHandler && this.isClickOut(evt)) {
|
|
this.clickOutHandler(evt);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var focusInMixin = {
|
|
data: function data() {
|
|
return {
|
|
listenForFocusIn: false
|
|
};
|
|
},
|
|
watch: {
|
|
listenForFocusIn: function listenForFocusIn(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
eventOff(this.focusInElement, 'focusin', this._focusInHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
|
|
if (newValue) {
|
|
eventOn(this.focusInElement, 'focusin', this._focusInHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
beforeCreate: function beforeCreate() {
|
|
// Declare non-reactive properties
|
|
this.focusInElement = null;
|
|
},
|
|
mounted: function mounted() {
|
|
if (!this.focusInElement) {
|
|
this.focusInElement = document;
|
|
}
|
|
|
|
if (this.listenForFocusIn) {
|
|
eventOn(this.focusInElement, 'focusin', this._focusInHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
eventOff(this.focusInElement, 'focusin', this._focusInHandler, EVENT_OPTIONS_NO_CAPTURE);
|
|
},
|
|
methods: {
|
|
_focusInHandler: function _focusInHandler(evt) {
|
|
if (this.focusInHandler) {
|
|
this.focusInHandler(evt);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Root dropdown event names
|
|
|
|
var ROOT_EVENT_PREFIX = 'bv::dropdown::';
|
|
var ROOT_EVENT_SHOWN = "".concat(ROOT_EVENT_PREFIX, "shown");
|
|
var ROOT_EVENT_HIDDEN = "".concat(ROOT_EVENT_PREFIX, "hidden"); // CSS selectors
|
|
|
|
var SELECTOR_FORM_CHILD = '.dropdown form';
|
|
var SELECTOR_ITEM = ['.dropdown-item', '.b-dropdown-form'].map(function (selector) {
|
|
return "".concat(selector, ":not(.disabled):not([disabled])");
|
|
}).join(', '); // --- Utility methods ---
|
|
// Return an array of visible items
|
|
|
|
var filterVisibles = function filterVisibles(els) {
|
|
return (els || []).filter(isVisible);
|
|
}; // --- Props ---
|
|
|
|
|
|
var commonProps = makePropsConfigurable({
|
|
dropup: {
|
|
// place on top if possible
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dropright: {
|
|
// place right if possible
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dropleft: {
|
|
// place left if possible
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
right: {
|
|
// Right align menu (default is left align)
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
offset: {
|
|
// Number of pixels to offset menu, or a CSS unit value (i.e. `1px`, `1rem`, etc.)
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
noFlip: {
|
|
// Disable auto-flipping of menu from bottom <=> top
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
popperOpts: {
|
|
type: Object,
|
|
default: function _default() {}
|
|
},
|
|
boundary: {
|
|
// String: `scrollParent`, `window` or `viewport`
|
|
// HTMLElement: HTML Element reference
|
|
type: [String, HTMLElement],
|
|
default: 'scrollParent'
|
|
}
|
|
}, NAME_DROPDOWN);
|
|
var props$m = _objectSpread2(_objectSpread2({}, commonProps), makePropsConfigurable({
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_DROPDOWN)); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var dropdownMixin = {
|
|
mixins: [idMixin, clickOutMixin, focusInMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvDropdown: this
|
|
};
|
|
},
|
|
inject: {
|
|
bvNavbar: {
|
|
default: null
|
|
}
|
|
},
|
|
props: props$m,
|
|
data: function data() {
|
|
return {
|
|
visible: false,
|
|
visibleChangePrevented: false
|
|
};
|
|
},
|
|
computed: {
|
|
inNavbar: function inNavbar() {
|
|
return !isNull(this.bvNavbar);
|
|
},
|
|
toggler: function toggler() {
|
|
var toggle = this.$refs.toggle;
|
|
return toggle ? toggle.$el || toggle : null;
|
|
},
|
|
directionClass: function directionClass() {
|
|
if (this.dropup) {
|
|
return 'dropup';
|
|
} else if (this.dropright) {
|
|
return 'dropright';
|
|
} else if (this.dropleft) {
|
|
return 'dropleft';
|
|
}
|
|
|
|
return '';
|
|
},
|
|
boundaryClass: function boundaryClass() {
|
|
// Position `static` is needed to allow menu to "breakout" of the `scrollParent`
|
|
// boundaries when boundary is anything other than `scrollParent`
|
|
// See: https://github.com/twbs/bootstrap/issues/24251#issuecomment-341413786
|
|
return this.boundary !== 'scrollParent' && !this.inNavbar ? 'position-static' : '';
|
|
}
|
|
},
|
|
watch: {
|
|
visible: function visible(newValue, oldValue) {
|
|
if (this.visibleChangePrevented) {
|
|
this.visibleChangePrevented = false;
|
|
return;
|
|
}
|
|
|
|
if (newValue !== oldValue) {
|
|
var evtName = newValue ? 'show' : 'hide';
|
|
var bvEvt = new BvEvent(evtName, {
|
|
cancelable: true,
|
|
vueTarget: this,
|
|
target: this.$refs.menu,
|
|
relatedTarget: null,
|
|
componentId: this.safeId ? this.safeId() : this.id || null
|
|
});
|
|
this.emitEvent(bvEvt);
|
|
|
|
if (bvEvt.defaultPrevented) {
|
|
// Reset value and exit if canceled
|
|
this.visibleChangePrevented = true;
|
|
this.visible = oldValue; // Just in case a child element triggered `this.hide(true)`
|
|
|
|
this.$off('hidden', this.focusToggler);
|
|
return;
|
|
}
|
|
|
|
if (evtName === 'show') {
|
|
this.showMenu();
|
|
} else {
|
|
this.hideMenu();
|
|
}
|
|
}
|
|
},
|
|
disabled: function disabled(newValue, oldValue) {
|
|
if (newValue !== oldValue && newValue && this.visible) {
|
|
// Hide dropdown if disabled changes to true
|
|
this.visible = false;
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_popper = null;
|
|
this.$_hideTimeout = null;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
// In case we are inside a `<keep-alive>`
|
|
this.visible = false;
|
|
this.whileOpenListen(false);
|
|
this.destroyPopper();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.visible = false;
|
|
this.whileOpenListen(false);
|
|
this.destroyPopper();
|
|
this.clearHideTimeout();
|
|
},
|
|
methods: {
|
|
// Event emitter
|
|
emitEvent: function emitEvent(bvEvt) {
|
|
var type = bvEvt.type;
|
|
this.$emit(type, bvEvt);
|
|
this.$root.$emit("".concat(ROOT_EVENT_PREFIX).concat(type), bvEvt);
|
|
},
|
|
showMenu: function showMenu() {
|
|
var _this = this;
|
|
|
|
if (this.disabled) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Only instantiate Popper.js when dropdown is not in `<b-navbar>`
|
|
|
|
|
|
if (!this.inNavbar) {
|
|
if (typeof Popper === 'undefined') {
|
|
/* istanbul ignore next */
|
|
warn('Popper.js not found. Falling back to CSS positioning', NAME_DROPDOWN);
|
|
} else {
|
|
// For dropup with alignment we use the parent element as popper container
|
|
var el = this.dropup && this.right || this.split ? this.$el : this.$refs.toggle; // Make sure we have a reference to an element, not a component!
|
|
|
|
el = el.$el || el; // Instantiate Popper.js
|
|
|
|
this.createPopper(el);
|
|
}
|
|
} // Ensure other menus are closed
|
|
|
|
|
|
this.$root.$emit(ROOT_EVENT_SHOWN, this); // Enable listeners
|
|
|
|
this.whileOpenListen(true); // Wrap in `$nextTick()` to ensure menu is fully rendered/shown
|
|
|
|
this.$nextTick(function () {
|
|
// Focus on the menu container on show
|
|
_this.focusMenu(); // Emit the shown event
|
|
|
|
|
|
_this.$emit('shown');
|
|
});
|
|
},
|
|
hideMenu: function hideMenu() {
|
|
this.whileOpenListen(false);
|
|
this.$root.$emit(ROOT_EVENT_HIDDEN, this);
|
|
this.$emit('hidden');
|
|
this.destroyPopper();
|
|
},
|
|
createPopper: function createPopper(element) {
|
|
this.destroyPopper();
|
|
this.$_popper = new Popper(element, this.$refs.menu, this.getPopperConfig());
|
|
},
|
|
// Ensure popper event listeners are removed cleanly
|
|
destroyPopper: function destroyPopper() {
|
|
this.$_popper && this.$_popper.destroy();
|
|
this.$_popper = null;
|
|
},
|
|
// Instructs popper to re-computes the dropdown position
|
|
// useful if the content changes size
|
|
updatePopper: function updatePopper() {
|
|
try {
|
|
this.$_popper.scheduleUpdate();
|
|
} catch (_unused) {}
|
|
},
|
|
clearHideTimeout: function clearHideTimeout() {
|
|
clearTimeout(this.$_hideTimeout);
|
|
this.$_hideTimeout = null;
|
|
},
|
|
getPopperConfig: function getPopperConfig() {
|
|
var placement = PLACEMENT_BOTTOM_START;
|
|
|
|
if (this.dropup) {
|
|
placement = this.right ? PLACEMENT_TOP_END : PLACEMENT_TOP_START;
|
|
} else if (this.dropright) {
|
|
placement = PLACEMENT_RIGHT_START;
|
|
} else if (this.dropleft) {
|
|
placement = PLACEMENT_LEFT_START;
|
|
} else if (this.right) {
|
|
placement = PLACEMENT_BOTTOM_END;
|
|
}
|
|
|
|
var popperConfig = {
|
|
placement: placement,
|
|
modifiers: {
|
|
offset: {
|
|
offset: this.offset || 0
|
|
},
|
|
flip: {
|
|
enabled: !this.noFlip
|
|
}
|
|
}
|
|
};
|
|
var boundariesElement = this.boundary;
|
|
|
|
if (boundariesElement) {
|
|
popperConfig.modifiers.preventOverflow = {
|
|
boundariesElement: boundariesElement
|
|
};
|
|
}
|
|
|
|
return mergeDeep(popperConfig, this.popperOpts || {});
|
|
},
|
|
// Turn listeners on/off while open
|
|
whileOpenListen: function whileOpenListen(isOpen) {
|
|
// Hide the dropdown when clicked outside
|
|
this.listenForClickOut = isOpen; // Hide the dropdown when it loses focus
|
|
|
|
this.listenForFocusIn = isOpen; // Hide the dropdown when another dropdown is opened
|
|
|
|
var method = isOpen ? '$on' : '$off';
|
|
this.$root[method](ROOT_EVENT_SHOWN, this.rootCloseListener);
|
|
},
|
|
rootCloseListener: function rootCloseListener(vm) {
|
|
if (vm !== this) {
|
|
this.visible = false;
|
|
}
|
|
},
|
|
// Public method to show dropdown
|
|
show: function show() {
|
|
var _this2 = this;
|
|
|
|
if (this.disabled) {
|
|
return;
|
|
} // Wrap in a `requestAF()` to allow any previous
|
|
// click handling to occur first
|
|
|
|
|
|
requestAF(function () {
|
|
_this2.visible = true;
|
|
});
|
|
},
|
|
// Public method to hide dropdown
|
|
hide: function hide() {
|
|
var refocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
|
|
/* istanbul ignore next */
|
|
if (this.disabled) {
|
|
return;
|
|
}
|
|
|
|
this.visible = false;
|
|
|
|
if (refocus) {
|
|
// Child element is closing the dropdown on click
|
|
this.$once('hidden', this.focusToggler);
|
|
}
|
|
},
|
|
// Called only by a button that toggles the menu
|
|
toggle: function toggle(evt) {
|
|
evt = evt || {}; // Early exit when not a click event or ENTER, SPACE or DOWN were pressed
|
|
|
|
var _evt = evt,
|
|
type = _evt.type,
|
|
keyCode = _evt.keyCode;
|
|
|
|
if (type !== 'click' && !(type === 'keydown' && [CODE_ENTER, CODE_SPACE, CODE_DOWN].indexOf(keyCode) !== -1)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
if (this.disabled) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
|
|
this.$emit('toggle', evt);
|
|
stopEvent(evt); // Toggle visibility
|
|
|
|
if (this.visible) {
|
|
this.hide(true);
|
|
} else {
|
|
this.show();
|
|
}
|
|
},
|
|
// Mousedown handler for the toggle
|
|
|
|
/* istanbul ignore next */
|
|
onMousedown: function onMousedown(evt) {
|
|
// We prevent the 'mousedown' event for the toggle to stop the
|
|
// 'focusin' event from being fired
|
|
// The event would otherwise be picked up by the global 'focusin'
|
|
// listener and there is no cross-browser solution to detect it
|
|
// relates to the toggle click
|
|
// The 'click' event will still be fired and we handle closing
|
|
// other dropdowns there too
|
|
// See https://github.com/bootstrap-vue/bootstrap-vue/issues/4328
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
},
|
|
// Called from dropdown menu context
|
|
onKeydown: function onKeydown(evt) {
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (keyCode === CODE_ESC) {
|
|
// Close on ESC
|
|
this.onEsc(evt);
|
|
} else if (keyCode === CODE_DOWN) {
|
|
// Down Arrow
|
|
this.focusNext(evt, false);
|
|
} else if (keyCode === CODE_UP) {
|
|
// Up Arrow
|
|
this.focusNext(evt, true);
|
|
}
|
|
},
|
|
// If user presses ESC, close the menu
|
|
onEsc: function onEsc(evt) {
|
|
if (this.visible) {
|
|
this.visible = false;
|
|
stopEvent(evt); // Return focus to original trigger button
|
|
|
|
this.$once('hidden', this.focusToggler);
|
|
}
|
|
},
|
|
// Called only in split button mode, for the split button
|
|
onSplitClick: function onSplitClick(evt) {
|
|
/* istanbul ignore next */
|
|
if (this.disabled) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
|
|
this.$emit('click', evt);
|
|
},
|
|
// Shared hide handler between click-out and focus-in events
|
|
hideHandler: function hideHandler(evt) {
|
|
var _this3 = this;
|
|
|
|
var target = evt.target;
|
|
|
|
if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
|
|
this.clearHideTimeout();
|
|
this.$_hideTimeout = setTimeout(function () {
|
|
return _this3.hide();
|
|
}, this.inNavbar ? 300 : 0);
|
|
}
|
|
},
|
|
// Document click-out listener
|
|
clickOutHandler: function clickOutHandler(evt) {
|
|
this.hideHandler(evt);
|
|
},
|
|
// Document focus-in listener
|
|
focusInHandler: function focusInHandler(evt) {
|
|
this.hideHandler(evt);
|
|
},
|
|
// Keyboard nav
|
|
focusNext: function focusNext(evt, up) {
|
|
var _this4 = this;
|
|
|
|
// Ignore key up/down on form elements
|
|
var target = evt.target;
|
|
|
|
if (!this.visible || evt && closest(SELECTOR_FORM_CHILD, target)) {
|
|
/* istanbul ignore next: should never happen */
|
|
return;
|
|
}
|
|
|
|
stopEvent(evt);
|
|
this.$nextTick(function () {
|
|
var items = _this4.getItems();
|
|
|
|
if (items.length < 1) {
|
|
/* istanbul ignore next: should never happen */
|
|
return;
|
|
}
|
|
|
|
var index = items.indexOf(target);
|
|
|
|
if (up && index > 0) {
|
|
index--;
|
|
} else if (!up && index < items.length - 1) {
|
|
index++;
|
|
}
|
|
|
|
if (index < 0) {
|
|
/* istanbul ignore next: should never happen */
|
|
index = 0;
|
|
}
|
|
|
|
_this4.focusItem(index, items);
|
|
});
|
|
},
|
|
focusItem: function focusItem(index, items) {
|
|
var el = items.find(function (el, i) {
|
|
return i === index;
|
|
});
|
|
attemptFocus(el);
|
|
},
|
|
getItems: function getItems() {
|
|
// Get all items
|
|
return filterVisibles(selectAll(SELECTOR_ITEM, this.$refs.menu));
|
|
},
|
|
focusMenu: function focusMenu() {
|
|
attemptFocus(this.$refs.menu);
|
|
},
|
|
focusToggler: function focusToggler() {
|
|
var _this5 = this;
|
|
|
|
this.$nextTick(function () {
|
|
attemptFocus(_this5.toggler);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$n = makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$m), {}, {
|
|
text: {
|
|
type: String // default: null
|
|
|
|
},
|
|
html: {
|
|
type: String // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
block: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
menuClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
toggleTag: {
|
|
type: String,
|
|
default: 'button'
|
|
},
|
|
toggleText: {
|
|
// TODO: This really should be `toggleLabel`
|
|
type: String,
|
|
default: 'Toggle dropdown'
|
|
},
|
|
toggleClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
noCaret: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
split: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
splitHref: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
splitTo: {
|
|
type: [String, Object] // default: undefined
|
|
|
|
},
|
|
splitVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
splitClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
splitButtonType: {
|
|
type: String,
|
|
default: 'button',
|
|
validator: function validator(value) {
|
|
return arrayIncludes(['button', 'submit', 'reset'], value);
|
|
}
|
|
},
|
|
lazy: {
|
|
// If true, only render menu contents when open
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
role: {
|
|
type: String,
|
|
default: 'menu'
|
|
}
|
|
}), NAME_DROPDOWN); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BDropdown = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN,
|
|
mixins: [idMixin, dropdownMixin, normalizeSlotMixin],
|
|
props: props$n,
|
|
computed: {
|
|
dropdownClasses: function dropdownClasses() {
|
|
var block = this.block,
|
|
split = this.split;
|
|
return [this.directionClass, this.boundaryClass, {
|
|
show: this.visible,
|
|
// The 'btn-group' class is required in `split` mode for button alignment
|
|
// It needs also to be applied when `block` is disabled to allow multiple
|
|
// dropdowns to be aligned one line
|
|
'btn-group': split || !block,
|
|
// When `block` is enabled and we are in `split` mode the 'd-flex' class
|
|
// needs to be applied to allow the buttons to stretch to full width
|
|
'd-flex': block && split
|
|
}];
|
|
},
|
|
menuClasses: function menuClasses() {
|
|
return [this.menuClass, {
|
|
'dropdown-menu-right': this.right,
|
|
show: this.visible
|
|
}];
|
|
},
|
|
toggleClasses: function toggleClasses() {
|
|
var split = this.split;
|
|
return [this.toggleClass, {
|
|
'dropdown-toggle-split': split,
|
|
'dropdown-toggle-no-caret': this.noCaret && !split
|
|
}];
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var visible = this.visible,
|
|
variant = this.variant,
|
|
size = this.size,
|
|
block = this.block,
|
|
disabled = this.disabled,
|
|
split = this.split,
|
|
role = this.role,
|
|
hide = this.hide,
|
|
toggle = this.toggle;
|
|
var commonProps = {
|
|
variant: variant,
|
|
size: size,
|
|
block: block,
|
|
disabled: disabled
|
|
};
|
|
var buttonContentSlotName = 'button-content';
|
|
var $buttonChildren = this.normalizeSlot(buttonContentSlotName);
|
|
var buttonContentDomProps = this.hasNormalizedSlot(buttonContentSlotName) ? {} : htmlOrText(this.html, this.text);
|
|
var $split = h();
|
|
|
|
if (split) {
|
|
var splitTo = this.splitTo,
|
|
splitHref = this.splitHref,
|
|
splitButtonType = this.splitButtonType;
|
|
|
|
var btnProps = _objectSpread2(_objectSpread2({}, commonProps), {}, {
|
|
variant: this.splitVariant || variant
|
|
}); // We add these as needed due to <router-link> issues with
|
|
// defined property with `undefined`/`null` values
|
|
|
|
|
|
if (splitTo) {
|
|
btnProps.to = splitTo;
|
|
} else if (splitHref) {
|
|
btnProps.href = splitHref;
|
|
} else if (splitButtonType) {
|
|
btnProps.type = splitButtonType;
|
|
}
|
|
|
|
$split = h(BButton, {
|
|
class: this.splitClass,
|
|
attrs: {
|
|
id: this.safeId('_BV_button_')
|
|
},
|
|
props: btnProps,
|
|
domProps: buttonContentDomProps,
|
|
on: {
|
|
click: this.onSplitClick
|
|
},
|
|
ref: 'button'
|
|
}, $buttonChildren); // Overwrite button content for the toggle when in `split` mode
|
|
|
|
$buttonChildren = [h('span', {
|
|
class: ['sr-only']
|
|
}, [this.toggleText])];
|
|
buttonContentDomProps = {};
|
|
}
|
|
|
|
var $toggle = h(BButton, {
|
|
staticClass: 'dropdown-toggle',
|
|
class: this.toggleClasses,
|
|
attrs: {
|
|
id: this.safeId('_BV_toggle_'),
|
|
'aria-haspopup': 'true',
|
|
'aria-expanded': toString$1(visible)
|
|
},
|
|
props: _objectSpread2(_objectSpread2({}, commonProps), {}, {
|
|
tag: this.toggleTag,
|
|
block: block && !split
|
|
}),
|
|
domProps: buttonContentDomProps,
|
|
on: {
|
|
mousedown: this.onMousedown,
|
|
click: toggle,
|
|
keydown: toggle // Handle ENTER, SPACE and DOWN
|
|
|
|
},
|
|
ref: 'toggle'
|
|
}, $buttonChildren);
|
|
var $menu = h('ul', {
|
|
staticClass: 'dropdown-menu',
|
|
class: this.menuClasses,
|
|
attrs: {
|
|
role: role,
|
|
tabindex: '-1',
|
|
'aria-labelledby': this.safeId(split ? '_BV_button_' : '_BV_toggle_')
|
|
},
|
|
on: {
|
|
keydown: this.onKeydown // Handle UP, DOWN and ESC
|
|
|
|
},
|
|
ref: 'menu'
|
|
}, [!this.lazy || visible ? this.normalizeSlot(SLOT_NAME_DEFAULT, {
|
|
hide: hide
|
|
}) : h()]);
|
|
return h('div', {
|
|
staticClass: 'dropdown b-dropdown',
|
|
class: this.dropdownClasses,
|
|
attrs: {
|
|
id: this.safeId()
|
|
}
|
|
}, [$split, $toggle, $menu]);
|
|
}
|
|
});
|
|
|
|
var props$o = omit(props$1, ['event', 'routerTag']); // @vue/component
|
|
|
|
var BDropdownItem = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_ITEM,
|
|
mixins: [attrsMixin, normalizeSlotMixin],
|
|
inject: {
|
|
bvDropdown: {
|
|
default: null
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$o), {}, {
|
|
linkClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}), NAME_DROPDOWN_ITEM),
|
|
computed: {
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
role: 'menuitem'
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
closeDropdown: function closeDropdown() {
|
|
var _this = this;
|
|
|
|
// Close on next animation frame to allow <b-link> time to process
|
|
requestAF(function () {
|
|
if (_this.bvDropdown) {
|
|
_this.bvDropdown.hide(true);
|
|
}
|
|
});
|
|
},
|
|
onClick: function onClick(evt) {
|
|
this.$emit('click', evt);
|
|
this.closeDropdown();
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var linkClass = this.linkClass,
|
|
variant = this.variant,
|
|
active = this.active,
|
|
disabled = this.disabled,
|
|
onClick = this.onClick;
|
|
return h('li', {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}, [h(BLink, {
|
|
staticClass: 'dropdown-item',
|
|
class: [linkClass, _defineProperty({}, "text-".concat(variant), variant && !(active || disabled))],
|
|
props: this.$props,
|
|
attrs: this.computedAttrs,
|
|
on: {
|
|
click: onClick
|
|
},
|
|
ref: 'item'
|
|
}, this.normalizeSlot())]);
|
|
}
|
|
});
|
|
|
|
var props$p = makePropsConfigurable({
|
|
active: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
activeClass: {
|
|
type: String,
|
|
default: 'active'
|
|
},
|
|
buttonClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_DROPDOWN_ITEM_BUTTON); // @vue/component
|
|
|
|
var BDropdownItemButton = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_ITEM_BUTTON,
|
|
mixins: [attrsMixin, normalizeSlotMixin],
|
|
inject: {
|
|
bvDropdown: {
|
|
default: null
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$p,
|
|
computed: {
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
role: 'menuitem',
|
|
type: 'button',
|
|
disabled: this.disabled
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
closeDropdown: function closeDropdown() {
|
|
if (this.bvDropdown) {
|
|
this.bvDropdown.hide(true);
|
|
}
|
|
},
|
|
onClick: function onClick(evt) {
|
|
this.$emit('click', evt);
|
|
this.closeDropdown();
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _ref;
|
|
|
|
return h('li', {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}, [h('button', {
|
|
staticClass: 'dropdown-item',
|
|
class: [this.buttonClass, (_ref = {}, _defineProperty(_ref, this.activeClass, this.active), _defineProperty(_ref, "text-".concat(this.variant), this.variant && !(this.active || this.disabled)), _ref)],
|
|
attrs: this.computedAttrs,
|
|
on: {
|
|
click: this.onClick
|
|
},
|
|
ref: 'button'
|
|
}, this.normalizeSlot())]);
|
|
}
|
|
});
|
|
|
|
var props$q = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'header'
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_DROPDOWN_HEADER); // @vue/component
|
|
|
|
var BDropdownHeader = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_HEADER,
|
|
functional: true,
|
|
props: props$q,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var $attrs = data.attrs || {};
|
|
data.attrs = {};
|
|
return h('li', a(data, {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}), [h(props.tag, {
|
|
staticClass: 'dropdown-header',
|
|
class: _defineProperty({}, "text-".concat(props.variant), props.variant),
|
|
attrs: _objectSpread2(_objectSpread2({}, $attrs), {}, {
|
|
id: props.id || null,
|
|
role: 'heading'
|
|
}),
|
|
ref: 'header'
|
|
}, children)]);
|
|
}
|
|
});
|
|
|
|
var props$r = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'hr'
|
|
}
|
|
}, NAME_DROPDOWN_DIVIDER); // @vue/component
|
|
|
|
var BDropdownDivider = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_DIVIDER,
|
|
functional: true,
|
|
props: props$r,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data;
|
|
var $attrs = data.attrs || {};
|
|
data.attrs = {};
|
|
return h('li', a(data, {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}), [h(props.tag, {
|
|
staticClass: 'dropdown-divider',
|
|
attrs: _objectSpread2(_objectSpread2({}, $attrs), {}, {
|
|
role: 'separator',
|
|
'aria-orientation': 'horizontal'
|
|
}),
|
|
ref: 'divider'
|
|
})]);
|
|
}
|
|
});
|
|
|
|
var props$s = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
inline: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
novalidate: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
validated: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_FORM); // @vue/component
|
|
|
|
var BForm = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM,
|
|
functional: true,
|
|
props: props$s,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h('form', a(data, {
|
|
class: {
|
|
'form-inline': props.inline,
|
|
'was-validated': props.validated
|
|
},
|
|
attrs: {
|
|
id: props.id,
|
|
novalidate: props.novalidate
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var BDropdownForm = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_FORM,
|
|
functional: true,
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$s), {}, {
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
formClass: {
|
|
type: [String, Object, Array] // default: null
|
|
|
|
}
|
|
}), NAME_DROPDOWN_FORM),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var $attrs = data.attrs || {};
|
|
var $listeners = data.on || {};
|
|
data.attrs = {};
|
|
data.on = {};
|
|
return h('li', a(data, {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}), [h(BForm, {
|
|
ref: 'form',
|
|
staticClass: 'b-dropdown-form',
|
|
class: [props.formClass, {
|
|
disabled: props.disabled
|
|
}],
|
|
props: props,
|
|
attrs: _objectSpread2(_objectSpread2({}, $attrs), {}, {
|
|
disabled: props.disabled,
|
|
// Tab index of -1 for keyboard navigation
|
|
tabindex: props.disabled ? null : '-1'
|
|
}),
|
|
on: $listeners
|
|
}, children)]);
|
|
}
|
|
});
|
|
|
|
var BDropdownText = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_TEXT,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'p'
|
|
},
|
|
textClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_DROPDOWN_TEXT),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var tag = props.tag,
|
|
textClass = props.textClass,
|
|
variant = props.variant;
|
|
var attrs = data.attrs || {};
|
|
data.attrs = {};
|
|
return h('li', a(data, {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}), [h(tag, {
|
|
staticClass: 'b-dropdown-text',
|
|
class: [textClass, _defineProperty({}, "text-".concat(variant), variant)],
|
|
props: props,
|
|
attrs: attrs,
|
|
ref: 'text'
|
|
}, children)]);
|
|
}
|
|
});
|
|
|
|
var props$t = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
header: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerTag: {
|
|
type: String,
|
|
default: 'header'
|
|
},
|
|
headerVariant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerClasses: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
ariaDescribedby: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_DROPDOWN_GROUP); // @vue/component
|
|
|
|
var BDropdownGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_DROPDOWN_GROUP,
|
|
functional: true,
|
|
props: props$t,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var $slots = slots();
|
|
var $scopedSlots = scopedSlots || {};
|
|
var $attrs = data.attrs || {};
|
|
data.attrs = {};
|
|
var header;
|
|
var headerId = null;
|
|
|
|
if (hasNormalizedSlot(SLOT_NAME_HEADER, $scopedSlots, $slots) || props.header) {
|
|
headerId = props.id ? "_bv_".concat(props.id, "_group_dd_header") : null;
|
|
header = h(props.headerTag, {
|
|
staticClass: 'dropdown-header',
|
|
class: [props.headerClasses, _defineProperty({}, "text-".concat(props.variant), props.variant)],
|
|
attrs: {
|
|
id: headerId,
|
|
role: 'heading'
|
|
}
|
|
}, normalizeSlot(SLOT_NAME_HEADER, {}, $scopedSlots, $slots) || props.header);
|
|
}
|
|
|
|
var adb = [headerId, props.ariaDescribedBy].filter(identity).join(' ').trim();
|
|
return h('li', a(data, {
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}), [header || h(), h('ul', {
|
|
staticClass: 'list-unstyled',
|
|
attrs: _objectSpread2(_objectSpread2({}, $attrs), {}, {
|
|
id: props.id || null,
|
|
role: 'group',
|
|
'aria-describedby': adb || null
|
|
})
|
|
}, normalizeSlot(SLOT_NAME_DEFAULT, {}, $scopedSlots, $slots))]);
|
|
}
|
|
});
|
|
|
|
var DropdownPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BDropdown: BDropdown,
|
|
BDd: BDropdown,
|
|
BDropdownItem: BDropdownItem,
|
|
BDdItem: BDropdownItem,
|
|
BDropdownItemButton: BDropdownItemButton,
|
|
BDropdownItemBtn: BDropdownItemButton,
|
|
BDdItemButton: BDropdownItemButton,
|
|
BDdItemBtn: BDropdownItemButton,
|
|
BDropdownHeader: BDropdownHeader,
|
|
BDdHeader: BDropdownHeader,
|
|
BDropdownDivider: BDropdownDivider,
|
|
BDdDivider: BDropdownDivider,
|
|
BDropdownForm: BDropdownForm,
|
|
BDdForm: BDropdownForm,
|
|
BDropdownText: BDropdownText,
|
|
BDdText: BDropdownText,
|
|
BDropdownGroup: BDropdownGroup,
|
|
BDdGroup: BDropdownGroup
|
|
}
|
|
});
|
|
|
|
var TYPES = ['iframe', 'embed', 'video', 'object', 'img', 'b-img', 'b-img-lazy']; // --- Props ---
|
|
|
|
var props$u = makePropsConfigurable({
|
|
type: {
|
|
type: String,
|
|
default: 'iframe',
|
|
validator: function validator(value) {
|
|
return arrayIncludes(TYPES, value);
|
|
}
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
aspect: {
|
|
type: String,
|
|
default: '16by9'
|
|
}
|
|
}, NAME_EMBED); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BEmbed = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_EMBED,
|
|
functional: true,
|
|
props: props$u,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, {
|
|
ref: data.ref,
|
|
staticClass: 'embed-responsive',
|
|
class: _defineProperty({}, "embed-responsive-".concat(props.aspect), props.aspect)
|
|
}, [h(props.type, a(data, {
|
|
ref: '',
|
|
staticClass: 'embed-responsive-item'
|
|
}), children)]);
|
|
}
|
|
});
|
|
|
|
var EmbedPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BEmbed: BEmbed
|
|
}
|
|
});
|
|
|
|
var OPTIONS_OBJECT_DEPRECATED_MSG = 'Setting prop "options" to an object is deprecated. Use the array format instead.'; // --- Props ---
|
|
|
|
var props$v = makePropsConfigurable({
|
|
options: {
|
|
type: [Array, Object],
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
},
|
|
valueField: {
|
|
type: String,
|
|
default: 'value'
|
|
},
|
|
textField: {
|
|
type: String,
|
|
default: 'text'
|
|
},
|
|
htmlField: {
|
|
type: String,
|
|
default: 'html'
|
|
},
|
|
disabledField: {
|
|
type: String,
|
|
default: 'disabled'
|
|
}
|
|
}, 'formOptionControls'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formOptionsMixin = {
|
|
props: props$v,
|
|
computed: {
|
|
formOptions: function formOptions() {
|
|
return this.normalizeOptions(this.options);
|
|
}
|
|
},
|
|
methods: {
|
|
normalizeOption: function normalizeOption(option) {
|
|
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
|
|
// When the option is an object, normalize it
|
|
if (isPlainObject(option)) {
|
|
var value = get(option, this.valueField);
|
|
var text = get(option, this.textField);
|
|
return {
|
|
value: isUndefined(value) ? key || text : value,
|
|
text: stripTags(String(isUndefined(text) ? key : text)),
|
|
html: get(option, this.htmlField),
|
|
disabled: Boolean(get(option, this.disabledField))
|
|
};
|
|
} // Otherwise create an `<option>` object from the given value
|
|
|
|
|
|
return {
|
|
value: key || option,
|
|
text: stripTags(String(option)),
|
|
disabled: false
|
|
};
|
|
},
|
|
normalizeOptions: function normalizeOptions(options) {
|
|
var _this = this;
|
|
|
|
// Normalize the given options array
|
|
if (isArray(options)) {
|
|
return options.map(function (option) {
|
|
return _this.normalizeOption(option);
|
|
});
|
|
} else if (isPlainObject(options)) {
|
|
// Deprecate the object options format
|
|
warn(OPTIONS_OBJECT_DEPRECATED_MSG, this.$options.name); // Normalize a `options` object to an array of options
|
|
|
|
return keys(options).map(function (key) {
|
|
return _this.normalizeOption(options[key] || {}, key);
|
|
});
|
|
} // If not an array or object, return an empty array
|
|
|
|
/* istanbul ignore next */
|
|
|
|
|
|
return [];
|
|
}
|
|
}
|
|
};
|
|
|
|
var BFormDatalist = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_DATALIST,
|
|
mixins: [formOptionsMixin, normalizeSlotMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$v), {}, {
|
|
id: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
}), NAME_FORM_DATALIST),
|
|
render: function render(h) {
|
|
var $options = this.formOptions.map(function (option, index) {
|
|
var value = option.value,
|
|
text = option.text,
|
|
html = option.html,
|
|
disabled = option.disabled;
|
|
return h('option', {
|
|
attrs: {
|
|
value: value,
|
|
disabled: disabled
|
|
},
|
|
domProps: htmlOrText(html, text),
|
|
key: "option_".concat(index)
|
|
});
|
|
});
|
|
return h('datalist', {
|
|
attrs: {
|
|
id: this.id
|
|
}
|
|
}, [$options, this.normalizeSlot()]);
|
|
}
|
|
});
|
|
|
|
var props$w = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'small'
|
|
},
|
|
textVariant: {
|
|
type: String,
|
|
default: 'muted'
|
|
},
|
|
inline: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_FORM_TEXT); // @vue/component
|
|
|
|
var BFormText = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_TEXT,
|
|
functional: true,
|
|
props: props$w,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
class: _defineProperty({
|
|
'form-text': !props.inline
|
|
}, "text-".concat(props.textVariant), props.textVariant),
|
|
attrs: {
|
|
id: props.id
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$x = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
tooltip: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
forceShow: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
state: {
|
|
// Tri-state prop: `true`, `false`, or `null`
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
ariaLive: {
|
|
type: String // default: null
|
|
|
|
},
|
|
role: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_FORM_INVALID_FEEDBACK); // @vue/component
|
|
|
|
var BFormInvalidFeedback = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_INVALID_FEEDBACK,
|
|
functional: true,
|
|
props: props$x,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var show = props.forceShow === true || props.state === false;
|
|
return h(props.tag, a(data, {
|
|
class: {
|
|
'invalid-feedback': !props.tooltip,
|
|
'invalid-tooltip': props.tooltip,
|
|
'd-block': show
|
|
},
|
|
attrs: {
|
|
id: props.id || null,
|
|
role: props.role || null,
|
|
'aria-live': props.ariaLive || null,
|
|
'aria-atomic': props.ariaLive ? 'true' : null
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$y = makePropsConfigurable({
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
tooltip: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
forceShow: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
state: {
|
|
// Tri-state prop: `true`, `false`, or `null`
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
ariaLive: {
|
|
type: String // default: null
|
|
|
|
},
|
|
role: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_FORM_VALID_FEEDBACK); // @vue/component
|
|
|
|
var BFormValidFeedback = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_VALID_FEEDBACK,
|
|
functional: true,
|
|
props: props$y,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var show = props.forceShow === true || props.state === true;
|
|
return h(props.tag, a(data, {
|
|
class: {
|
|
'valid-feedback': !props.tooltip,
|
|
'valid-tooltip': props.tooltip,
|
|
'd-block': show
|
|
},
|
|
attrs: {
|
|
id: props.id || null,
|
|
role: props.role || null,
|
|
'aria-live': props.ariaLive || null,
|
|
'aria-atomic': props.ariaLive ? 'true' : null
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$z = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_FORM_ROW); // @vue/component
|
|
|
|
var BFormRow = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_ROW,
|
|
functional: true,
|
|
props: props$z,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'form-row'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var FormPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BForm: BForm,
|
|
BFormDatalist: BFormDatalist,
|
|
BDatalist: BFormDatalist,
|
|
BFormText: BFormText,
|
|
BFormInvalidFeedback: BFormInvalidFeedback,
|
|
BFormFeedback: BFormInvalidFeedback,
|
|
BFormValidFeedback: BFormValidFeedback,
|
|
// Added here for convenience
|
|
BFormRow: BFormRow
|
|
}
|
|
}); // BFormRow is not exported here as a named export, as it is exported by Layout
|
|
|
|
var looseIndexOf = function looseIndexOf(arr, val) {
|
|
// Assumes that the first argument is an array
|
|
for (var i = 0; i < arr.length; i++) {
|
|
if (looseEqual(arr[i], val)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
var SELECTOR = 'input, textarea, select'; // --- Props ---
|
|
|
|
var props$A = _objectSpread2({
|
|
id: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
name: {
|
|
type: String // default: undefined
|
|
|
|
}
|
|
}, makePropsConfigurable({
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
required: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
form: {
|
|
type: String // default: null
|
|
|
|
},
|
|
autofocus: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, 'formControls')); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formControlMixin = {
|
|
props: props$A,
|
|
mounted: function mounted() {
|
|
this.handleAutofocus();
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
this.handleAutofocus();
|
|
},
|
|
methods: {
|
|
handleAutofocus: function handleAutofocus() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
var el = _this.$el;
|
|
|
|
if (_this.autofocus && isVisible(el)) {
|
|
if (!matches(el, SELECTOR)) {
|
|
el = select(SELECTOR, el);
|
|
}
|
|
|
|
attemptFocus(el);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$B = makePropsConfigurable({
|
|
plain: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, 'formControls'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formCustomMixin = {
|
|
props: props$B,
|
|
computed: {
|
|
custom: function custom() {
|
|
return !this.plain;
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$C = makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$B), {}, {
|
|
value: {// Value when checked
|
|
// type: Object,
|
|
// default: undefined
|
|
},
|
|
checked: {// This is the v-model
|
|
// type: Object,
|
|
// default: undefined
|
|
},
|
|
inline: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
button: {
|
|
// Only applicable in standalone mode (non group)
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonVariant: {
|
|
// Only applicable when rendered with button style
|
|
type: String // default: null
|
|
|
|
},
|
|
ariaLabel: {
|
|
// Placed on the input if present.
|
|
type: String // default: null
|
|
|
|
},
|
|
ariaLabelledby: {
|
|
// Placed on the input if present.
|
|
type: String // default: null
|
|
|
|
}
|
|
}), 'formRadioCheckControls'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formRadioCheckMixin = {
|
|
mixins: [attrsMixin, formCustomMixin, normalizeSlotMixin],
|
|
inheritAttrs: false,
|
|
model: {
|
|
prop: 'checked',
|
|
event: 'input'
|
|
},
|
|
props: props$C,
|
|
data: function data() {
|
|
return {
|
|
localChecked: this.isGroup ? this.bvGroup.checked : this.checked,
|
|
hasFocus: false
|
|
};
|
|
},
|
|
computed: {
|
|
computedLocalChecked: {
|
|
get: function get() {
|
|
return this.isGroup ? this.bvGroup.localChecked : this.localChecked;
|
|
},
|
|
set: function set(val) {
|
|
if (this.isGroup) {
|
|
this.bvGroup.localChecked = val;
|
|
} else {
|
|
this.localChecked = val;
|
|
}
|
|
}
|
|
},
|
|
isGroup: function isGroup() {
|
|
// Is this check/radio a child of check-group or radio-group?
|
|
return Boolean(this.bvGroup);
|
|
},
|
|
isBtnMode: function isBtnMode() {
|
|
// Support button style in single input mode
|
|
return this.isGroup ? this.bvGroup.buttons : this.button;
|
|
},
|
|
isPlain: function isPlain() {
|
|
return this.isBtnMode ? false : this.isGroup ? this.bvGroup.plain : this.plain;
|
|
},
|
|
isCustom: function isCustom() {
|
|
return this.isBtnMode ? false : !this.isPlain;
|
|
},
|
|
isSwitch: function isSwitch() {
|
|
// Custom switch styling (checkboxes only)
|
|
return this.isBtnMode || this.isRadio || this.isPlain ? false : this.isGroup ? this.bvGroup.switches : this.switch;
|
|
},
|
|
isInline: function isInline() {
|
|
return this.isGroup ? this.bvGroup.inline : this.inline;
|
|
},
|
|
isDisabled: function isDisabled() {
|
|
// Child can be disabled while parent isn't, but is always disabled if group is
|
|
return this.isGroup ? this.bvGroup.disabled || this.disabled : this.disabled;
|
|
},
|
|
isRequired: function isRequired() {
|
|
// Required only works when a name is provided for the input(s)
|
|
// Child can only be required when parent is
|
|
// Groups will always have a name (either user supplied or auto generated)
|
|
return this.getName && (this.isGroup ? this.bvGroup.required : this.required);
|
|
},
|
|
getName: function getName() {
|
|
// Group name preferred over local name
|
|
return (this.isGroup ? this.bvGroup.groupName : this.name) || null;
|
|
},
|
|
getForm: function getForm() {
|
|
return (this.isGroup ? this.bvGroup.form : this.form) || null;
|
|
},
|
|
getSize: function getSize() {
|
|
return (this.isGroup ? this.bvGroup.size : this.size) || '';
|
|
},
|
|
getState: function getState() {
|
|
return this.isGroup ? this.bvGroup.computedState : this.computedState;
|
|
},
|
|
getButtonVariant: function getButtonVariant() {
|
|
// Local variant preferred over group variant
|
|
if (this.buttonVariant) {
|
|
return this.buttonVariant;
|
|
} else if (this.isGroup && this.bvGroup.buttonVariant) {
|
|
return this.bvGroup.buttonVariant;
|
|
} // default variant
|
|
|
|
|
|
return 'secondary';
|
|
},
|
|
buttonClasses: function buttonClasses() {
|
|
var _ref;
|
|
|
|
// Same for radio & check
|
|
return ['btn', "btn-".concat(this.getButtonVariant), (_ref = {}, _defineProperty(_ref, "btn-".concat(this.getSize), this.getSize), _defineProperty(_ref, "disabled", this.isDisabled), _defineProperty(_ref, "active", this.isChecked), _defineProperty(_ref, "focus", this.hasFocus), _ref)];
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
id: this.safeId(),
|
|
type: this.isRadio ? 'radio' : 'checkbox',
|
|
name: this.getName,
|
|
form: this.getForm,
|
|
disabled: this.isDisabled,
|
|
required: this.isRequired,
|
|
'aria-required': this.isRequired || null,
|
|
'aria-label': this.ariaLabel || null,
|
|
'aria-labelledby': this.ariaLabelledby || null
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
checked: function checked(newValue) {
|
|
if (!looseEqual(newValue, this.computedLocalChecked)) {
|
|
this.computedLocalChecked = newValue;
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleFocus: function handleFocus(evt) {
|
|
// When in buttons mode, we need to add 'focus' class to label when input focused
|
|
// As it is the hidden input which has actual focus
|
|
if (evt.target) {
|
|
if (evt.type === 'focus') {
|
|
this.hasFocus = true;
|
|
} else if (evt.type === 'blur') {
|
|
this.hasFocus = false;
|
|
}
|
|
}
|
|
},
|
|
// Convenience methods for focusing the input
|
|
focus: function focus() {
|
|
if (!this.isDisabled) {
|
|
attemptFocus(this.$refs.input);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.isDisabled) {
|
|
attemptBlur(this.$refs.input);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var defaultSlot = this.normalizeSlot(); // Generate the input element
|
|
|
|
var on = {
|
|
change: this.handleChange
|
|
};
|
|
|
|
if (this.isBtnMode) {
|
|
// Handlers for focus styling when in button mode
|
|
on.focus = on.blur = this.handleFocus;
|
|
}
|
|
|
|
var input = h('input', {
|
|
ref: 'input',
|
|
key: 'input',
|
|
on: on,
|
|
class: {
|
|
'form-check-input': this.isPlain,
|
|
'custom-control-input': this.isCustom,
|
|
'is-valid': this.getState === true && !this.isBtnMode,
|
|
'is-invalid': this.getState === false && !this.isBtnMode,
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/2911
|
|
'position-static': this.isPlain && !defaultSlot
|
|
},
|
|
directives: [{
|
|
name: 'model',
|
|
rawName: 'v-model',
|
|
value: this.computedLocalChecked,
|
|
expression: 'computedLocalChecked'
|
|
}],
|
|
attrs: this.computedAttrs,
|
|
domProps: {
|
|
value: this.value,
|
|
checked: this.isChecked
|
|
}
|
|
});
|
|
|
|
if (this.isBtnMode) {
|
|
// Button mode
|
|
var button = h('label', {
|
|
class: this.buttonClasses
|
|
}, [input, defaultSlot]);
|
|
|
|
if (!this.isGroup) {
|
|
// Standalone button mode, so wrap in 'btn-group-toggle'
|
|
// and flag it as inline-block to mimic regular buttons
|
|
button = h('div', {
|
|
class: ['btn-group-toggle', 'd-inline-block']
|
|
}, [button]);
|
|
}
|
|
|
|
return button;
|
|
} else {
|
|
// Not button mode
|
|
var label = h(); // If no label content in plain mode we dont render the label
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/2911
|
|
|
|
if (!(this.isPlain && !defaultSlot)) {
|
|
label = h('label', {
|
|
class: {
|
|
'form-check-label': this.isPlain,
|
|
'custom-control-label': this.isCustom
|
|
},
|
|
attrs: {
|
|
for: this.safeId()
|
|
}
|
|
}, defaultSlot);
|
|
} // Wrap it in a div
|
|
|
|
|
|
return h('div', {
|
|
class: _defineProperty({
|
|
'form-check': this.isPlain,
|
|
'form-check-inline': this.isPlain && this.isInline,
|
|
'custom-control': this.isCustom,
|
|
'custom-control-inline': this.isCustom && this.isInline,
|
|
'custom-checkbox': this.isCustom && this.isCheck && !this.isSwitch,
|
|
'custom-switch': this.isSwitch,
|
|
'custom-radio': this.isCustom && this.isRadio
|
|
}, "b-custom-control-".concat(this.getSize), Boolean(this.getSize && !this.isBtnMode))
|
|
}, [input, label]);
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$D = makePropsConfigurable({
|
|
size: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, 'formControls'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formSizeMixin = {
|
|
props: props$D,
|
|
computed: {
|
|
sizeFormClass: function sizeFormClass() {
|
|
return [this.size ? "form-control-".concat(this.size) : null];
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Form control contextual state class computation
|
|
*
|
|
* Returned class is either 'is-valid' or 'is-invalid' based on the 'state' prop
|
|
* state can be one of five values:
|
|
* - true for is-valid
|
|
* - false for is-invalid
|
|
* - null for no contextual state
|
|
*/
|
|
|
|
var props$E = makePropsConfigurable({
|
|
state: {
|
|
// Tri-state prop: true, false, null (or undefined)
|
|
type: Boolean,
|
|
default: null
|
|
}
|
|
}, 'formState'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formStateMixin = {
|
|
props: props$E,
|
|
computed: {
|
|
computedState: function computedState() {
|
|
// If not a boolean, ensure that value is null
|
|
return isBoolean(this.state) ? this.state : null;
|
|
},
|
|
stateClass: function stateClass() {
|
|
var state = this.computedState;
|
|
return state === true ? 'is-valid' : state === false ? 'is-invalid' : null;
|
|
}
|
|
}
|
|
};
|
|
|
|
var BFormCheckbox = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_CHECKBOX,
|
|
mixins: [formRadioCheckMixin, // Includes shared render function
|
|
idMixin, formControlMixin, formSizeMixin, formStateMixin],
|
|
inject: {
|
|
bvGroup: {
|
|
from: 'bvCheckGroup',
|
|
default: false
|
|
}
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$C), props$D), props$E), {}, {
|
|
value: {
|
|
// type: [String, Number, Boolean, Object],
|
|
default: true
|
|
},
|
|
uncheckedValue: {
|
|
// type: [String, Number, Boolean, Object],
|
|
// Not applicable in multi-check mode
|
|
default: false
|
|
},
|
|
indeterminate: {
|
|
// Not applicable in multi-check mode
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
switch: {
|
|
// Custom switch styling
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
checked: {
|
|
// v-model (Array when multiple checkboxes have same name)
|
|
// type: [String, Number, Boolean, Object, Array],
|
|
default: null
|
|
}
|
|
}), NAME_FORM_CHECKBOX),
|
|
computed: {
|
|
isChecked: function isChecked() {
|
|
var value = this.value,
|
|
checked = this.computedLocalChecked;
|
|
return isArray(checked) ? looseIndexOf(checked, value) > -1 : looseEqual(checked, value);
|
|
},
|
|
isRadio: function isRadio() {
|
|
return false;
|
|
},
|
|
isCheck: function isCheck() {
|
|
return true;
|
|
}
|
|
},
|
|
watch: {
|
|
computedLocalChecked: function computedLocalChecked(newValue, oldValue) {
|
|
if (!looseEqual(newValue, oldValue)) {
|
|
this.$emit('input', newValue);
|
|
var $input = this.$refs.input;
|
|
|
|
if ($input) {
|
|
this.$emit('update:indeterminate', $input.indeterminate);
|
|
}
|
|
}
|
|
},
|
|
indeterminate: function indeterminate(newVal) {
|
|
this.setIndeterminate(newVal);
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
// Set initial indeterminate state
|
|
this.setIndeterminate(this.indeterminate);
|
|
},
|
|
methods: {
|
|
handleChange: function handleChange(_ref) {
|
|
var _this = this;
|
|
|
|
var _ref$target = _ref.target,
|
|
checked = _ref$target.checked,
|
|
indeterminate = _ref$target.indeterminate;
|
|
var value = this.value,
|
|
uncheckedValue = this.uncheckedValue; // Update `computedLocalChecked`
|
|
|
|
var localChecked = this.computedLocalChecked;
|
|
|
|
if (isArray(localChecked)) {
|
|
var index = looseIndexOf(localChecked, value);
|
|
|
|
if (checked && index < 0) {
|
|
// Add value to array
|
|
localChecked = localChecked.concat(value);
|
|
} else if (!checked && index > -1) {
|
|
// Remove value from array
|
|
localChecked = localChecked.slice(0, index).concat(localChecked.slice(index + 1));
|
|
}
|
|
} else {
|
|
localChecked = checked ? value : uncheckedValue;
|
|
}
|
|
|
|
this.computedLocalChecked = localChecked; // Fire events in a `$nextTick()` to ensure the `v-model` is updated
|
|
|
|
this.$nextTick(function () {
|
|
// Change is only emitted on user interaction
|
|
_this.$emit('change', localChecked); // If this is a child of `<form-checkbox-group>`,
|
|
// we emit a change event on it as well
|
|
|
|
|
|
if (_this.isGroup) {
|
|
_this.bvGroup.$emit('change', localChecked);
|
|
}
|
|
|
|
_this.$emit('update:indeterminate', indeterminate);
|
|
});
|
|
},
|
|
setIndeterminate: function setIndeterminate(state) {
|
|
// Indeterminate only supported in single checkbox mode
|
|
if (isArray(this.computedLocalChecked)) {
|
|
state = false;
|
|
}
|
|
|
|
var $input = this.$refs.input;
|
|
|
|
if ($input) {
|
|
$input.indeterminate = state; // Emit update event to prop
|
|
|
|
this.$emit('update:indeterminate', state);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var BFormRadio = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_RADIO,
|
|
mixins: [idMixin, formRadioCheckMixin, // Includes shared render function
|
|
formControlMixin, formSizeMixin, formStateMixin],
|
|
inject: {
|
|
bvGroup: {
|
|
from: 'bvRadioGroup',
|
|
default: false
|
|
}
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$C), props$D), props$E), {}, {
|
|
checked: {
|
|
// v-model
|
|
// type: [String, Number, Boolean, Object],
|
|
default: null
|
|
}
|
|
}), NAME_FORM_RADIO),
|
|
computed: {
|
|
isChecked: function isChecked() {
|
|
return looseEqual(this.value, this.computedLocalChecked);
|
|
},
|
|
isRadio: function isRadio() {
|
|
return true;
|
|
},
|
|
isCheck: function isCheck() {
|
|
return false;
|
|
}
|
|
},
|
|
watch: {
|
|
computedLocalChecked: function computedLocalChecked(newValue, oldValue) {
|
|
if (!looseEqual(newValue, oldValue)) {
|
|
this.$emit('input', newValue);
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleChange: function handleChange(_ref) {
|
|
var _this = this;
|
|
|
|
var checked = _ref.target.checked;
|
|
var value = this.value;
|
|
var localChecked = checked ? value : null;
|
|
this.computedLocalChecked = value; // Fire events in a `$nextTick()` to ensure the `v-model` is updated
|
|
|
|
this.$nextTick(function () {
|
|
// Change is only emitted on user interaction
|
|
_this.$emit('change', localChecked); // If this is a child of `<form-radio-group>`,
|
|
// we emit a change event on it as well
|
|
|
|
|
|
if (_this.isGroup) {
|
|
_this.bvGroup.$emit('change', localChecked);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
var props$F = makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$B), {}, {
|
|
validated: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaInvalid: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
stacked: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttons: {
|
|
// Render as button style
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonVariant: {
|
|
// Only applicable when rendered with button style
|
|
type: String // default: null
|
|
|
|
}
|
|
}), 'formRadioCheckGroups'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formRadioCheckGroupMixin = {
|
|
mixins: [formCustomMixin, normalizeSlotMixin],
|
|
model: {
|
|
prop: 'checked',
|
|
event: 'input'
|
|
},
|
|
props: props$F,
|
|
computed: {
|
|
inline: function inline() {
|
|
return !this.stacked;
|
|
},
|
|
groupName: function groupName() {
|
|
// Checks/Radios tied to the same model must have the same name,
|
|
// especially for ARIA accessibility.
|
|
return this.name || this.safeId();
|
|
},
|
|
groupClasses: function groupClasses() {
|
|
if (this.buttons) {
|
|
return ['btn-group-toggle', this.inline ? 'btn-group' : 'btn-group-vertical', this.size ? "btn-group-".concat(this.size) : '', this.validated ? "was-validated" : ''];
|
|
}
|
|
|
|
return [this.validated ? "was-validated" : ''];
|
|
},
|
|
computedAriaInvalid: function computedAriaInvalid() {
|
|
var ariaInvalid = this.ariaInvalid;
|
|
|
|
if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') {
|
|
return 'true';
|
|
}
|
|
|
|
return this.computedState === false ? 'true' : null;
|
|
}
|
|
},
|
|
watch: {
|
|
checked: function checked(newVal) {
|
|
if (!looseEqual(newVal, this.localChecked)) {
|
|
this.localChecked = newVal;
|
|
}
|
|
},
|
|
localChecked: function localChecked(newValue, oldValue) {
|
|
if (!looseEqual(newValue, oldValue)) {
|
|
this.$emit('input', newValue);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this = this;
|
|
|
|
var $inputs = this.formOptions.map(function (option, index) {
|
|
var key = "BV_option_".concat(index);
|
|
return h(_this.isRadioGroup ? BFormRadio : BFormCheckbox, {
|
|
props: {
|
|
id: _this.safeId(key),
|
|
value: option.value,
|
|
// Individual radios or checks can be disabled in a group
|
|
disabled: option.disabled || false // We don't need to include these, since the input's will know they are inside here
|
|
// name: this.groupName,
|
|
// form: this.form || null,
|
|
// required: Boolean(this.name && this.required)
|
|
|
|
},
|
|
key: key
|
|
}, [h('span', {
|
|
domProps: htmlOrText(option.html, option.text)
|
|
})]);
|
|
});
|
|
return h('div', {
|
|
class: [this.groupClasses, 'bv-no-focus-ring'],
|
|
attrs: {
|
|
id: this.safeId(),
|
|
role: this.isRadioGroup ? 'radiogroup' : 'group',
|
|
// Add `tabindex="-1"` to allow group to be focused if needed by screen readers
|
|
tabindex: '-1',
|
|
'aria-required': this.required ? 'true' : null,
|
|
'aria-invalid': this.computedAriaInvalid
|
|
}
|
|
}, [this.normalizeSlot(SLOT_NAME_FIRST), $inputs, this.normalizeSlot()]);
|
|
}
|
|
};
|
|
|
|
var props$G = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$v), props$F), props$D), props$E), {}, {
|
|
switches: {
|
|
// Custom switch styling
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
checked: {
|
|
type: Array,
|
|
default: null
|
|
}
|
|
}), NAME_FORM_CHECKBOX_GROUP); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BFormCheckboxGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_CHECKBOX_GROUP,
|
|
mixins: [idMixin, formControlMixin, formRadioCheckGroupMixin, // Includes render function
|
|
formOptionsMixin, formSizeMixin, formStateMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvCheckGroup: this
|
|
};
|
|
},
|
|
props: props$G,
|
|
data: function data() {
|
|
return {
|
|
localChecked: this.checked || []
|
|
};
|
|
},
|
|
computed: {
|
|
isRadioGroup: function isRadioGroup() {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
var FormCheckboxPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormCheckbox: BFormCheckbox,
|
|
BCheckbox: BFormCheckbox,
|
|
BCheck: BFormCheckbox,
|
|
BFormCheckboxGroup: BFormCheckboxGroup,
|
|
BCheckboxGroup: BFormCheckboxGroup,
|
|
BCheckGroup: BFormCheckboxGroup
|
|
}
|
|
});
|
|
|
|
// v-b-hover directive
|
|
|
|
var PROP = '__BV_hover_handler__';
|
|
var MOUSEENTER = 'mouseenter';
|
|
var MOUSELEAVE = 'mouseleave'; // --- Utility methods ---
|
|
|
|
var createListener = function createListener(handler) {
|
|
var listener = function listener(evt) {
|
|
handler(evt.type === MOUSEENTER, evt);
|
|
};
|
|
|
|
listener.fn = handler;
|
|
return listener;
|
|
};
|
|
|
|
var updateListeners = function updateListeners(on, el, listener) {
|
|
eventOnOff(on, el, MOUSEENTER, listener, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOnOff(on, el, MOUSELEAVE, listener, EVENT_OPTIONS_NO_CAPTURE);
|
|
}; // --- Directive bind/unbind/update handler ---
|
|
|
|
|
|
var directive = function directive(el, _ref) {
|
|
var _ref$value = _ref.value,
|
|
handler = _ref$value === void 0 ? null : _ref$value;
|
|
|
|
if (isBrowser) {
|
|
var listener = el[PROP];
|
|
var hasListener = isFunction(listener);
|
|
var handlerChanged = !(hasListener && listener.fn === handler);
|
|
|
|
if (hasListener && handlerChanged) {
|
|
updateListeners(false, el, listener);
|
|
delete el[PROP];
|
|
}
|
|
|
|
if (isFunction(handler) && handlerChanged) {
|
|
el[PROP] = createListener(handler);
|
|
updateListeners(true, el, el[PROP]);
|
|
}
|
|
}
|
|
}; // VBHover directive
|
|
|
|
|
|
var VBHover = {
|
|
bind: directive,
|
|
componentUpdated: directive,
|
|
unbind: function unbind(el) {
|
|
directive(el, {
|
|
value: null
|
|
});
|
|
}
|
|
};
|
|
|
|
var props$H = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, omit(props$A, ['autofocus'])), props$D), commonProps), props$E), {}, {
|
|
value: {
|
|
// This is the value placed on the hidden input
|
|
type: String,
|
|
default: ''
|
|
},
|
|
formattedValue: {
|
|
// This is the value shown in the label
|
|
// Defaults back to `value`
|
|
type: String // default: null
|
|
|
|
},
|
|
placeholder: {
|
|
// This is the value placed on the hidden input when no value selected
|
|
type: String // default: null
|
|
|
|
},
|
|
labelSelected: {
|
|
// Value placed in sr-only span inside label when value is present
|
|
type: String // default: null
|
|
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lang: {
|
|
type: String // default: null
|
|
|
|
},
|
|
rtl: {
|
|
// Tri-state prop: `true`, `false` or `null`
|
|
type: Boolean,
|
|
// We must explicitly default to `null` here otherwise
|
|
// Vue coerces `undefined` into Boolean `false`
|
|
default: null
|
|
},
|
|
buttonOnly: {
|
|
// When true, renders a btn-group wrapper and visually hides the label
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonVariant: {
|
|
// Applicable in button mode only
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
menuClass: {
|
|
// Extra classes to apply to the `dropdown-menu` div
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BVFormBtnLabelControl = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_BUTTON_LABEL_CONTROL,
|
|
directives: {
|
|
BHover: VBHover
|
|
},
|
|
mixins: [idMixin, formSizeMixin, formStateMixin, dropdownMixin, normalizeSlotMixin],
|
|
props: props$H,
|
|
data: function data() {
|
|
return {
|
|
isHovered: false,
|
|
hasFocus: false
|
|
};
|
|
},
|
|
computed: {
|
|
idButton: function idButton() {
|
|
return this.safeId();
|
|
},
|
|
idLabel: function idLabel() {
|
|
return this.safeId('_value_');
|
|
},
|
|
idMenu: function idMenu() {
|
|
return this.safeId('_dialog_');
|
|
},
|
|
idWrapper: function idWrapper() {
|
|
return this.safeId('_outer_');
|
|
},
|
|
computedDir: function computedDir() {
|
|
return this.rtl === true ? 'rtl' : this.rtl === false ? 'ltr' : null;
|
|
}
|
|
},
|
|
methods: {
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$refs.toggle);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$refs.toggle);
|
|
}
|
|
},
|
|
setFocus: function setFocus(evt) {
|
|
this.hasFocus = evt.type === 'focus';
|
|
},
|
|
handleHover: function handleHover(hovered) {
|
|
this.isHovered = hovered;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _class;
|
|
|
|
var idButton = this.idButton,
|
|
idLabel = this.idLabel,
|
|
idMenu = this.idMenu,
|
|
idWrapper = this.idWrapper,
|
|
disabled = this.disabled,
|
|
readonly = this.readonly,
|
|
required = this.required,
|
|
name = this.name,
|
|
state = this.state,
|
|
visible = this.visible,
|
|
size = this.size,
|
|
isHovered = this.isHovered,
|
|
hasFocus = this.hasFocus,
|
|
labelSelected = this.labelSelected,
|
|
buttonVariant = this.buttonVariant;
|
|
var value = toString$1(this.value) || '';
|
|
var buttonOnly = !!this.buttonOnly;
|
|
var invalid = state === false || required && !value;
|
|
var btnScope = {
|
|
isHovered: isHovered,
|
|
hasFocus: hasFocus,
|
|
state: state,
|
|
opened: visible
|
|
};
|
|
var $button = h('button', {
|
|
ref: 'toggle',
|
|
staticClass: 'btn',
|
|
class: (_class = {}, _defineProperty(_class, "btn-".concat(buttonVariant), buttonOnly), _defineProperty(_class, "btn-".concat(size), !!size), _defineProperty(_class, 'h-auto', !buttonOnly), _defineProperty(_class, 'dropdown-toggle', buttonOnly), _defineProperty(_class, 'dropdown-toggle-no-caret', buttonOnly), _class),
|
|
attrs: {
|
|
id: idButton,
|
|
type: 'button',
|
|
disabled: disabled,
|
|
'aria-haspopup': 'dialog',
|
|
'aria-expanded': visible ? 'true' : 'false',
|
|
'aria-invalid': invalid ? 'true' : null,
|
|
'aria-required': required ? 'true' : null
|
|
},
|
|
directives: [{
|
|
name: 'b-hover',
|
|
value: this.handleHover
|
|
}],
|
|
on: {
|
|
mousedown: this.onMousedown,
|
|
click: this.toggle,
|
|
keydown: this.toggle,
|
|
// Handle ENTER, SPACE and DOWN
|
|
'!focus': this.setFocus,
|
|
'!blur': this.setFocus
|
|
}
|
|
}, [this.hasNormalizedSlot(SLOT_NAME_BUTTON_CONTENT) ? this.normalizeSlot(SLOT_NAME_BUTTON_CONTENT, btnScope) :
|
|
/* istanbul ignore next */
|
|
h(BIconChevronDown, {
|
|
props: {
|
|
scale: 1.25
|
|
}
|
|
})]); // Hidden input
|
|
|
|
var $hidden = h();
|
|
|
|
if (name && !disabled) {
|
|
$hidden = h('input', {
|
|
attrs: {
|
|
type: 'hidden',
|
|
name: name || null,
|
|
form: this.form || null,
|
|
value: value
|
|
}
|
|
});
|
|
} // Dropdown content
|
|
|
|
|
|
var $menu = h('div', {
|
|
ref: 'menu',
|
|
staticClass: 'dropdown-menu',
|
|
class: [this.menuClass, {
|
|
show: visible,
|
|
'dropdown-menu-right': this.right
|
|
}],
|
|
attrs: {
|
|
id: idMenu,
|
|
role: 'dialog',
|
|
tabindex: '-1',
|
|
'aria-modal': 'false',
|
|
'aria-labelledby': idLabel
|
|
},
|
|
on: {
|
|
keydown: this.onKeydown // Handle ESC
|
|
|
|
}
|
|
}, [this.normalizeSlot(SLOT_NAME_DEFAULT, {
|
|
opened: visible
|
|
})]); // Value label
|
|
|
|
var $label = h('label', {
|
|
staticClass: 'form-control text-break text-wrap bg-transparent h-auto',
|
|
class: [{
|
|
// Hidden in button only mode
|
|
'sr-only': buttonOnly,
|
|
// Mute the text if showing the placeholder
|
|
'text-muted': !value
|
|
}, this.stateClass, this.sizeFormClass],
|
|
attrs: {
|
|
id: idLabel,
|
|
for: idButton,
|
|
'aria-invalid': invalid ? 'true' : null,
|
|
'aria-required': required ? 'true' : null
|
|
},
|
|
directives: [{
|
|
name: 'b-hover',
|
|
value: this.handleHover
|
|
}],
|
|
on: {
|
|
// Disable bubbling of the click event to
|
|
// prevent menu from closing and re-opening
|
|
'!click':
|
|
/* istanbul ignore next */
|
|
function click(evt) {
|
|
stopEvent(evt, {
|
|
preventDefault: false
|
|
});
|
|
}
|
|
}
|
|
}, [value ? this.formattedValue || value : this.placeholder || '', // Add the selected label for screen readers when a value is provided
|
|
value && labelSelected ? h('bdi', {
|
|
staticClass: 'sr-only'
|
|
}, labelSelected) : '']); // Return the custom form control wrapper
|
|
|
|
return h('div', {
|
|
staticClass: 'b-form-btn-label-control dropdown',
|
|
class: [this.directionClass, this.boundaryClass, [{
|
|
'btn-group': buttonOnly,
|
|
'form-control': !buttonOnly,
|
|
'd-flex': !buttonOnly,
|
|
'h-auto': !buttonOnly,
|
|
'align-items-stretch': !buttonOnly,
|
|
focus: hasFocus && !buttonOnly,
|
|
show: visible,
|
|
'is-valid': state === true,
|
|
'is-invalid': state === false
|
|
}, buttonOnly ? null : this.sizeFormClass]],
|
|
attrs: {
|
|
id: idWrapper,
|
|
role: buttonOnly ? null : 'group',
|
|
lang: this.lang || null,
|
|
dir: this.computedDir,
|
|
'aria-disabled': disabled,
|
|
'aria-readonly': readonly && !disabled,
|
|
'aria-labelledby': idLabel,
|
|
'aria-invalid': state === false || required && !value ? 'true' : null,
|
|
'aria-required': required ? 'true' : null
|
|
}
|
|
}, [$button, $hidden, $menu, $label]);
|
|
}
|
|
});
|
|
|
|
// @vue/component
|
|
|
|
var BFormDatepicker = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_DATEPICKER,
|
|
// The mixins order determines the order of appearance in the props reference section
|
|
mixins: [idMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2({}, props$7), omit(props$H, ['id', 'value', 'formattedValue', 'rtl', 'lang'])), {}, {
|
|
resetValue: {
|
|
type: [String, Date] // default: null
|
|
|
|
},
|
|
noCloseOnSelect: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonOnly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonVariant: {
|
|
// Applicable in button only mode
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
calendarWidth: {
|
|
// Width of the calendar dropdown
|
|
type: String,
|
|
default: '270px'
|
|
},
|
|
todayButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelTodayButton: {
|
|
type: String,
|
|
default: 'Select today'
|
|
},
|
|
todayButtonVariant: {
|
|
type: String,
|
|
default: 'outline-primary'
|
|
},
|
|
resetButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelResetButton: {
|
|
type: String,
|
|
default: 'Reset'
|
|
},
|
|
resetButtonVariant: {
|
|
type: String,
|
|
default: 'outline-danger'
|
|
},
|
|
closeButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelCloseButton: {
|
|
type: String,
|
|
default: 'Close'
|
|
},
|
|
closeButtonVariant: {
|
|
type: String,
|
|
default: 'outline-secondary'
|
|
},
|
|
// Dark mode
|
|
dark: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_FORM_DATEPICKER),
|
|
data: function data() {
|
|
return {
|
|
// We always use `YYYY-MM-DD` value internally
|
|
localYMD: formatYMD(this.value) || '',
|
|
// If the popup is open
|
|
isVisible: false,
|
|
// Context data from BCalendar
|
|
localLocale: null,
|
|
isRTL: false,
|
|
formattedValue: '',
|
|
activeYMD: ''
|
|
};
|
|
},
|
|
computed: {
|
|
calendarYM: function calendarYM() {
|
|
// Returns the calendar year/month
|
|
// Returns the `YYYY-MM` portion of the active calendar date
|
|
return this.activeYMD.slice(0, -3);
|
|
},
|
|
computedLang: function computedLang() {
|
|
return (this.localLocale || '').replace(/-u-.*$/i, '') || null;
|
|
},
|
|
computedResetValue: function computedResetValue() {
|
|
return formatYMD(constrainDate(this.resetValue)) || '';
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal) {
|
|
this.localYMD = formatYMD(newVal) || '';
|
|
},
|
|
localYMD: function localYMD(newVal) {
|
|
// We only update the v-model when the datepicker is open
|
|
if (this.isVisible) {
|
|
this.$emit('input', this.valueAsDate ? parseYMD(newVal) || null : newVal || '');
|
|
}
|
|
},
|
|
calendarYM: function calendarYM(newVal, oldVal) {
|
|
// Displayed calendar month has changed
|
|
// So possibly the calendar height has changed...
|
|
// We need to update popper computed position
|
|
if (newVal !== oldVal && oldVal) {
|
|
try {
|
|
this.$refs.control.updatePopper();
|
|
} catch (_unused) {}
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// Public methods
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$refs.control);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$refs.control);
|
|
}
|
|
},
|
|
// Private methods
|
|
setAndClose: function setAndClose(ymd) {
|
|
var _this = this;
|
|
|
|
this.localYMD = ymd; // Close calendar popup, unless `noCloseOnSelect`
|
|
|
|
if (!this.noCloseOnSelect) {
|
|
this.$nextTick(function () {
|
|
_this.$refs.control.hide(true);
|
|
});
|
|
}
|
|
},
|
|
onSelected: function onSelected(ymd) {
|
|
var _this2 = this;
|
|
|
|
this.$nextTick(function () {
|
|
_this2.setAndClose(ymd);
|
|
});
|
|
},
|
|
onInput: function onInput(ymd) {
|
|
if (this.localYMD !== ymd) {
|
|
this.localYMD = ymd;
|
|
}
|
|
},
|
|
onContext: function onContext(ctx) {
|
|
var activeYMD = ctx.activeYMD,
|
|
isRTL = ctx.isRTL,
|
|
locale = ctx.locale,
|
|
selectedYMD = ctx.selectedYMD,
|
|
selectedFormatted = ctx.selectedFormatted;
|
|
this.isRTL = isRTL;
|
|
this.localLocale = locale;
|
|
this.formattedValue = selectedFormatted;
|
|
this.localYMD = selectedYMD;
|
|
this.activeYMD = activeYMD; // Re-emit the context event
|
|
|
|
this.$emit('context', ctx);
|
|
},
|
|
onTodayButton: function onTodayButton() {
|
|
// Set to today (or min/max if today is out of range)
|
|
this.setAndClose(formatYMD(constrainDate(createDate(), this.min, this.max)));
|
|
},
|
|
onResetButton: function onResetButton() {
|
|
this.setAndClose(this.computedResetValue);
|
|
},
|
|
onCloseButton: function onCloseButton() {
|
|
this.$refs.control.hide(true);
|
|
},
|
|
// Menu handlers
|
|
onShow: function onShow() {
|
|
this.isVisible = true;
|
|
},
|
|
onShown: function onShown() {
|
|
var _this3 = this;
|
|
|
|
this.$nextTick(function () {
|
|
attemptFocus(_this3.$refs.calendar);
|
|
|
|
_this3.$emit('shown');
|
|
});
|
|
},
|
|
onHidden: function onHidden() {
|
|
this.isVisible = false;
|
|
this.$emit('hidden');
|
|
},
|
|
// Render helpers
|
|
defaultButtonFn: function defaultButtonFn(_ref) {
|
|
var isHovered = _ref.isHovered,
|
|
hasFocus = _ref.hasFocus;
|
|
return this.$createElement(isHovered || hasFocus ? BIconCalendarFill : BIconCalendar, {
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var localYMD = this.localYMD,
|
|
disabled = this.disabled,
|
|
readonly = this.readonly,
|
|
dark = this.dark,
|
|
$props = this.$props,
|
|
$scopedSlots = this.$scopedSlots;
|
|
var placeholder = isUndefinedOrNull(this.placeholder) ? this.labelNoDateSelected : this.placeholder; // Optional footer buttons
|
|
|
|
var $footer = [];
|
|
|
|
if (this.todayButton) {
|
|
var label = this.labelTodayButton;
|
|
$footer.push(h(BButton, {
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled || readonly,
|
|
variant: this.todayButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': label || null
|
|
},
|
|
on: {
|
|
click: this.onTodayButton
|
|
}
|
|
}, label));
|
|
}
|
|
|
|
if (this.resetButton) {
|
|
var _label = this.labelResetButton;
|
|
$footer.push(h(BButton, {
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled || readonly,
|
|
variant: this.resetButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': _label || null
|
|
},
|
|
on: {
|
|
click: this.onResetButton
|
|
}
|
|
}, _label));
|
|
}
|
|
|
|
if (this.closeButton) {
|
|
var _label2 = this.labelCloseButton;
|
|
$footer.push(h(BButton, {
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled,
|
|
variant: this.closeButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': _label2 || null
|
|
},
|
|
on: {
|
|
click: this.onCloseButton
|
|
}
|
|
}, _label2));
|
|
}
|
|
|
|
if ($footer.length > 0) {
|
|
$footer = [h('div', {
|
|
staticClass: 'b-form-date-controls d-flex flex-wrap',
|
|
class: {
|
|
'justify-content-between': $footer.length > 1,
|
|
'justify-content-end': $footer.length < 2
|
|
}
|
|
}, $footer)];
|
|
}
|
|
|
|
var $calendar = h(BCalendar, {
|
|
key: 'calendar',
|
|
ref: 'calendar',
|
|
staticClass: 'b-form-date-calendar w-100',
|
|
props: _objectSpread2(_objectSpread2({}, pluckProps(props$7, $props)), {}, {
|
|
value: localYMD,
|
|
hidden: !this.isVisible
|
|
}),
|
|
on: {
|
|
selected: this.onSelected,
|
|
input: this.onInput,
|
|
context: this.onContext
|
|
},
|
|
scopedSlots: pick($scopedSlots, ['nav-prev-decade', 'nav-prev-year', 'nav-prev-month', 'nav-this-month', 'nav-next-month', 'nav-next-year', 'nav-next-decade'])
|
|
}, $footer);
|
|
return h(BVFormBtnLabelControl, {
|
|
ref: 'control',
|
|
staticClass: 'b-form-datepicker',
|
|
props: _objectSpread2(_objectSpread2({}, pluckProps(props$H, $props)), {}, {
|
|
id: this.safeId(),
|
|
value: localYMD,
|
|
formattedValue: localYMD ? this.formattedValue : '',
|
|
placeholder: placeholder,
|
|
rtl: this.isRTL,
|
|
lang: this.computedLang,
|
|
menuClass: [{
|
|
'bg-dark': !!dark,
|
|
'text-light': !!dark
|
|
}, this.menuClass]
|
|
}),
|
|
on: {
|
|
show: this.onShow,
|
|
shown: this.onShown,
|
|
hidden: this.onHidden
|
|
},
|
|
scopedSlots: {
|
|
'button-content': $scopedSlots['button-content'] || this.defaultButtonFn
|
|
}
|
|
}, [$calendar]);
|
|
}
|
|
});
|
|
|
|
var FormDatepickerPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormDatepicker: BFormDatepicker,
|
|
BDatepicker: BFormDatepicker
|
|
}
|
|
});
|
|
|
|
var VALUE_EMPTY_DEPRECATED_MSG = 'Setting "value"/"v-model" to an empty string for reset is deprecated. Set to "null" instead.'; // --- Helper methods ---
|
|
|
|
var isValidValue = function isValidValue(value) {
|
|
return isFile(value) || isArray(value) && value.every(function (v) {
|
|
return isValidValue(v);
|
|
});
|
|
}; // Helper method to "safely" get the entry from a data-transfer item
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
|
|
|
|
var getDataTransferItemEntry = function getDataTransferItemEntry(item) {
|
|
return isFunction(item.getAsEntry) ? item.getAsEntry() : isFunction(item.webkitGetAsEntry) ? item.webkitGetAsEntry() : null;
|
|
}; // Drop handler function to get all files
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
|
|
|
|
var getAllFileEntries = function getAllFileEntries(dataTransferItemList) {
|
|
var traverseDirectories = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
return Promise.all(from(dataTransferItemList).filter(function (item) {
|
|
return item.kind === 'file';
|
|
}).map(function (item) {
|
|
var entry = getDataTransferItemEntry(item);
|
|
|
|
if (entry) {
|
|
if (entry.isDirectory && traverseDirectories) {
|
|
return getAllFileEntriesInDirectory(entry.createReader(), "".concat(entry.name, "/"));
|
|
} else if (entry.isFile) {
|
|
return new Promise(function (resolve) {
|
|
entry.file(function (file) {
|
|
file.$path = '';
|
|
resolve(file);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}).filter(identity));
|
|
}; // Get all the file entries (recursive) in a directory
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
|
|
|
|
var getAllFileEntriesInDirectory = function getAllFileEntriesInDirectory(directoryReader) {
|
|
var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
|
return new Promise(function (resolve) {
|
|
var entryPromises = [];
|
|
|
|
var readDirectoryEntries = function readDirectoryEntries() {
|
|
directoryReader.readEntries(function (entries) {
|
|
if (entries.length === 0) {
|
|
resolve(Promise.all(entryPromises).then(function (entries) {
|
|
return flatten(entries);
|
|
}));
|
|
} else {
|
|
entryPromises.push(Promise.all(entries.map(function (entry) {
|
|
if (entry) {
|
|
if (entry.isDirectory) {
|
|
return getAllFileEntriesInDirectory(entry.createReader(), "".concat(path).concat(entry.name, "/"));
|
|
} else if (entry.isFile) {
|
|
return new Promise(function (resolve) {
|
|
entry.file(function (file) {
|
|
file.$path = "".concat(path).concat(file.name);
|
|
resolve(file);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}).filter(identity)));
|
|
readDirectoryEntries();
|
|
}
|
|
});
|
|
};
|
|
|
|
readDirectoryEntries();
|
|
});
|
|
}; // @vue/component
|
|
|
|
|
|
var BFormFile = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_FILE,
|
|
mixins: [attrsMixin, idMixin, formControlMixin, formStateMixin, formCustomMixin, normalizeSlotMixin],
|
|
inheritAttrs: false,
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$B), props$E), props$D), {}, {
|
|
value: {
|
|
type: [File, Array],
|
|
default: null,
|
|
validator: function validator(value) {
|
|
/* istanbul ignore next */
|
|
if (value === '') {
|
|
warn(VALUE_EMPTY_DEPRECATED_MSG, NAME_FORM_FILE);
|
|
return true;
|
|
}
|
|
|
|
return isUndefinedOrNull(value) || isValidValue(value);
|
|
}
|
|
},
|
|
accept: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// Instruct input to capture from camera
|
|
capture: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: 'No file chosen'
|
|
},
|
|
browseText: {
|
|
type: String,
|
|
default: 'Browse'
|
|
},
|
|
dropPlaceholder: {
|
|
type: String,
|
|
default: 'Drop files here'
|
|
},
|
|
noDropPlaceholder: {
|
|
type: String,
|
|
default: 'Not allowed'
|
|
},
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
directory: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// TODO:
|
|
// Should we deprecate this and only support flat file structures?
|
|
// Nested file structures are only supported when files are dropped
|
|
// A Chromium "bug" prevents `webkitEntries` from being populated
|
|
// on the file input's `change` event and is marked as "WontFix"
|
|
// Mozilla implemented the behavior the same way as Chromium
|
|
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=138987
|
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1326031
|
|
noTraverse: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noDrop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fileNameFormatter: {
|
|
type: Function // default: null
|
|
|
|
}
|
|
}), NAME_FORM_FILE),
|
|
data: function data() {
|
|
return {
|
|
files: [],
|
|
dragging: false,
|
|
// IE 11 doesn't respect setting `evt.dataTransfer.dropEffect`,
|
|
// so we handle it ourselves as well
|
|
// https://stackoverflow.com/a/46915971/2744776
|
|
dropAllowed: !this.noDrop,
|
|
hasFocus: false
|
|
};
|
|
},
|
|
computed: {
|
|
// Convert `accept` to an array of `[{ RegExpr, isMime }, ...]`
|
|
computedAccept: function computedAccept() {
|
|
var accept = this.accept;
|
|
accept = (accept || '').trim().split(/[,\s]+/).filter(Boolean); // Allow any file type/extension
|
|
|
|
if (accept.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return accept.map(function (extOrType) {
|
|
var prop = 'name';
|
|
var startMatch = '^';
|
|
var endMatch = '$';
|
|
|
|
if (RX_EXTENSION.test(extOrType)) {
|
|
// File extension /\.ext$/
|
|
startMatch = '';
|
|
} else {
|
|
// MIME type /^mime\/.+$/ or /^mime\/type$/
|
|
prop = 'type';
|
|
|
|
if (RX_STAR.test(extOrType)) {
|
|
endMatch = '.+$'; // Remove trailing `*`
|
|
|
|
extOrType = extOrType.slice(0, -1);
|
|
}
|
|
} // Escape all RegExp special chars
|
|
|
|
|
|
extOrType = escapeRegExp(extOrType);
|
|
var rx = new RegExp("".concat(startMatch).concat(extOrType).concat(endMatch));
|
|
return {
|
|
rx: rx,
|
|
prop: prop
|
|
};
|
|
});
|
|
},
|
|
computedCapture: function computedCapture() {
|
|
var capture = this.capture;
|
|
return capture === true || capture === '' ? true : capture || null;
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
var name = this.name,
|
|
disabled = this.disabled,
|
|
required = this.required,
|
|
form = this.form,
|
|
computedCapture = this.computedCapture,
|
|
accept = this.accept,
|
|
multiple = this.multiple,
|
|
directory = this.directory;
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
type: 'file',
|
|
id: this.safeId(),
|
|
name: name,
|
|
disabled: disabled,
|
|
required: required,
|
|
form: form || null,
|
|
capture: computedCapture,
|
|
accept: accept || null,
|
|
multiple: multiple,
|
|
directory: directory,
|
|
webkitdirectory: directory,
|
|
'aria-required': required ? 'true' : null
|
|
});
|
|
},
|
|
computedFileNameFormatter: function computedFileNameFormatter() {
|
|
var fileNameFormatter = this.fileNameFormatter;
|
|
var result = null;
|
|
|
|
try {
|
|
result = fileNameFormatter();
|
|
} catch (_unused) {}
|
|
|
|
return isUndefined(result) ? this.defaultFileNameFormatter : fileNameFormatter;
|
|
},
|
|
clonedFiles: function clonedFiles() {
|
|
return cloneDeep(this.files);
|
|
},
|
|
flattenedFiles: function flattenedFiles() {
|
|
return flattenDeep(this.files);
|
|
},
|
|
fileNames: function fileNames() {
|
|
return this.flattenedFiles.map(function (file) {
|
|
return file.name;
|
|
});
|
|
},
|
|
labelContent: function labelContent() {
|
|
var h = this.$createElement; // Draging active
|
|
|
|
/* istanbul ignore next: used by drag/drop which can't be tested easily */
|
|
|
|
if (this.dragging && !this.noDrop) {
|
|
return (// TODO: Add additional scope with file count, and other not-allowed reasons
|
|
this.normalizeSlot('drop-placeholder', {
|
|
allowed: this.dropAllowed
|
|
}) || (this.dropAllowed ? this.dropPlaceholder : h('span', {
|
|
staticClass: 'text-danger'
|
|
}, this.noDropPlaceholder))
|
|
);
|
|
} // No file chosen
|
|
|
|
|
|
if (this.files.length === 0) {
|
|
return this.normalizeSlot('placeholder') || this.placeholder;
|
|
}
|
|
|
|
var flattenedFiles = this.flattenedFiles,
|
|
clonedFiles = this.clonedFiles,
|
|
fileNames = this.fileNames,
|
|
computedFileNameFormatter = this.computedFileNameFormatter; // There is a slot for formatting the files/names
|
|
|
|
if (this.hasNormalizedSlot('file-name')) {
|
|
return this.normalizeSlot('file-name', {
|
|
files: flattenedFiles,
|
|
filesTraversed: clonedFiles,
|
|
names: fileNames
|
|
});
|
|
}
|
|
|
|
return computedFileNameFormatter(flattenedFiles, clonedFiles, fileNames);
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newValue) {
|
|
if (!newValue || isArray(newValue) && newValue.length === 0) {
|
|
this.reset();
|
|
}
|
|
},
|
|
files: function files(newValue, oldValue) {
|
|
if (!looseEqual(newValue, oldValue)) {
|
|
var multiple = this.multiple,
|
|
noTraverse = this.noTraverse;
|
|
var files = !multiple || noTraverse ? flattenDeep(newValue) : newValue;
|
|
this.$emit('input', multiple ? files : files[0] || null);
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// Listen for form reset events, to reset the file input
|
|
var $form = closest('form', this.$el);
|
|
|
|
if ($form) {
|
|
eventOn($form, 'reset', this.reset, EVENT_OPTIONS_PASSIVE);
|
|
this.$on('hook:beforeDestroy', function () {
|
|
eventOff($form, 'reset', _this.reset, EVENT_OPTIONS_PASSIVE);
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
isFileValid: function isFileValid(file) {
|
|
if (!file) {
|
|
return false;
|
|
}
|
|
|
|
var accept = this.computedAccept;
|
|
return accept ? accept.some(function (a) {
|
|
return a.rx.test(file[a.prop]);
|
|
}) : true;
|
|
},
|
|
isFilesArrayValid: function isFilesArrayValid(files) {
|
|
var _this2 = this;
|
|
|
|
return isArray(files) ? files.every(function (file) {
|
|
return _this2.isFileValid(file);
|
|
}) : this.isFileValid(files);
|
|
},
|
|
defaultFileNameFormatter: function defaultFileNameFormatter(flattenedFiles, clonedFiles, fileNames) {
|
|
return fileNames.join(', ');
|
|
},
|
|
setFiles: function setFiles(files) {
|
|
// Reset the dragging flags
|
|
this.dropAllowed = !this.noDrop;
|
|
this.dragging = false; // Set the selected files
|
|
|
|
this.files = this.multiple ? this.directory ? files : flattenDeep(files) : flattenDeep(files).slice(0, 1);
|
|
},
|
|
|
|
/* istanbul ignore next: used by Drag/Drop */
|
|
setInputFiles: function setInputFiles(files) {
|
|
// Try an set the file input files array so that `required`
|
|
// constraint works for dropped files (will fail in IE11 though)
|
|
// To be used only when dropping files
|
|
try {
|
|
// Firefox < 62 workaround exploiting https://bugzilla.mozilla.org/show_bug.cgi?id=1422655
|
|
var dataTransfer = new ClipboardEvent('').clipboardData || new DataTransfer(); // Add flattened files to temp `dataTransfer` object to get a true `FileList` array
|
|
|
|
flattenDeep(cloneDeep(files)).forEach(function (file) {
|
|
// Make sure to remove the custom `$path` attribute
|
|
delete file.$path;
|
|
dataTransfer.items.add(file);
|
|
});
|
|
this.$refs.input.files = dataTransfer.files;
|
|
} catch (_unused2) {}
|
|
},
|
|
reset: function reset() {
|
|
// IE 11 doesn't support setting `$input.value` to `''` or `null`
|
|
// So we use this little extra hack to reset the value, just in case
|
|
// This also appears to work on modern browsers as well
|
|
// Wrapped in try in case IE 11 or mobile Safari crap out
|
|
try {
|
|
var $input = this.$refs.input;
|
|
$input.value = '';
|
|
$input.type = '';
|
|
$input.type = 'file';
|
|
} catch (_unused3) {}
|
|
|
|
this.files = [];
|
|
},
|
|
handleFiles: function handleFiles(files) {
|
|
var isDrop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
if (isDrop) {
|
|
// When dropped, make sure to filter files with the internal `accept` logic
|
|
var filteredFiles = files.filter(this.isFilesArrayValid); // Only update files when we have any after filtering
|
|
|
|
if (filteredFiles.length > 0) {
|
|
this.setFiles(filteredFiles); // Try an set the file input's files array so that `required`
|
|
// constraint works for dropped files (will fail in IE 11 though)
|
|
|
|
this.setInputFiles(filteredFiles);
|
|
}
|
|
} else {
|
|
// We always update the files from the `change` event
|
|
this.setFiles(files);
|
|
}
|
|
},
|
|
focusHandler: function focusHandler(evt) {
|
|
// Bootstrap v4 doesn't have focus styling for custom file input
|
|
// Firefox has a `[type=file]:focus ~ sibling` selector issue,
|
|
// so we add a `focus` class to get around these bugs
|
|
if (this.plain || evt.type === 'focusout') {
|
|
this.hasFocus = false;
|
|
} else {
|
|
// Add focus styling for custom file input
|
|
this.hasFocus = true;
|
|
}
|
|
},
|
|
onChange: function onChange(evt) {
|
|
var _this3 = this;
|
|
|
|
var type = evt.type,
|
|
target = evt.target,
|
|
_evt$dataTransfer = evt.dataTransfer,
|
|
dataTransfer = _evt$dataTransfer === void 0 ? {} : _evt$dataTransfer;
|
|
var isDrop = type === 'drop'; // Always emit original event
|
|
|
|
this.$emit('change', evt);
|
|
var items = from(dataTransfer.items || []);
|
|
|
|
if (hasPromiseSupport && items.length > 0 && !isNull(getDataTransferItemEntry(items[0]))) {
|
|
// Drop handling for modern browsers
|
|
// Supports nested directory structures in `directory` mode
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
getAllFileEntries(items, this.directory).then(function (files) {
|
|
return _this3.handleFiles(files, isDrop);
|
|
});
|
|
} else {
|
|
// Standard file input handling (native file input change event),
|
|
// or fallback drop mode (IE 11 / Opera) which don't support `directory` mode
|
|
var files = from(target.files || dataTransfer.files || []).map(function (file) {
|
|
// Add custom `$path` property to each file (to be consistent with drop mode)
|
|
file.$path = file.webkitRelativePath || '';
|
|
return file;
|
|
});
|
|
this.handleFiles(files, isDrop);
|
|
}
|
|
},
|
|
onDragenter: function onDragenter(evt) {
|
|
stopEvent(evt);
|
|
this.dragging = true;
|
|
var _evt$dataTransfer2 = evt.dataTransfer,
|
|
dataTransfer = _evt$dataTransfer2 === void 0 ? {} : _evt$dataTransfer2; // Early exit when the input or dropping is disabled
|
|
|
|
if (this.noDrop || this.disabled || !this.dropAllowed) {
|
|
// Show deny feedback
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
dataTransfer.dropEffect = 'none';
|
|
this.dropAllowed = false;
|
|
return;
|
|
}
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
|
|
|
|
dataTransfer.dropEffect = 'copy';
|
|
},
|
|
// Note this event fires repeatedly while the mouse is over the dropzone at
|
|
// intervals in the milliseconds, so avoid doing much processing in here
|
|
onDragover: function onDragover(evt) {
|
|
stopEvent(evt);
|
|
this.dragging = true;
|
|
var _evt$dataTransfer3 = evt.dataTransfer,
|
|
dataTransfer = _evt$dataTransfer3 === void 0 ? {} : _evt$dataTransfer3; // Early exit when the input or dropping is disabled
|
|
|
|
if (this.noDrop || this.disabled || !this.dropAllowed) {
|
|
// Show deny feedback
|
|
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
dataTransfer.dropEffect = 'none';
|
|
this.dropAllowed = false;
|
|
return;
|
|
}
|
|
/* istanbul ignore next: not supported in JSDOM */
|
|
|
|
|
|
dataTransfer.dropEffect = 'copy';
|
|
},
|
|
onDragleave: function onDragleave(evt) {
|
|
var _this4 = this;
|
|
|
|
stopEvent(evt);
|
|
this.$nextTick(function () {
|
|
_this4.dragging = false; // Reset `dropAllowed` to default
|
|
|
|
_this4.dropAllowed = !_this4.noDrop;
|
|
});
|
|
},
|
|
// Triggered by a file drop onto drop target
|
|
onDrop: function onDrop(evt) {
|
|
var _this5 = this;
|
|
|
|
stopEvent(evt);
|
|
this.dragging = false; // Early exit when the input or dropping is disabled
|
|
|
|
if (this.noDrop || this.disabled || !this.dropAllowed) {
|
|
this.$nextTick(function () {
|
|
// Reset `dropAllowed` to default
|
|
_this5.dropAllowed = !_this5.noDrop;
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.onChange(evt);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var custom = this.custom,
|
|
plain = this.plain,
|
|
size = this.size,
|
|
dragging = this.dragging,
|
|
stateClass = this.stateClass; // Form Input
|
|
|
|
var $input = h('input', {
|
|
ref: 'input',
|
|
class: [{
|
|
'form-control-file': plain,
|
|
'custom-file-input': custom,
|
|
focus: custom && this.hasFocus
|
|
}, stateClass],
|
|
// With IE 11, the input gets in the "way" of the drop events,
|
|
// so we move it out of the way by putting it behind the label
|
|
// Bootstrap v4 has it in front
|
|
style: custom ? {
|
|
zIndex: -5
|
|
} : {},
|
|
attrs: this.computedAttrs,
|
|
on: {
|
|
change: this.onChange,
|
|
focusin: this.focusHandler,
|
|
focusout: this.focusHandler,
|
|
reset: this.reset
|
|
}
|
|
});
|
|
|
|
if (plain) {
|
|
return $input;
|
|
} // Overlay label
|
|
|
|
|
|
var $label = h('label', {
|
|
staticClass: 'custom-file-label',
|
|
class: {
|
|
dragging: dragging
|
|
},
|
|
attrs: {
|
|
for: this.safeId(),
|
|
// This goes away in Bootstrap v5
|
|
'data-browse': this.browseText || null
|
|
}
|
|
}, [h('span', {
|
|
staticClass: 'd-block form-file-text',
|
|
// `pointer-events: none` is used to make sure
|
|
// the drag events fire only on the label
|
|
style: {
|
|
pointerEvents: 'none'
|
|
}
|
|
}, [this.labelContent])]); // Return rendered custom file input
|
|
|
|
return h('div', {
|
|
staticClass: 'custom-file b-form-file',
|
|
class: [_defineProperty({}, "b-custom-control-".concat(size), size), stateClass],
|
|
attrs: {
|
|
id: this.safeId('_BV_file_outer_')
|
|
},
|
|
on: {
|
|
dragenter: this.onDragenter,
|
|
dragover: this.onDragover,
|
|
dragleave: this.onDragleave,
|
|
drop: this.onDrop
|
|
}
|
|
}, [$input, $label]);
|
|
}
|
|
});
|
|
|
|
var FormFilePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormFile: BFormFile,
|
|
BFile: BFormFile
|
|
}
|
|
});
|
|
|
|
var escapeChar = function escapeChar(value) {
|
|
return '\\' + value;
|
|
}; // The `cssEscape()` util is based on this `CSS.escape()` polyfill:
|
|
// https://github.com/mathiasbynens/CSS.escape
|
|
|
|
|
|
var cssEscape = function cssEscape(value) {
|
|
value = toString$1(value);
|
|
var length = value.length;
|
|
var firstCharCode = value.charCodeAt(0);
|
|
return value.split('').reduce(function (result, char, index) {
|
|
var charCode = value.charCodeAt(index); // If the character is NULL (U+0000), use (U+FFFD) as replacement
|
|
|
|
if (charCode === 0x0000) {
|
|
return result + "\uFFFD";
|
|
} // If the character ...
|
|
|
|
|
|
if ( // ... is U+007F OR
|
|
charCode === 0x007f || // ... is in the range [\1-\1F] (U+0001 to U+001F) OR ...
|
|
charCode >= 0x0001 && charCode <= 0x001f || // ... is the first character and is in the range [0-9] (U+0030 to U+0039) OR ...
|
|
index === 0 && charCode >= 0x0030 && charCode <= 0x0039 || // ... is the second character and is in the range [0-9] (U+0030 to U+0039)
|
|
// and the first character is a `-` (U+002D) ...
|
|
index === 1 && charCode >= 0x0030 && charCode <= 0x0039 && firstCharCode === 0x002d) {
|
|
// ... https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
|
|
return result + escapeChar("".concat(charCode.toString(16), " "));
|
|
} // If the character ...
|
|
|
|
|
|
if ( // ... is the first character AND ...
|
|
index === 0 && // ... is a `-` (U+002D) AND ...
|
|
charCode === 0x002d && // ... there is no second character ...
|
|
length === 1) {
|
|
// ... use the escaped character
|
|
return result + escapeChar(char);
|
|
} // If the character ...
|
|
|
|
|
|
if ( // ... is greater than or equal to U+0080 OR ...
|
|
charCode >= 0x0080 || // ... is `-` (U+002D) OR ...
|
|
charCode === 0x002d || // ... is `_` (U+005F) OR ...
|
|
charCode === 0x005f || // ... is in the range [0-9] (U+0030 to U+0039) OR ...
|
|
charCode >= 0x0030 && charCode <= 0x0039 || // ... is in the range [A-Z] (U+0041 to U+005A) OR ...
|
|
charCode >= 0x0041 && charCode <= 0x005a || // ... is in the range [a-z] (U+0061 to U+007A) ...
|
|
charCode >= 0x0061 && charCode <= 0x007a) {
|
|
// ... use the character itself
|
|
return result + char;
|
|
} // Otherwise use the escaped character
|
|
// See: https://drafts.csswg.org/cssom/#escape-a-character
|
|
|
|
|
|
return result + escapeChar(char);
|
|
}, '');
|
|
};
|
|
|
|
var ALIGN_SELF_VALUES = ['auto', 'start', 'end', 'center', 'baseline', 'stretch']; // Generates a prop object with a type of `[Boolean, String, Number]`
|
|
|
|
var boolStrNum = function boolStrNum() {
|
|
return {
|
|
type: [Boolean, String, Number],
|
|
default: false
|
|
};
|
|
}; // Generates a prop object with a type of `[String, Number]`
|
|
|
|
|
|
var strNum = function strNum() {
|
|
return {
|
|
type: [String, Number],
|
|
default: null
|
|
};
|
|
}; // Compute a breakpoint class name
|
|
|
|
|
|
var computeBreakpoint = function computeBreakpoint(type, breakpoint, val) {
|
|
var className = type;
|
|
|
|
if (isUndefinedOrNull(val) || val === false) {
|
|
return undefined;
|
|
}
|
|
|
|
if (breakpoint) {
|
|
className += "-".concat(breakpoint);
|
|
} // Handling the boolean style prop when accepting [Boolean, String, Number]
|
|
// means Vue will not convert <b-col sm></b-col> to sm: true for us.
|
|
// Since the default is false, an empty string indicates the prop's presence.
|
|
|
|
|
|
if (type === 'col' && (val === '' || val === true)) {
|
|
// .col-md
|
|
return lowerCase(className);
|
|
} // .order-md-6
|
|
|
|
|
|
className += "-".concat(val);
|
|
return lowerCase(className);
|
|
}; // Memoized function for better performance on generating class names
|
|
|
|
|
|
var computeBreakpointClass = memoize(computeBreakpoint); // Cached copy of the breakpoint prop names
|
|
|
|
var breakpointPropMap = create(null); // Lazy evaled props factory for BCol
|
|
|
|
var generateProps = function generateProps() {
|
|
// Grab the breakpoints from the cached config (exclude the '' (xs) breakpoint)
|
|
var breakpoints = getBreakpointsUpCached().filter(identity); // Supports classes like: .col-sm, .col-md-6, .col-lg-auto
|
|
|
|
var breakpointCol = breakpoints.reduce(function (propMap, breakpoint) {
|
|
if (breakpoint) {
|
|
// We filter out the '' breakpoint (xs), as making a prop name ''
|
|
// would not work. The `cols` prop is used for `xs`
|
|
propMap[breakpoint] = boolStrNum();
|
|
}
|
|
|
|
return propMap;
|
|
}, create(null)); // Supports classes like: .offset-md-1, .offset-lg-12
|
|
|
|
var breakpointOffset = breakpoints.reduce(function (propMap, breakpoint) {
|
|
propMap[suffixPropName(breakpoint, 'offset')] = strNum();
|
|
return propMap;
|
|
}, create(null)); // Supports classes like: .order-md-1, .order-lg-12
|
|
|
|
var breakpointOrder = breakpoints.reduce(function (propMap, breakpoint) {
|
|
propMap[suffixPropName(breakpoint, 'order')] = strNum();
|
|
return propMap;
|
|
}, create(null)); // For loop doesn't need to check hasOwnProperty
|
|
// when using an object created from null
|
|
|
|
breakpointPropMap = assign(create(null), {
|
|
col: keys(breakpointCol),
|
|
offset: keys(breakpointOffset),
|
|
order: keys(breakpointOrder)
|
|
}); // Return the generated props
|
|
|
|
return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
|
|
// Generic flexbox .col (xs)
|
|
col: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// .col-[1-12]|auto (xs)
|
|
cols: strNum()
|
|
}, breakpointCol), {}, {
|
|
offset: strNum()
|
|
}, breakpointOffset), {}, {
|
|
order: strNum()
|
|
}, breakpointOrder), {}, {
|
|
// Flex alignment
|
|
alignSelf: {
|
|
type: String,
|
|
default: null,
|
|
validator: function validator(value) {
|
|
return arrayIncludes(ALIGN_SELF_VALUES, value);
|
|
}
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
});
|
|
}; // We do not use Vue.extend here as that would evaluate the props
|
|
// immediately, which we do not want to happen
|
|
// @vue/component
|
|
|
|
|
|
var BCol = {
|
|
name: NAME_COL,
|
|
functional: true,
|
|
|
|
get props() {
|
|
// Allow props to be lazy evaled on first access and
|
|
// then they become a non-getter afterwards.
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
|
|
delete this.props; // eslint-disable-next-line no-return-assign
|
|
|
|
return this.props = generateProps();
|
|
},
|
|
|
|
render: function render(h, _ref) {
|
|
var _classList$push;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var classList = []; // Loop through `col`, `offset`, `order` breakpoint props
|
|
|
|
for (var type in breakpointPropMap) {
|
|
// Returns colSm, offset, offsetSm, orderMd, etc.
|
|
var _keys = breakpointPropMap[type];
|
|
|
|
for (var i = 0; i < _keys.length; i++) {
|
|
// computeBreakpoint(col, colSm => Sm, value=[String, Number, Boolean])
|
|
var c = computeBreakpointClass(type, _keys[i].replace(type, ''), props[_keys[i]]); // If a class is returned, push it onto the array.
|
|
|
|
if (c) {
|
|
classList.push(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
var hasColClasses = classList.some(function (className) {
|
|
return RX_COL_CLASS.test(className);
|
|
});
|
|
classList.push((_classList$push = {
|
|
// Default to .col if no other col-{bp}-* classes generated nor `cols` specified.
|
|
col: props.col || !hasColClasses && !props.cols
|
|
}, _defineProperty(_classList$push, "col-".concat(props.cols), props.cols), _defineProperty(_classList$push, "offset-".concat(props.offset), props.offset), _defineProperty(_classList$push, "order-".concat(props.order), props.order), _defineProperty(_classList$push, "align-self-".concat(props.alignSelf), props.alignSelf), _classList$push));
|
|
return h(props.tag, a(data, {
|
|
class: classList
|
|
}), children);
|
|
}
|
|
};
|
|
|
|
// Selector for finding first input in the form-group
|
|
|
|
var INPUT_SELECTOR = 'input:not([disabled]),textarea:not([disabled]),select:not([disabled])'; // A list of interactive elements (tag names) inside `<b-form-group>`'s legend
|
|
|
|
var LEGEND_INTERACTIVE_ELEMENTS = ['input', 'select', 'textarea', 'label', 'button', 'a']; // -- BFormGroup prop factory -- used for lazy generation of props
|
|
// Memoize this function to return cached values to
|
|
// save time in computed functions
|
|
|
|
var makePropName = memoize(function () {
|
|
var breakpoint = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
|
return "".concat(prefix).concat(upperFirst(breakpoint));
|
|
}); // BFormGroup prop generator for lazy generation of props
|
|
|
|
var generateProps$1 = function generateProps() {
|
|
var CODE_BREAKPOINTS = getBreakpointsUpCached(); // Generate the `labelCol` breakpoint props
|
|
|
|
var bpLabelColProps = CODE_BREAKPOINTS.reduce(function (props, breakpoint) {
|
|
// i.e. 'label-cols', 'label-cols-sm', 'label-cols-md', ...
|
|
props[makePropName(breakpoint, 'labelCols')] = {
|
|
type: [Number, String, Boolean],
|
|
default: breakpoint ? false : null
|
|
};
|
|
return props;
|
|
}, create(null)); // Generate the `labelAlign` breakpoint props
|
|
|
|
var bpLabelAlignProps = CODE_BREAKPOINTS.reduce(function (props, breakpoint) {
|
|
// 'label-align', 'bel-align-sm', 'label-align-md', ...
|
|
props[makePropName(breakpoint, 'labelAlign')] = {
|
|
type: String // left, right, center
|
|
// default: null
|
|
|
|
};
|
|
return props;
|
|
}, create(null));
|
|
return makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$E), {}, {
|
|
label: {
|
|
type: String // default: null
|
|
|
|
},
|
|
labelFor: {
|
|
type: String // default: null
|
|
|
|
},
|
|
labelSize: {
|
|
type: String // default: null
|
|
|
|
},
|
|
labelSrOnly: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, bpLabelColProps), bpLabelAlignProps), {}, {
|
|
labelClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
description: {
|
|
type: String // default: null
|
|
|
|
},
|
|
invalidFeedback: {
|
|
type: String // default: null
|
|
|
|
},
|
|
validFeedback: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tooltip: {
|
|
// Enable tooltip style feedback
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
feedbackAriaLive: {
|
|
type: String,
|
|
default: 'assertive'
|
|
},
|
|
validated: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_FORM_GROUP);
|
|
}; // We do not use Vue.extend here as that would evaluate the props
|
|
// immediately, which we do not want to happen
|
|
// @vue/component
|
|
|
|
|
|
var BFormGroup = {
|
|
name: NAME_FORM_GROUP,
|
|
mixins: [idMixin, formStateMixin, normalizeSlotMixin],
|
|
|
|
get props() {
|
|
// Allow props to be lazy evaled on first access and
|
|
// then they become a non-getter afterwards
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
|
|
delete this.props; // eslint-disable-next-line no-return-assign
|
|
|
|
return this.props = generateProps$1();
|
|
},
|
|
|
|
data: function data() {
|
|
return {
|
|
describedByIds: ''
|
|
};
|
|
},
|
|
computed: {
|
|
labelColProps: function labelColProps() {
|
|
var _this = this;
|
|
|
|
var props = {};
|
|
getBreakpointsUpCached().forEach(function (breakpoint) {
|
|
// Grab the value if the label column breakpoint prop
|
|
var propVal = _this[makePropName(breakpoint, 'labelCols')]; // Handle case where the prop's value is an empty string,
|
|
// which represents `true`
|
|
|
|
|
|
propVal = propVal === '' ? true : propVal || false;
|
|
|
|
if (!isBoolean(propVal) && propVal !== 'auto') {
|
|
// Convert to column size to number
|
|
propVal = toInteger(propVal, 0); // Ensure column size is greater than `0`
|
|
|
|
propVal = propVal > 0 ? propVal : false;
|
|
}
|
|
|
|
if (propVal) {
|
|
// Add the prop to the list of props to give to `<b-col>`
|
|
// If breakpoint is '' (`labelCols` is `true`), then we use the
|
|
// col prop to make equal width at 'xs'
|
|
props[breakpoint || (isBoolean(propVal) ? 'col' : 'cols')] = propVal;
|
|
}
|
|
});
|
|
return props;
|
|
},
|
|
labelAlignClasses: function labelAlignClasses() {
|
|
var _this2 = this;
|
|
|
|
var classes = [];
|
|
getBreakpointsUpCached().forEach(function (breakpoint) {
|
|
// Assemble the label column breakpoint align classes
|
|
var propVal = _this2[makePropName(breakpoint, 'labelAlign')] || null;
|
|
|
|
if (propVal) {
|
|
var className = breakpoint ? "text-".concat(breakpoint, "-").concat(propVal) : "text-".concat(propVal);
|
|
classes.push(className);
|
|
}
|
|
});
|
|
return classes;
|
|
},
|
|
isHorizontal: function isHorizontal() {
|
|
// Determine if the resultant form-group will be rendered
|
|
// horizontal (meaning it has label-col breakpoints)
|
|
return keys(this.labelColProps).length > 0;
|
|
}
|
|
},
|
|
watch: {
|
|
describedByIds: function describedByIds(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
this.setInputDescribedBy(newValue, oldValue);
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this3 = this;
|
|
|
|
this.$nextTick(function () {
|
|
// Set the `aria-describedby` IDs on the input specified by `label-for`
|
|
// We do this in a `$nextTick()` to ensure the children have finished rendering
|
|
_this3.setInputDescribedBy(_this3.describedByIds);
|
|
});
|
|
},
|
|
methods: {
|
|
legendClick: function legendClick(evt) {
|
|
// Don't do anything if labelFor is set
|
|
|
|
/* istanbul ignore next: clicking a label will focus the input, so no need to test */
|
|
if (this.labelFor) {
|
|
return;
|
|
}
|
|
|
|
var target = evt.target;
|
|
var tagName = target ? target.tagName : ''; // If clicked an interactive element inside legend,
|
|
// we just let the default happen
|
|
|
|
/* istanbul ignore next */
|
|
|
|
if (LEGEND_INTERACTIVE_ELEMENTS.indexOf(tagName) !== -1) {
|
|
return;
|
|
}
|
|
|
|
var inputs = selectAll(INPUT_SELECTOR, this.$refs.content).filter(isVisible); // If only a single input, focus it, emulating label behaviour
|
|
|
|
if (inputs && inputs.length === 1) {
|
|
attemptFocus(inputs[0]);
|
|
}
|
|
},
|
|
// Sets the `aria-describedby` attribute on the input if label-for is set
|
|
// Optionally accepts a string of IDs to remove as the second parameter
|
|
// Preserves any `aria-describedby` value(s) user may have on input
|
|
setInputDescribedBy: function setInputDescribedBy(add, remove) {
|
|
if (this.labelFor && isBrowser) {
|
|
// We need to escape `labelFor` since it can be user-provided
|
|
var input = select("#".concat(cssEscape(this.labelFor)), this.$refs.content);
|
|
|
|
if (input) {
|
|
var adb = 'aria-describedby';
|
|
var ids = (getAttr(input, adb) || '').split(/\s+/);
|
|
add = (add || '').split(/\s+/);
|
|
remove = (remove || '').split(/\s+/); // Update ID list, preserving any original IDs
|
|
// and ensuring the ID's are unique
|
|
|
|
ids = ids.filter(function (id) {
|
|
return !arrayIncludes(remove, id);
|
|
}).concat(add).filter(Boolean);
|
|
ids = keys(ids.reduce(function (memo, id) {
|
|
return _objectSpread2(_objectSpread2({}, memo), {}, _defineProperty({}, id, true));
|
|
}, {})).join(' ').trim();
|
|
|
|
if (ids) {
|
|
setAttr(input, adb, ids);
|
|
} else {
|
|
// No IDs, so remove the attribute
|
|
removeAttr(input, adb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var labelFor = this.labelFor,
|
|
tooltip = this.tooltip,
|
|
feedbackAriaLive = this.feedbackAriaLive,
|
|
state = this.computedState,
|
|
isHorizontal = this.isHorizontal,
|
|
normalizeSlot = this.normalizeSlot;
|
|
var isFieldset = !labelFor;
|
|
var $label = h();
|
|
var labelContent = normalizeSlot(SLOT_NAME_LABEL) || this.label;
|
|
var labelId = labelContent ? this.safeId('_BV_label_') : null;
|
|
|
|
if (labelContent || isHorizontal) {
|
|
var labelSize = this.labelSize,
|
|
labelColProps = this.labelColProps;
|
|
var isLegend = isFieldset;
|
|
var labelTag = isLegend ? 'legend' : 'label';
|
|
|
|
if (this.labelSrOnly) {
|
|
if (labelContent) {
|
|
$label = h(labelTag, {
|
|
class: 'sr-only',
|
|
attrs: {
|
|
id: labelId,
|
|
for: labelFor || null
|
|
}
|
|
}, [labelContent]);
|
|
}
|
|
|
|
$label = h(isHorizontal ? BCol : 'div', {
|
|
props: isHorizontal ? labelColProps : {}
|
|
}, [$label]);
|
|
} else {
|
|
$label = h(isHorizontal ? BCol : labelTag, {
|
|
on: isLegend ? {
|
|
click: this.legendClick
|
|
} : {},
|
|
props: isHorizontal ? _objectSpread2({
|
|
tag: labelTag
|
|
}, labelColProps) : {},
|
|
attrs: {
|
|
id: labelId,
|
|
for: labelFor || null,
|
|
// We add a `tabindex` to legend so that screen readers
|
|
// will properly read the `aria-labelledby` in IE
|
|
tabindex: isLegend ? '-1' : null
|
|
},
|
|
class: [// Hide the focus ring on the legend
|
|
isLegend ? 'bv-no-focus-ring' : '', // When horizontal or if a legend is rendered, add 'col-form-label' class
|
|
// for correct sizing as Bootstrap has inconsistent font styling for
|
|
// legend in non-horizontal form-groups
|
|
// See: https://github.com/twbs/bootstrap/issues/27805
|
|
isHorizontal || isLegend ? 'col-form-label' : '', // Emulate label padding top of `0` on legend when not horizontal
|
|
!isHorizontal && isLegend ? 'pt-0' : '', // If not horizontal and not a legend, we add 'd-block' class to label
|
|
// so that label-align works
|
|
!isHorizontal && !isLegend ? 'd-block' : '', labelSize ? "col-form-label-".concat(labelSize) : '', this.labelAlignClasses, this.labelClass]
|
|
}, [labelContent]);
|
|
}
|
|
}
|
|
|
|
var $invalidFeedback = h();
|
|
var invalidFeedbackContent = normalizeSlot('invalid-feedback') || this.invalidFeedback;
|
|
var invalidFeedbackId = invalidFeedbackContent ? this.safeId('_BV_feedback_invalid_') : null;
|
|
|
|
if (invalidFeedbackContent) {
|
|
$invalidFeedback = h(BFormInvalidFeedback, {
|
|
props: {
|
|
id: invalidFeedbackId,
|
|
// If state is explicitly `false`, always show the feedback
|
|
state: state,
|
|
tooltip: tooltip,
|
|
ariaLive: feedbackAriaLive,
|
|
role: feedbackAriaLive ? 'alert' : null
|
|
},
|
|
attrs: {
|
|
tabindex: invalidFeedbackContent ? '-1' : null
|
|
}
|
|
}, [invalidFeedbackContent]);
|
|
}
|
|
|
|
var $validFeedback = h();
|
|
var validFeedbackContent = normalizeSlot('valid-feedback') || this.validFeedback;
|
|
var validFeedbackId = validFeedbackContent ? this.safeId('_BV_feedback_valid_') : null;
|
|
|
|
if (validFeedbackContent) {
|
|
$validFeedback = h(BFormValidFeedback, {
|
|
props: {
|
|
id: validFeedbackId,
|
|
// If state is explicitly `true`, always show the feedback
|
|
state: state,
|
|
tooltip: tooltip,
|
|
ariaLive: feedbackAriaLive,
|
|
role: feedbackAriaLive ? 'alert' : null
|
|
},
|
|
attrs: {
|
|
tabindex: validFeedbackContent ? '-1' : null
|
|
}
|
|
}, [validFeedbackContent]);
|
|
}
|
|
|
|
var $description = h();
|
|
var descriptionContent = normalizeSlot(SLOT_NAME_DESCRIPTION) || this.description;
|
|
var descriptionId = descriptionContent ? this.safeId('_BV_description_') : null;
|
|
|
|
if (descriptionContent) {
|
|
$description = h(BFormText, {
|
|
attrs: {
|
|
id: descriptionId,
|
|
tabindex: descriptionContent ? '-1' : null
|
|
}
|
|
}, [descriptionContent]);
|
|
}
|
|
|
|
var $content = h(isHorizontal ? BCol : 'div', {
|
|
ref: 'content',
|
|
// Hide focus ring
|
|
staticClass: 'bv-no-focus-ring',
|
|
attrs: {
|
|
tabindex: isFieldset ? '-1' : null,
|
|
role: isFieldset ? 'group' : null,
|
|
'aria-labelledby': isFieldset ? labelId : null
|
|
}
|
|
}, [normalizeSlot() || h(), $invalidFeedback, $validFeedback, $description]); // Update the `aria-describedby` IDs
|
|
// Screen readers will read out any content linked to by `aria-describedby`
|
|
// even if the content is hidden with `display: none;`, hence we only include
|
|
// feedback IDs if the form-group's state is explicitly valid or invalid
|
|
|
|
this.describedByIds = [descriptionId, state === false ? invalidFeedbackId : null, state === true ? validFeedbackId : null].filter(Boolean).join(' '); // Return it wrapped in a form-group
|
|
// Note: Fieldsets do not support adding `row` or `form-row` directly
|
|
// to them due to browser specific render issues, so we move the `form-row`
|
|
// to an inner wrapper div when horizontal and using a fieldset
|
|
|
|
return h(isFieldset ? 'fieldset' : isHorizontal ? BFormRow : 'div', {
|
|
staticClass: 'form-group',
|
|
class: [this.validated ? 'was-validated' : null, this.stateClass],
|
|
attrs: {
|
|
id: this.safeId(),
|
|
disabled: isFieldset ? this.disabled : null,
|
|
role: isFieldset ? null : 'group',
|
|
'aria-invalid': state === false ? 'true' : null,
|
|
// Only apply aria-labelledby if we are a horizontal fieldset
|
|
// as the legend is no longer a direct child of fieldset
|
|
'aria-labelledby': isFieldset && isHorizontal ? labelId : null,
|
|
// Only apply `aria-describedby` IDs if we are a fieldset
|
|
// as the input will have the IDs when not a fieldset
|
|
'aria-describedby': isFieldset ? this.describedByIds : null
|
|
}
|
|
}, isHorizontal && isFieldset ? [h(BFormRow, [$label, $content])] : [$label, $content]);
|
|
}
|
|
};
|
|
|
|
var FormGroupPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormGroup: BFormGroup,
|
|
BFormFieldset: BFormGroup
|
|
}
|
|
});
|
|
|
|
// @vue/component
|
|
var formSelectionMixin = {
|
|
computed: {
|
|
selectionStart: {
|
|
// Expose selectionStart for formatters, etc
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.selectionStart;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
set: function set(val) {
|
|
this.$refs.input.selectionStart = val;
|
|
}
|
|
},
|
|
selectionEnd: {
|
|
// Expose selectionEnd for formatters, etc
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.selectionEnd;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
set: function set(val) {
|
|
this.$refs.input.selectionEnd = val;
|
|
}
|
|
},
|
|
selectionDirection: {
|
|
// Expose selectionDirection for formatters, etc
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.selectionDirection;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
set: function set(val) {
|
|
this.$refs.input.selectionDirection = val;
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
/* istanbul ignore next */
|
|
select: function select() {
|
|
var _this$$refs$input;
|
|
|
|
// For external handler that may want a select() method
|
|
(_this$$refs$input = this.$refs.input).select.apply(_this$$refs$input, arguments);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
setSelectionRange: function setSelectionRange() {
|
|
var _this$$refs$input2;
|
|
|
|
// For external handler that may want a setSelectionRange(a,b,c) method
|
|
(_this$$refs$input2 = this.$refs.input).setSelectionRange.apply(_this$$refs$input2, arguments);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
setRangeText: function setRangeText() {
|
|
var _this$$refs$input3;
|
|
|
|
// For external handler that may want a setRangeText(a,b,c) method
|
|
(_this$$refs$input3 = this.$refs.input).setRangeText.apply(_this$$refs$input3, arguments);
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$I = makePropsConfigurable({
|
|
value: {
|
|
type: [String, Number],
|
|
default: ''
|
|
},
|
|
ariaInvalid: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
plaintext: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
autocomplete: {
|
|
type: String // default: null
|
|
|
|
},
|
|
placeholder: {
|
|
type: String // default: null
|
|
|
|
},
|
|
formatter: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
lazyFormatter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
trim: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
number: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lazy: {
|
|
// Only update the `v-model` on blur/change events
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
debounce: {
|
|
// Debounce timeout (in ms). Not applicable with `lazy` prop
|
|
type: [Number, String],
|
|
default: 0
|
|
}
|
|
}, 'formTextControls'); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var formTextMixin = {
|
|
model: {
|
|
prop: 'value',
|
|
event: 'update'
|
|
},
|
|
props: props$I,
|
|
data: function data() {
|
|
return {
|
|
localValue: toString$1(this.value),
|
|
vModelValue: this.value
|
|
};
|
|
},
|
|
computed: {
|
|
computedClass: function computedClass() {
|
|
return [{
|
|
// Range input needs class `custom-range`
|
|
'custom-range': this.type === 'range',
|
|
// `plaintext` not supported by `type="range"` or `type="color"`
|
|
'form-control-plaintext': this.plaintext && this.type !== 'range' && this.type !== 'color',
|
|
// `form-control` not used by `type="range"` or `plaintext`
|
|
// Always used by `type="color"`
|
|
'form-control': !this.plaintext && this.type !== 'range' || this.type === 'color'
|
|
}, this.sizeFormClass, this.stateClass];
|
|
},
|
|
computedAriaInvalid: function computedAriaInvalid() {
|
|
if (!this.ariaInvalid || this.ariaInvalid === 'false') {
|
|
// `this.ariaInvalid` is `null` or `false` or 'false'
|
|
return this.computedState === false ? 'true' : null;
|
|
}
|
|
|
|
if (this.ariaInvalid === true) {
|
|
// User wants explicit `:aria-invalid="true"`
|
|
return 'true';
|
|
} // Most likely a string value (which could be the string 'true')
|
|
|
|
|
|
return this.ariaInvalid;
|
|
},
|
|
computedDebounce: function computedDebounce() {
|
|
// Ensure we have a positive number equal to or greater than 0
|
|
return mathMax(toInteger(this.debounce, 0), 0);
|
|
},
|
|
hasFormatter: function hasFormatter() {
|
|
var result = null;
|
|
|
|
try {
|
|
result = this.formatter();
|
|
} catch (_unused) {}
|
|
|
|
return !isUndefined(result);
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal) {
|
|
var stringifyValue = toString$1(newVal);
|
|
|
|
if (stringifyValue !== this.localValue && newVal !== this.vModelValue) {
|
|
// Clear any pending debounce timeout, as we are overwriting the user input
|
|
this.clearDebounce(); // Update the local values
|
|
|
|
this.localValue = stringifyValue;
|
|
this.vModelValue = newVal;
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_inputDebounceTimer = null;
|
|
},
|
|
mounted: function mounted() {
|
|
// Set up destroy handler
|
|
this.$on('hook:beforeDestroy', this.clearDebounce); // Preset the internal state
|
|
|
|
var value = this.value;
|
|
var stringifyValue = toString$1(value);
|
|
/* istanbul ignore next */
|
|
|
|
if (stringifyValue !== this.localValue && value !== this.vModelValue) {
|
|
this.localValue = stringifyValue;
|
|
this.vModelValue = value;
|
|
}
|
|
},
|
|
methods: {
|
|
clearDebounce: function clearDebounce() {
|
|
clearTimeout(this.$_inputDebounceTimer);
|
|
this.$_inputDebounceTimer = null;
|
|
},
|
|
formatValue: function formatValue(value, evt) {
|
|
var force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
value = toString$1(value);
|
|
|
|
if (this.hasFormatter && (!this.lazyFormatter || force)) {
|
|
value = this.formatter(value, evt);
|
|
}
|
|
|
|
return value;
|
|
},
|
|
modifyValue: function modifyValue(value) {
|
|
// Emulate `.trim` modifier behaviour
|
|
if (this.trim) {
|
|
value = value.trim();
|
|
} // Emulate `.number` modifier behaviour
|
|
|
|
|
|
if (this.number) {
|
|
value = toFloat(value, value);
|
|
}
|
|
|
|
return value;
|
|
},
|
|
updateValue: function updateValue(value) {
|
|
var _this = this;
|
|
|
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
var lazy = this.lazy;
|
|
|
|
if (lazy && !force) {
|
|
return;
|
|
} // Make sure to always clear the debounce when `updateValue()`
|
|
// is called, even when the v-model hasn't changed
|
|
|
|
|
|
this.clearDebounce(); // Define the shared update logic in a method to be able to use
|
|
// it for immediate and debounced value changes
|
|
|
|
var doUpdate = function doUpdate() {
|
|
value = _this.modifyValue(value);
|
|
|
|
if (value !== _this.vModelValue) {
|
|
_this.vModelValue = value;
|
|
|
|
_this.$emit('update', value);
|
|
} else if (_this.hasFormatter) {
|
|
// When the `vModelValue` hasn't changed but the actual input value
|
|
// is out of sync, make sure to change it to the given one
|
|
// Usually caused by browser autocomplete and how it triggers the
|
|
// change or input event, or depending on the formatter function
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/2657
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3498
|
|
|
|
/* istanbul ignore next: hard to test */
|
|
var $input = _this.$refs.input;
|
|
/* istanbul ignore if: hard to test out of sync value */
|
|
|
|
if ($input && value !== $input.value) {
|
|
$input.value = value;
|
|
}
|
|
}
|
|
}; // Only debounce the value update when a value greater than `0`
|
|
// is set and we are not in lazy mode or this is a forced update
|
|
|
|
|
|
var debounce = this.computedDebounce;
|
|
|
|
if (debounce > 0 && !lazy && !force) {
|
|
this.$_inputDebounceTimer = setTimeout(doUpdate, debounce);
|
|
} else {
|
|
// Immediately update the v-model
|
|
doUpdate();
|
|
}
|
|
},
|
|
onInput: function onInput(evt) {
|
|
// `evt.target.composing` is set by Vue
|
|
// https://github.com/vuejs/vue/blob/dev/src/platforms/web/runtime/directives/model.js
|
|
// TODO: Is this needed now with the latest Vue?
|
|
|
|
/* istanbul ignore if: hard to test composition events */
|
|
if (evt.target.composing) {
|
|
return;
|
|
}
|
|
|
|
var value = evt.target.value;
|
|
var formattedValue = this.formatValue(value, evt); // Exit when the `formatter` function strictly returned `false`
|
|
// or prevented the input event
|
|
|
|
/* istanbul ignore next */
|
|
|
|
if (formattedValue === false || evt.defaultPrevented) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.localValue = formattedValue;
|
|
this.updateValue(formattedValue);
|
|
this.$emit('input', formattedValue);
|
|
},
|
|
onChange: function onChange(evt) {
|
|
var value = evt.target.value;
|
|
var formattedValue = this.formatValue(value, evt); // Exit when the `formatter` function strictly returned `false`
|
|
// or prevented the input event
|
|
|
|
/* istanbul ignore next */
|
|
|
|
if (formattedValue === false || evt.defaultPrevented) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.localValue = formattedValue;
|
|
this.updateValue(formattedValue, true);
|
|
this.$emit('change', formattedValue);
|
|
},
|
|
onBlur: function onBlur(evt) {
|
|
// Apply the `localValue` on blur to prevent cursor jumps
|
|
// on mobile browsers (e.g. caused by autocomplete)
|
|
var value = evt.target.value;
|
|
var formattedValue = this.formatValue(value, evt, true);
|
|
|
|
if (formattedValue !== false) {
|
|
// We need to use the modified value here to apply the
|
|
// `.trim` and `.number` modifiers properly
|
|
this.localValue = toString$1(this.modifyValue(formattedValue)); // We pass the formatted value here since the `updateValue` method
|
|
// handles the modifiers itself
|
|
|
|
this.updateValue(formattedValue, true);
|
|
} // Emit native blur event
|
|
|
|
|
|
this.$emit('blur', evt);
|
|
},
|
|
focus: function focus() {
|
|
// For external handler that may want a focus method
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$el);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
// For external handler that may want a blur method
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$el);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// @vue/component
|
|
var formValidityMixin = {
|
|
computed: {
|
|
validity: {
|
|
// Expose validity property
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.validity;
|
|
}
|
|
},
|
|
validationMessage: {
|
|
// Expose validationMessage property
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.validationMessage;
|
|
}
|
|
},
|
|
willValidate: {
|
|
// Expose willValidate property
|
|
cache: false,
|
|
|
|
/* istanbul ignore next */
|
|
get: function get() {
|
|
return this.$refs.input.willValidate;
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
/* istanbul ignore next */
|
|
setCustomValidity: function setCustomValidity() {
|
|
var _this$$refs$input;
|
|
|
|
// For external handler that may want a setCustomValidity(...) method
|
|
return (_this$$refs$input = this.$refs.input).setCustomValidity.apply(_this$$refs$input, arguments);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
checkValidity: function checkValidity() {
|
|
var _this$$refs$input2;
|
|
|
|
// For external handler that may want a checkValidity(...) method
|
|
return (_this$$refs$input2 = this.$refs.input).checkValidity.apply(_this$$refs$input2, arguments);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
reportValidity: function reportValidity() {
|
|
var _this$$refs$input3;
|
|
|
|
// For external handler that may want a reportValidity(...) method
|
|
return (_this$$refs$input3 = this.$refs.input).reportValidity.apply(_this$$refs$input3, arguments);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Valid supported input types
|
|
|
|
var TYPES$1 = ['text', 'password', 'email', 'number', 'url', 'tel', 'search', 'range', 'color', 'date', 'time', 'datetime', 'datetime-local', 'month', 'week']; // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BFormInput = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_INPUT,
|
|
// Mixin order is important!
|
|
mixins: [listenersMixin, idMixin, formControlMixin, formSizeMixin, formStateMixin, formTextMixin, formSelectionMixin, formValidityMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$D), props$E), props$I), {}, {
|
|
// `value` prop is defined in form-text mixin
|
|
type: {
|
|
type: String,
|
|
default: 'text',
|
|
validator: function validator(type) {
|
|
return arrayIncludes(TYPES$1, type);
|
|
}
|
|
},
|
|
noWheel: {
|
|
// Disable mousewheel to prevent wheel from
|
|
// changing values (i.e. number/date)
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
min: {
|
|
type: [String, Number] // default: null
|
|
|
|
},
|
|
max: {
|
|
type: [String, Number] // default: null
|
|
|
|
},
|
|
step: {
|
|
type: [String, Number] // default: null
|
|
|
|
},
|
|
list: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}), NAME_FORM_INPUT),
|
|
computed: {
|
|
localType: function localType() {
|
|
// We only allow certain types
|
|
return arrayIncludes(TYPES$1, this.type) ? this.type : 'text';
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
var type = this.localType,
|
|
disabled = this.disabled,
|
|
placeholder = this.placeholder,
|
|
required = this.required,
|
|
min = this.min,
|
|
max = this.max,
|
|
step = this.step;
|
|
return {
|
|
id: this.safeId(),
|
|
name: this.name || null,
|
|
form: this.form || null,
|
|
type: type,
|
|
disabled: disabled,
|
|
placeholder: placeholder,
|
|
required: required,
|
|
autocomplete: this.autocomplete || null,
|
|
readonly: this.readonly || this.plaintext,
|
|
min: min,
|
|
max: max,
|
|
step: step,
|
|
list: type !== 'password' ? this.list : null,
|
|
'aria-required': required ? 'true' : null,
|
|
'aria-invalid': this.computedAriaInvalid
|
|
};
|
|
},
|
|
computedListeners: function computedListeners() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvListeners), {}, {
|
|
input: this.onInput,
|
|
change: this.onChange,
|
|
blur: this.onBlur
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
noWheel: function noWheel(newVal) {
|
|
this.setWheelStopper(newVal);
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
this.setWheelStopper(this.noWheel);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
// Turn off listeners when keep-alive component deactivated
|
|
|
|
/* istanbul ignore next */
|
|
this.setWheelStopper(false);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
// Turn on listeners (if no-wheel) when keep-alive component activated
|
|
|
|
/* istanbul ignore next */
|
|
this.setWheelStopper(this.noWheel);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
/* istanbul ignore next */
|
|
this.setWheelStopper(false);
|
|
},
|
|
methods: {
|
|
setWheelStopper: function setWheelStopper(on) {
|
|
var input = this.$el; // We use native events, so that we don't interfere with propagation
|
|
|
|
eventOnOff(on, input, 'focus', this.onWheelFocus);
|
|
eventOnOff(on, input, 'blur', this.onWheelBlur);
|
|
|
|
if (!on) {
|
|
eventOff(document, 'wheel', this.stopWheel);
|
|
}
|
|
},
|
|
onWheelFocus: function onWheelFocus() {
|
|
eventOn(document, 'wheel', this.stopWheel);
|
|
},
|
|
onWheelBlur: function onWheelBlur() {
|
|
eventOff(document, 'wheel', this.stopWheel);
|
|
},
|
|
stopWheel: function stopWheel(evt) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
attemptBlur(this.$el);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('input', {
|
|
ref: 'input',
|
|
class: this.computedClass,
|
|
attrs: this.computedAttrs,
|
|
domProps: {
|
|
value: this.localValue
|
|
},
|
|
on: this.computedListeners
|
|
});
|
|
}
|
|
});
|
|
|
|
var FormInputPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormInput: BFormInput,
|
|
BInput: BFormInput
|
|
}
|
|
});
|
|
|
|
var props$J = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$v), props$F), props$D), props$E), {}, {
|
|
checked: {
|
|
// type: [String, Number, Boolean, Object],
|
|
default: null
|
|
}
|
|
}), NAME_FORM_RADIO_GROUP); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BFormRadioGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_RADIO_GROUP,
|
|
mixins: [idMixin, formControlMixin, formRadioCheckGroupMixin, // Includes render function
|
|
formOptionsMixin, formSizeMixin, formStateMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvRadioGroup: this
|
|
};
|
|
},
|
|
props: props$J,
|
|
data: function data() {
|
|
return {
|
|
localChecked: this.checked
|
|
};
|
|
},
|
|
computed: {
|
|
isRadioGroup: function isRadioGroup() {
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
var FormRadioPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormRadio: BFormRadio,
|
|
BRadio: BFormRadio,
|
|
BFormRadioGroup: BFormRadioGroup,
|
|
BRadioGroup: BFormRadioGroup
|
|
}
|
|
});
|
|
|
|
var MIN_STARS = 3;
|
|
var DEFAULT_STARS = 5; // --- Utility methods ---
|
|
|
|
var computeStars = function computeStars(stars) {
|
|
return mathMax(MIN_STARS, toInteger(stars, DEFAULT_STARS));
|
|
};
|
|
|
|
var clampValue = function clampValue(value, min, max) {
|
|
return mathMax(mathMin(value, max), min);
|
|
}; // --- Private helper components ---
|
|
// @vue/component
|
|
|
|
|
|
var BVFormRatingStar = Vue__default['default'].extend({
|
|
name: NAME_FORM_RATING_STAR,
|
|
mixins: [normalizeSlotMixin],
|
|
props: {
|
|
rating: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
star: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
focused: {
|
|
// If parent is focused
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hasClear: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
methods: {
|
|
onClick: function onClick(evt) {
|
|
if (!this.disabled && !this.readonly) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
this.$emit('selected', this.star);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var rating = this.rating,
|
|
star = this.star,
|
|
focused = this.focused,
|
|
hasClear = this.hasClear,
|
|
variant = this.variant,
|
|
disabled = this.disabled,
|
|
readonly = this.readonly;
|
|
var minStar = hasClear ? 0 : 1;
|
|
var type = rating >= star ? 'full' : rating >= star - 0.5 ? 'half' : 'empty';
|
|
var slotScope = {
|
|
variant: variant,
|
|
disabled: disabled,
|
|
readonly: readonly
|
|
};
|
|
return h('span', {
|
|
staticClass: 'b-rating-star',
|
|
class: {
|
|
// When not hovered, we use this class to focus the current (or first) star
|
|
focused: focused && rating === star || !toInteger(rating) && star === minStar,
|
|
// We add type classes to we can handle RTL styling
|
|
'b-rating-star-empty': type === 'empty',
|
|
'b-rating-star-half': type === 'half',
|
|
'b-rating-star-full': type === 'full'
|
|
},
|
|
attrs: {
|
|
tabindex: !disabled && !readonly ? '-1' : null
|
|
},
|
|
on: {
|
|
click: this.onClick
|
|
}
|
|
}, [h('span', {
|
|
staticClass: 'b-rating-icon'
|
|
}, [this.normalizeSlot(type, slotScope)])]);
|
|
}
|
|
}); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BFormRating = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_RATING,
|
|
components: {
|
|
BIconStar: BIconStar,
|
|
BIconStarHalf: BIconStarHalf,
|
|
BIconStarFill: BIconStarFill,
|
|
BIconX: BIconX
|
|
},
|
|
mixins: [idMixin, formSizeMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'change'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2({}, omit(props$A, ['required', 'autofocus'])), props$D), {}, {
|
|
value: {
|
|
type: [Number, String],
|
|
default: null
|
|
},
|
|
stars: {
|
|
type: [Number, String],
|
|
default: DEFAULT_STARS,
|
|
validator: function validator(value) {
|
|
return toInteger(value) >= MIN_STARS;
|
|
}
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
color: {
|
|
// CSS color string (overrides variant)
|
|
type: String // default: undefined
|
|
|
|
},
|
|
showValue: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showValueMax: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noBorder: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
inline: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
precision: {
|
|
type: [Number, String],
|
|
default: null
|
|
},
|
|
iconEmpty: {
|
|
type: String,
|
|
default: 'star'
|
|
},
|
|
iconHalf: {
|
|
type: String,
|
|
default: 'star-half'
|
|
},
|
|
iconFull: {
|
|
type: String,
|
|
default: 'star-fill'
|
|
},
|
|
iconClear: {
|
|
type: String,
|
|
default: 'x'
|
|
},
|
|
locale: {
|
|
// Locale for the formatted value (if shown)
|
|
// Defaults to the browser locale. Falls back to `en`
|
|
type: [String, Array] // default: undefined
|
|
|
|
},
|
|
showClear: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_FORM_RATING),
|
|
data: function data() {
|
|
var value = toFloat(this.value, null);
|
|
var stars = computeStars(this.stars);
|
|
return {
|
|
localValue: isNull(value) ? null : clampValue(value, 0, stars),
|
|
hasFocus: false
|
|
};
|
|
},
|
|
computed: {
|
|
computedStars: function computedStars() {
|
|
return computeStars(this.stars);
|
|
},
|
|
computedRating: function computedRating() {
|
|
var value = toFloat(this.localValue, 0);
|
|
var precision = toInteger(this.precision, 3); // We clamp the value between `0` and stars
|
|
|
|
return clampValue(toFloat(value.toFixed(precision)), 0, this.computedStars);
|
|
},
|
|
computedLocale: function computedLocale() {
|
|
var locales = concat(this.locale).filter(identity);
|
|
var nf = new Intl.NumberFormat(locales);
|
|
return nf.resolvedOptions().locale;
|
|
},
|
|
isInteractive: function isInteractive() {
|
|
return !this.disabled && !this.readonly;
|
|
},
|
|
isRTL: function isRTL() {
|
|
return isLocaleRTL(this.computedLocale);
|
|
},
|
|
formattedRating: function formattedRating() {
|
|
var precision = toInteger(this.precision);
|
|
var showValueMax = this.showValueMax;
|
|
var locale = this.computedLocale;
|
|
var formatOptions = {
|
|
notation: 'standard',
|
|
minimumFractionDigits: isNaN(precision) ? 0 : precision,
|
|
maximumFractionDigits: isNaN(precision) ? 3 : precision
|
|
};
|
|
var stars = this.computedStars.toLocaleString(locale);
|
|
var value = this.localValue;
|
|
value = isNull(value) ? showValueMax ? '-' : '' : value.toLocaleString(locale, formatOptions);
|
|
return showValueMax ? "".concat(value, "/").concat(stars) : value;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
var value = toFloat(newVal, null);
|
|
this.localValue = isNull(value) ? null : clampValue(value, 0, this.computedStars);
|
|
}
|
|
},
|
|
localValue: function localValue(newVal, oldVal) {
|
|
if (newVal !== oldVal && newVal !== (this.value || 0)) {
|
|
this.$emit('change', newVal || null);
|
|
}
|
|
},
|
|
disabled: function disabled(newVal) {
|
|
if (newVal) {
|
|
this.hasFocus = false;
|
|
this.blur();
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// --- Public methods ---
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$el);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$el);
|
|
}
|
|
},
|
|
// --- Private methods ---
|
|
onKeydown: function onKeydown(evt) {
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (this.isInteractive && arrayIncludes([CODE_LEFT, CODE_DOWN, CODE_RIGHT, CODE_UP], keyCode)) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
var value = toInteger(this.localValue, 0);
|
|
var min = this.showClear ? 0 : 1;
|
|
var stars = this.computedStars; // In RTL mode, LEFT/RIGHT are swapped
|
|
|
|
var amountRtl = this.isRTL ? -1 : 1;
|
|
|
|
if (keyCode === CODE_LEFT) {
|
|
this.localValue = clampValue(value - amountRtl, min, stars) || null;
|
|
} else if (keyCode === CODE_RIGHT) {
|
|
this.localValue = clampValue(value + amountRtl, min, stars);
|
|
} else if (keyCode === CODE_DOWN) {
|
|
this.localValue = clampValue(value - 1, min, stars) || null;
|
|
} else if (keyCode === CODE_UP) {
|
|
this.localValue = clampValue(value + 1, min, stars);
|
|
}
|
|
}
|
|
},
|
|
onSelected: function onSelected(value) {
|
|
if (this.isInteractive) {
|
|
this.localValue = value;
|
|
}
|
|
},
|
|
onFocus: function onFocus(evt) {
|
|
this.hasFocus = !this.isInteractive ? false : evt.type === 'focus';
|
|
},
|
|
// --- Render methods ---
|
|
renderIcon: function renderIcon(icon) {
|
|
return this.$createElement(BIcon, {
|
|
props: {
|
|
icon: icon,
|
|
variant: this.disabled || this.color ? null : this.variant || null
|
|
}
|
|
});
|
|
},
|
|
iconEmptyFn: function iconEmptyFn() {
|
|
return this.renderIcon(this.iconEmpty);
|
|
},
|
|
iconHalfFn: function iconHalfFn() {
|
|
return this.renderIcon(this.iconHalf);
|
|
},
|
|
iconFullFn: function iconFullFn() {
|
|
return this.renderIcon(this.iconFull);
|
|
},
|
|
iconClearFn: function iconClearFn() {
|
|
return this.$createElement(BIcon, {
|
|
props: {
|
|
icon: this.iconClear
|
|
}
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this = this;
|
|
|
|
var disabled = this.disabled,
|
|
readonly = this.readonly,
|
|
name = this.name,
|
|
form = this.form,
|
|
inline = this.inline,
|
|
variant = this.variant,
|
|
color = this.color,
|
|
noBorder = this.noBorder,
|
|
hasFocus = this.hasFocus,
|
|
computedRating = this.computedRating,
|
|
computedStars = this.computedStars,
|
|
formattedRating = this.formattedRating,
|
|
showClear = this.showClear,
|
|
isRTL = this.isRTL,
|
|
isInteractive = this.isInteractive,
|
|
$scopedSlots = this.$scopedSlots;
|
|
var $content = [];
|
|
|
|
if (showClear && !disabled && !readonly) {
|
|
var $icon = h('span', {
|
|
staticClass: 'b-rating-icon'
|
|
}, [($scopedSlots['icon-clear'] || this.iconClearFn)()]);
|
|
$content.push(h('span', {
|
|
staticClass: 'b-rating-star b-rating-star-clear flex-grow-1',
|
|
class: {
|
|
focused: hasFocus && computedRating === 0
|
|
},
|
|
attrs: {
|
|
tabindex: isInteractive ? '-1' : null
|
|
},
|
|
on: {
|
|
click: function click() {
|
|
return _this.onSelected(null);
|
|
}
|
|
},
|
|
key: 'clear'
|
|
}, [$icon]));
|
|
}
|
|
|
|
for (var index = 0; index < computedStars; index++) {
|
|
var value = index + 1;
|
|
$content.push(h(BVFormRatingStar, {
|
|
staticClass: 'flex-grow-1',
|
|
style: color && !disabled ? {
|
|
color: color
|
|
} : {},
|
|
props: {
|
|
rating: computedRating,
|
|
star: value,
|
|
variant: disabled ? null : variant || null,
|
|
disabled: disabled,
|
|
readonly: readonly,
|
|
focused: hasFocus,
|
|
hasClear: showClear
|
|
},
|
|
on: {
|
|
selected: this.onSelected
|
|
},
|
|
scopedSlots: {
|
|
empty: $scopedSlots['icon-empty'] || this.iconEmptyFn,
|
|
half: $scopedSlots['icon-half'] || this.iconHalfFn,
|
|
full: $scopedSlots['icon-full'] || this.iconFullFn
|
|
},
|
|
key: index
|
|
}));
|
|
}
|
|
|
|
if (name) {
|
|
$content.push(h('input', {
|
|
attrs: {
|
|
type: 'hidden',
|
|
value: isNull(this.localValue) ? '' : computedRating,
|
|
name: name,
|
|
form: form || null
|
|
},
|
|
key: 'hidden'
|
|
}));
|
|
}
|
|
|
|
if (this.showValue) {
|
|
$content.push(h('b', {
|
|
staticClass: 'b-rating-value flex-grow-1',
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
},
|
|
key: 'value'
|
|
}, toString$1(formattedRating)));
|
|
}
|
|
|
|
return h('output', {
|
|
staticClass: 'b-rating form-control align-items-center',
|
|
class: [{
|
|
'd-inline-flex': inline,
|
|
'd-flex': !inline,
|
|
'border-0': noBorder,
|
|
disabled: disabled,
|
|
readonly: !disabled && readonly
|
|
}, this.sizeFormClass],
|
|
attrs: {
|
|
id: this.safeId(),
|
|
dir: isRTL ? 'rtl' : 'ltr',
|
|
tabindex: disabled ? null : '0',
|
|
disabled: disabled,
|
|
role: 'slider',
|
|
'aria-disabled': disabled ? 'true' : null,
|
|
'aria-readonly': !disabled && readonly ? 'true' : null,
|
|
'aria-live': 'off',
|
|
'aria-valuemin': showClear ? '0' : '1',
|
|
'aria-valuemax': toString$1(computedStars),
|
|
'aria-valuenow': computedRating ? toString$1(computedRating) : null
|
|
},
|
|
on: {
|
|
keydown: this.onKeydown,
|
|
focus: this.onFocus,
|
|
blur: this.onFocus
|
|
}
|
|
}, $content);
|
|
}
|
|
});
|
|
|
|
var FormRatingPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormRating: BFormRating,
|
|
BRating: BFormRating
|
|
}
|
|
});
|
|
|
|
var optionsMixin = {
|
|
mixins: [formOptionsMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$v), {}, {
|
|
labelField: {
|
|
type: String,
|
|
default: 'label'
|
|
},
|
|
optionsField: {
|
|
type: String,
|
|
default: 'options'
|
|
}
|
|
}), 'formOptions'),
|
|
methods: {
|
|
normalizeOption: function normalizeOption(option) {
|
|
var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
|
|
// When the option is an object, normalize it
|
|
if (isPlainObject(option)) {
|
|
var value = get(option, this.valueField);
|
|
var text = get(option, this.textField);
|
|
var options = get(option, this.optionsField, null); // When it has options, create an `<optgroup>` object
|
|
|
|
if (!isNull(options)) {
|
|
return {
|
|
label: String(get(option, this.labelField) || text),
|
|
options: this.normalizeOptions(options)
|
|
};
|
|
} // Otherwise create an `<option>` object
|
|
|
|
|
|
return {
|
|
value: isUndefined(value) ? key || text : value,
|
|
text: String(isUndefined(text) ? key : text),
|
|
html: get(option, this.htmlField),
|
|
disabled: Boolean(get(option, this.disabledField))
|
|
};
|
|
} // Otherwise create an `<option>` object from the given value
|
|
|
|
|
|
return {
|
|
value: key || option,
|
|
text: String(option),
|
|
disabled: false
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$K = makePropsConfigurable({
|
|
value: {
|
|
// type: [String, Number, Boolean, Object],
|
|
required: true
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_FORM_SELECT_OPTION); // @vue/component
|
|
|
|
var BFormSelectOption = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_SELECT_OPTION,
|
|
functional: true,
|
|
props: props$K,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var value = props.value,
|
|
disabled = props.disabled;
|
|
return h('option', a(data, {
|
|
attrs: {
|
|
disabled: disabled
|
|
},
|
|
domProps: {
|
|
value: value
|
|
}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var BFormSelectOptionGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_SELECT_OPTION_GROUP,
|
|
mixins: [normalizeSlotMixin, formOptionsMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, props$v), {}, {
|
|
label: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
}), NAME_FORM_SELECT_OPTION_GROUP),
|
|
render: function render(h) {
|
|
var $options = this.formOptions.map(function (option, index) {
|
|
var value = option.value,
|
|
text = option.text,
|
|
html = option.html,
|
|
disabled = option.disabled;
|
|
return h(BFormSelectOption, {
|
|
attrs: {
|
|
value: value,
|
|
disabled: disabled
|
|
},
|
|
domProps: htmlOrText(html, text),
|
|
key: "option_".concat(index)
|
|
});
|
|
});
|
|
return h('optgroup', {
|
|
attrs: {
|
|
label: this.label
|
|
}
|
|
}, [this.normalizeSlot(SLOT_NAME_FIRST), $options, this.normalizeSlot()]);
|
|
}
|
|
});
|
|
|
|
var BFormSelect = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_SELECT,
|
|
mixins: [idMixin, normalizeSlotMixin, formControlMixin, formSizeMixin, formStateMixin, formCustomMixin, optionsMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$B), props$D), props$E), {}, {
|
|
value: {// type: [Object, Array, String, Number, Boolean],
|
|
// default: undefined
|
|
},
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
selectSize: {
|
|
// Browsers default size to 0, which shows 4 rows in most browsers in multiple mode
|
|
// Size of 1 can bork out Firefox
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
ariaInvalid: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
}
|
|
}), NAME_FORM_SELECT),
|
|
data: function data() {
|
|
return {
|
|
localValue: this.value
|
|
};
|
|
},
|
|
computed: {
|
|
computedSelectSize: function computedSelectSize() {
|
|
// Custom selects with a size of zero causes the arrows to be hidden,
|
|
// so dont render the size attribute in this case
|
|
return !this.plain && this.selectSize === 0 ? null : this.selectSize;
|
|
},
|
|
inputClass: function inputClass() {
|
|
return [this.plain ? 'form-control' : 'custom-select', this.size && this.plain ? "form-control-".concat(this.size) : null, this.size && !this.plain ? "custom-select-".concat(this.size) : null, this.stateClass];
|
|
},
|
|
computedAriaInvalid: function computedAriaInvalid() {
|
|
if (this.ariaInvalid === true || this.ariaInvalid === 'true') {
|
|
return 'true';
|
|
}
|
|
|
|
return this.stateClass === 'is-invalid' ? 'true' : null;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal) {
|
|
this.localValue = newVal;
|
|
},
|
|
localValue: function localValue() {
|
|
this.$emit('input', this.localValue);
|
|
}
|
|
},
|
|
methods: {
|
|
focus: function focus() {
|
|
attemptFocus(this.$refs.input);
|
|
},
|
|
blur: function blur() {
|
|
attemptBlur(this.$refs.input);
|
|
},
|
|
onChange: function onChange(evt) {
|
|
var _this = this;
|
|
|
|
var target = evt.target;
|
|
var selectedVal = from(target.options).filter(function (o) {
|
|
return o.selected;
|
|
}).map(function (o) {
|
|
return '_value' in o ? o._value : o.value;
|
|
});
|
|
this.localValue = target.multiple ? selectedVal : selectedVal[0];
|
|
this.$nextTick(function () {
|
|
_this.$emit('change', _this.localValue);
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var name = this.name,
|
|
disabled = this.disabled,
|
|
required = this.required,
|
|
size = this.computedSelectSize,
|
|
value = this.localValue;
|
|
var $options = this.formOptions.map(function (option, index) {
|
|
var value = option.value,
|
|
label = option.label,
|
|
options = option.options,
|
|
disabled = option.disabled;
|
|
var key = "option_".concat(index);
|
|
return isArray(options) ? h(BFormSelectOptionGroup, {
|
|
props: {
|
|
label: label,
|
|
options: options
|
|
},
|
|
key: key
|
|
}) : h(BFormSelectOption, {
|
|
props: {
|
|
value: value,
|
|
disabled: disabled
|
|
},
|
|
domProps: htmlOrText(option.html, option.text),
|
|
key: key
|
|
});
|
|
});
|
|
return h('select', {
|
|
class: this.inputClass,
|
|
attrs: {
|
|
id: this.safeId(),
|
|
name: name,
|
|
form: this.form || null,
|
|
multiple: this.multiple || null,
|
|
size: size,
|
|
disabled: disabled,
|
|
required: required,
|
|
'aria-required': required ? 'true' : null,
|
|
'aria-invalid': this.computedAriaInvalid
|
|
},
|
|
on: {
|
|
change: this.onChange
|
|
},
|
|
directives: [{
|
|
name: 'model',
|
|
value: value
|
|
}],
|
|
ref: 'input'
|
|
}, [this.normalizeSlot(SLOT_NAME_FIRST), $options, this.normalizeSlot()]);
|
|
}
|
|
});
|
|
|
|
var FormSelectPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormSelect: BFormSelect,
|
|
BFormSelectOption: BFormSelectOption,
|
|
BFormSelectOptionGroup: BFormSelectOptionGroup,
|
|
BSelect: BFormSelect,
|
|
BSelectOption: BFormSelectOption,
|
|
BSelectOptionGroup: BFormSelectOptionGroup
|
|
}
|
|
});
|
|
|
|
// Default for spin button range and step
|
|
|
|
var DEFAULT_MIN = 1;
|
|
var DEFAULT_MAX = 100;
|
|
var DEFAULT_STEP = 1; // Delay before auto-repeat in ms
|
|
|
|
var DEFAULT_REPEAT_DELAY = 500; // Repeat interval in ms
|
|
|
|
var DEFAULT_REPEAT_INTERVAL = 100; // Repeat rate increased after number of repeats
|
|
|
|
var DEFAULT_REPEAT_THRESHOLD = 10; // Repeat speed multiplier (step multiplier, must be an integer)
|
|
|
|
var DEFAULT_REPEAT_MULTIPLIER = 4;
|
|
var KEY_CODES = [CODE_UP, CODE_DOWN, CODE_HOME, CODE_END, CODE_PAGEUP, CODE_PAGEDOWN]; // --- Props ---
|
|
|
|
var props$L = makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, omit(props$A, ['required', 'autofocus'])), props$D), props$E), {}, {
|
|
value: {
|
|
// Should this really be String, to match native number inputs?
|
|
type: Number,
|
|
default: null
|
|
},
|
|
min: {
|
|
type: [Number, String],
|
|
default: DEFAULT_MIN
|
|
},
|
|
max: {
|
|
type: [Number, String],
|
|
default: DEFAULT_MAX
|
|
},
|
|
step: {
|
|
type: [Number, String],
|
|
default: DEFAULT_STEP
|
|
},
|
|
wrap: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
formatterFn: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
placeholder: {
|
|
type: String // default: null
|
|
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
inline: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
vertical: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaLabel: {
|
|
type: String // default: null
|
|
|
|
},
|
|
ariaControls: {
|
|
type: String // default: null
|
|
|
|
},
|
|
labelDecrement: {
|
|
type: String,
|
|
default: 'Decrement'
|
|
},
|
|
labelIncrement: {
|
|
type: String,
|
|
default: 'Increment'
|
|
},
|
|
locale: {
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
repeatDelay: {
|
|
type: [Number, String],
|
|
default: DEFAULT_REPEAT_DELAY
|
|
},
|
|
repeatInterval: {
|
|
type: [Number, String],
|
|
default: DEFAULT_REPEAT_INTERVAL
|
|
},
|
|
repeatThreshold: {
|
|
type: [Number, String],
|
|
default: DEFAULT_REPEAT_THRESHOLD
|
|
},
|
|
repeatStepMultiplier: {
|
|
type: [Number, String],
|
|
default: DEFAULT_REPEAT_MULTIPLIER
|
|
}
|
|
}), NAME_FORM_SPINBUTTON); // --- BFormSpinbutton ---
|
|
// @vue/component
|
|
|
|
var BFormSpinbutton = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_SPINBUTTON,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, idMixin, formSizeMixin, formStateMixin, normalizeSlotMixin],
|
|
inheritAttrs: false,
|
|
props: props$L,
|
|
data: function data() {
|
|
return {
|
|
localValue: toFloat(this.value, null),
|
|
hasFocus: false
|
|
};
|
|
},
|
|
computed: {
|
|
spinId: function spinId() {
|
|
return this.safeId();
|
|
},
|
|
computedInline: function computedInline() {
|
|
return this.inline && !this.vertical;
|
|
},
|
|
computedReadonly: function computedReadonly() {
|
|
return this.readonly && !this.disabled;
|
|
},
|
|
computedRequired: function computedRequired() {
|
|
return this.required && !this.computedReadonly && !this.disabled;
|
|
},
|
|
computedStep: function computedStep() {
|
|
return toFloat(this.step, DEFAULT_STEP);
|
|
},
|
|
computedMin: function computedMin() {
|
|
return toFloat(this.min, DEFAULT_MIN);
|
|
},
|
|
computedMax: function computedMax() {
|
|
// We round down to the nearest maximum step value
|
|
var max = toFloat(this.max, DEFAULT_MAX);
|
|
var step = this.computedStep;
|
|
var min = this.computedMin;
|
|
return mathFloor((max - min) / step) * step + min;
|
|
},
|
|
computedDelay: function computedDelay() {
|
|
var delay = toInteger(this.repeatDelay, 0);
|
|
return delay > 0 ? delay : DEFAULT_REPEAT_DELAY;
|
|
},
|
|
computedInterval: function computedInterval() {
|
|
var interval = toInteger(this.repeatInterval, 0);
|
|
return interval > 0 ? interval : DEFAULT_REPEAT_INTERVAL;
|
|
},
|
|
computedThreshold: function computedThreshold() {
|
|
return mathMax(toInteger(this.repeatThreshold, DEFAULT_REPEAT_THRESHOLD), 1);
|
|
},
|
|
computedStepMultiplier: function computedStepMultiplier() {
|
|
return mathMax(toInteger(this.repeatStepMultiplier, DEFAULT_REPEAT_MULTIPLIER), 1);
|
|
},
|
|
computedPrecision: function computedPrecision() {
|
|
// Quick and dirty way to get the number of decimals
|
|
var step = this.computedStep;
|
|
return mathFloor(step) === step ? 0 : (step.toString().split('.')[1] || '').length;
|
|
},
|
|
computedMultiplier: function computedMultiplier() {
|
|
return mathPow(10, this.computedPrecision || 0);
|
|
},
|
|
valueAsFixed: function valueAsFixed() {
|
|
var value = this.localValue;
|
|
return isNull(value) ? '' : value.toFixed(this.computedPrecision);
|
|
},
|
|
computedLocale: function computedLocale() {
|
|
var locales = concat(this.locale).filter(identity);
|
|
var nf = new Intl.NumberFormat(locales);
|
|
return nf.resolvedOptions().locale;
|
|
},
|
|
computedRTL: function computedRTL() {
|
|
return isLocaleRTL(this.computedLocale);
|
|
},
|
|
defaultFormatter: function defaultFormatter() {
|
|
// Returns and `Intl.NumberFormat` formatter method reference
|
|
var precision = this.computedPrecision;
|
|
var nf = new Intl.NumberFormat(this.computedLocale, {
|
|
style: 'decimal',
|
|
useGrouping: false,
|
|
minimumIntegerDigits: 1,
|
|
minimumFractionDigits: precision,
|
|
maximumFractionDigits: precision,
|
|
notation: 'standard'
|
|
}); // Return the format method reference
|
|
|
|
return nf.format;
|
|
},
|
|
computedFormatter: function computedFormatter() {
|
|
var formatterFn = this.formatterFn;
|
|
var result = null;
|
|
|
|
try {
|
|
result = formatterFn();
|
|
} catch (_unused) {}
|
|
|
|
return isUndefined(result) ? this.defaultFormatter : formatterFn;
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
role: 'group',
|
|
lang: this.computedLocale,
|
|
tabindex: this.disabled ? null : '-1',
|
|
title: this.ariaLabel
|
|
});
|
|
},
|
|
computedSpinAttrs: function computedSpinAttrs() {
|
|
var spinId = this.spinId,
|
|
value = this.localValue,
|
|
required = this.computedRequired,
|
|
disabled = this.disabled,
|
|
state = this.state,
|
|
computedFormatter = this.computedFormatter;
|
|
var hasValue = !isNull(value);
|
|
return _objectSpread2(_objectSpread2({
|
|
dir: this.computedRTL ? 'rtl' : 'ltr'
|
|
}, this.bvAttrs), {}, {
|
|
id: spinId,
|
|
role: 'spinbutton',
|
|
tabindex: disabled ? null : '0',
|
|
'aria-live': 'off',
|
|
'aria-label': this.ariaLabel || null,
|
|
'aria-controls': this.ariaControls || null,
|
|
// TODO: May want to check if the value is in range
|
|
'aria-invalid': state === false || !hasValue && required ? 'true' : null,
|
|
'aria-required': required ? 'true' : null,
|
|
// These attrs are required for role spinbutton
|
|
'aria-valuemin': toString$1(this.computedMin),
|
|
'aria-valuemax': toString$1(this.computedMax),
|
|
// These should be `null` if the value is out of range
|
|
// They must also be non-existent attrs if the value is out of range or `null`
|
|
'aria-valuenow': hasValue ? value : null,
|
|
'aria-valuetext': hasValue ? computedFormatter(value) : null
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(_value) {
|
|
this.localValue = toFloat(_value, null);
|
|
},
|
|
localValue: function localValue(value) {
|
|
this.$emit('input', value);
|
|
},
|
|
disabled: function disabled(_disabled) {
|
|
if (_disabled) {
|
|
this.clearRepeat();
|
|
}
|
|
},
|
|
readonly: function readonly(_readonly) {
|
|
if (_readonly) {
|
|
this.clearRepeat();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create non reactive properties
|
|
this.$_autoDelayTimer = null;
|
|
this.$_autoRepeatTimer = null;
|
|
this.$_keyIsDown = false;
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.clearRepeat();
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
this.clearRepeat();
|
|
},
|
|
methods: {
|
|
// --- Public methods ---
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$refs.spinner);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$refs.spinner);
|
|
}
|
|
},
|
|
// --- Private methods ---
|
|
emitChange: function emitChange() {
|
|
this.$emit('change', this.localValue);
|
|
},
|
|
stepValue: function stepValue(direction) {
|
|
// Sets a new incremented or decremented value, supporting optional wrapping
|
|
// Direction is either +1 or -1 (or a multiple thereof)
|
|
var value = this.localValue;
|
|
|
|
if (!this.disabled && !isNull(value)) {
|
|
var step = this.computedStep * direction;
|
|
var min = this.computedMin;
|
|
var max = this.computedMax;
|
|
var multiplier = this.computedMultiplier;
|
|
var wrap = this.wrap; // We ensure that the value steps like a native input
|
|
|
|
value = mathRound((value - min) / step) * step + min + step; // We ensure that precision is maintained (decimals)
|
|
|
|
value = mathRound(value * multiplier) / multiplier; // Handle if wrapping is enabled
|
|
|
|
this.localValue = value > max ? wrap ? min : max : value < min ? wrap ? max : min : value;
|
|
}
|
|
},
|
|
onFocusBlur: function onFocusBlur(evt) {
|
|
if (!this.disabled) {
|
|
this.hasFocus = evt.type === 'focus';
|
|
} else {
|
|
this.hasFocus = false;
|
|
}
|
|
},
|
|
stepUp: function stepUp() {
|
|
var multiplier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
|
|
var value = this.localValue;
|
|
|
|
if (isNull(value)) {
|
|
this.localValue = this.computedMin;
|
|
} else {
|
|
this.stepValue(+1 * multiplier);
|
|
}
|
|
},
|
|
stepDown: function stepDown() {
|
|
var multiplier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
|
|
var value = this.localValue;
|
|
|
|
if (isNull(value)) {
|
|
this.localValue = this.wrap ? this.computedMax : this.computedMin;
|
|
} else {
|
|
this.stepValue(-1 * multiplier);
|
|
}
|
|
},
|
|
onKeydown: function onKeydown(evt) {
|
|
var keyCode = evt.keyCode,
|
|
altKey = evt.altKey,
|
|
ctrlKey = evt.ctrlKey,
|
|
metaKey = evt.metaKey;
|
|
/* istanbul ignore if */
|
|
|
|
if (this.disabled || this.readonly || altKey || ctrlKey || metaKey) {
|
|
return;
|
|
}
|
|
|
|
if (arrayIncludes(KEY_CODES, keyCode)) {
|
|
// https://w3c.github.io/aria-practices/#spinbutton
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
/* istanbul ignore if */
|
|
|
|
if (this.$_keyIsDown) {
|
|
// Keypress is already in progress
|
|
return;
|
|
}
|
|
|
|
this.resetTimers();
|
|
|
|
if (arrayIncludes([CODE_UP, CODE_DOWN], keyCode)) {
|
|
// The following use the custom auto-repeat handling
|
|
this.$_keyIsDown = true;
|
|
|
|
if (keyCode === CODE_UP) {
|
|
this.handleStepRepeat(evt, this.stepUp);
|
|
} else if (keyCode === CODE_DOWN) {
|
|
this.handleStepRepeat(evt, this.stepDown);
|
|
}
|
|
} else {
|
|
// These use native OS key repeating
|
|
if (keyCode === CODE_PAGEUP) {
|
|
this.stepUp(this.computedStepMultiplier);
|
|
} else if (keyCode === CODE_PAGEDOWN) {
|
|
this.stepDown(this.computedStepMultiplier);
|
|
} else if (keyCode === CODE_HOME) {
|
|
this.localValue = this.computedMin;
|
|
} else if (keyCode === CODE_END) {
|
|
this.localValue = this.computedMax;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
onKeyup: function onKeyup(evt) {
|
|
// Emit a change event when the keyup happens
|
|
var keyCode = evt.keyCode,
|
|
altKey = evt.altKey,
|
|
ctrlKey = evt.ctrlKey,
|
|
metaKey = evt.metaKey;
|
|
/* istanbul ignore if */
|
|
|
|
if (this.disabled || this.readonly || altKey || ctrlKey || metaKey) {
|
|
return;
|
|
}
|
|
|
|
if (arrayIncludes(KEY_CODES, keyCode)) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
this.resetTimers();
|
|
this.$_keyIsDown = false;
|
|
this.emitChange();
|
|
}
|
|
},
|
|
handleStepRepeat: function handleStepRepeat(evt, stepper) {
|
|
var _this = this;
|
|
|
|
var _ref = evt || {},
|
|
type = _ref.type,
|
|
button = _ref.button;
|
|
|
|
if (!this.disabled && !this.readonly) {
|
|
/* istanbul ignore if */
|
|
if (type === 'mousedown' && button) {
|
|
// We only respond to left (main === 0) button clicks
|
|
return;
|
|
}
|
|
|
|
this.resetTimers(); // Step the counter initially
|
|
|
|
stepper(1);
|
|
var threshold = this.computedThreshold;
|
|
var multiplier = this.computedStepMultiplier;
|
|
var delay = this.computedDelay;
|
|
var interval = this.computedInterval; // Initiate the delay/repeat interval
|
|
|
|
this.$_autoDelayTimer = setTimeout(function () {
|
|
var count = 0;
|
|
_this.$_autoRepeatTimer = setInterval(function () {
|
|
// After N initial repeats, we increase the incrementing step amount
|
|
// We do this to minimize screen reader announcements of the value
|
|
// (values are announced every change, which can be chatty for SR users)
|
|
// And to make it easer to select a value when the range is large
|
|
stepper(count < threshold ? 1 : multiplier);
|
|
count++;
|
|
}, interval);
|
|
}, delay);
|
|
}
|
|
},
|
|
onMouseup: function onMouseup(evt) {
|
|
// `<body>` listener, only enabled when mousedown starts
|
|
var _ref2 = evt || {},
|
|
type = _ref2.type,
|
|
button = _ref2.button;
|
|
/* istanbul ignore if */
|
|
|
|
|
|
if (type === 'mouseup' && button) {
|
|
// Ignore non left button (main === 0) mouse button click
|
|
return;
|
|
}
|
|
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
this.resetTimers();
|
|
this.setMouseup(false); // Trigger the change event
|
|
|
|
this.emitChange();
|
|
},
|
|
setMouseup: function setMouseup(on) {
|
|
// Enable or disabled the body mouseup/touchend handlers
|
|
// Use try/catch to handle case when called server side
|
|
try {
|
|
eventOnOff(on, document.body, 'mouseup', this.onMouseup, false);
|
|
eventOnOff(on, document.body, 'touchend', this.onMouseup, false);
|
|
} catch (_unused2) {}
|
|
},
|
|
resetTimers: function resetTimers() {
|
|
clearTimeout(this.$_autoDelayTimer);
|
|
clearInterval(this.$_autoRepeatTimer);
|
|
this.$_autoDelayTimer = null;
|
|
this.$_autoRepeatTimer = null;
|
|
},
|
|
clearRepeat: function clearRepeat() {
|
|
this.resetTimers();
|
|
this.setMouseup(false);
|
|
this.$_keyIsDown = false;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this2 = this;
|
|
|
|
var spinId = this.spinId,
|
|
value = this.localValue,
|
|
inline = this.computedInline,
|
|
readonly = this.computedReadonly,
|
|
vertical = this.vertical,
|
|
disabled = this.disabled,
|
|
computedFormatter = this.computedFormatter;
|
|
var hasValue = !isNull(value);
|
|
|
|
var makeButton = function makeButton(stepper, label, IconCmp, keyRef, shortcut, btnDisabled, slotName) {
|
|
var $icon = h(IconCmp, {
|
|
props: {
|
|
scale: _this2.hasFocus ? 1.5 : 1.25
|
|
},
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
});
|
|
var scope = {
|
|
hasFocus: _this2.hasFocus
|
|
};
|
|
|
|
var handler = function handler(evt) {
|
|
if (!disabled && !readonly) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
|
|
_this2.setMouseup(true); // Since we `preventDefault()`, we must manually focus the button
|
|
|
|
|
|
attemptFocus(evt.currentTarget);
|
|
|
|
_this2.handleStepRepeat(evt, stepper);
|
|
}
|
|
};
|
|
|
|
return h('button', {
|
|
key: keyRef || null,
|
|
ref: keyRef,
|
|
staticClass: 'btn btn-sm border-0 rounded-0',
|
|
class: {
|
|
'py-0': !vertical
|
|
},
|
|
attrs: {
|
|
tabindex: '-1',
|
|
type: 'button',
|
|
disabled: disabled || readonly || btnDisabled,
|
|
'aria-disabled': disabled || readonly || btnDisabled ? 'true' : null,
|
|
'aria-controls': spinId,
|
|
'aria-label': label || null,
|
|
'aria-keyshortcuts': shortcut || null
|
|
},
|
|
on: {
|
|
mousedown: handler,
|
|
touchstart: handler
|
|
}
|
|
}, [h('div', [_this2.normalizeSlot(slotName, scope) || $icon])]);
|
|
}; // TODO: Add button disabled state when `wrap` is `false` and at value max/min
|
|
|
|
|
|
var $increment = makeButton(this.stepUp, this.labelIncrement, BIconPlus, 'inc', 'ArrowUp', false, 'increment');
|
|
var $decrement = makeButton(this.stepDown, this.labelDecrement, BIconDash, 'dec', 'ArrowDown', false, 'decrement');
|
|
var $hidden = h();
|
|
|
|
if (this.name && !disabled) {
|
|
$hidden = h('input', {
|
|
key: 'hidden',
|
|
attrs: {
|
|
type: 'hidden',
|
|
name: this.name,
|
|
form: this.form || null,
|
|
// TODO: Should this be set to '' if value is out of range?
|
|
value: this.valueAsFixed
|
|
}
|
|
});
|
|
}
|
|
|
|
var $spin = h( // We use 'output' element to make this accept a `<label for="id">` (Except IE)
|
|
'output', {
|
|
ref: 'spinner',
|
|
key: 'output',
|
|
staticClass: 'flex-grow-1',
|
|
class: {
|
|
'd-flex': vertical,
|
|
'align-self-center': !vertical,
|
|
'align-items-center': vertical,
|
|
'border-top': vertical,
|
|
'border-bottom': vertical,
|
|
'border-left': !vertical,
|
|
'border-right': !vertical
|
|
},
|
|
attrs: this.computedSpinAttrs
|
|
}, [h('bdi', hasValue ? computedFormatter(value) : this.placeholder || '')]);
|
|
return h('div', {
|
|
staticClass: 'b-form-spinbutton form-control',
|
|
class: [{
|
|
disabled: disabled,
|
|
readonly: readonly,
|
|
focus: this.hasFocus,
|
|
'd-inline-flex': inline || vertical,
|
|
'd-flex': !inline && !vertical,
|
|
'align-items-stretch': !vertical,
|
|
'flex-column': vertical
|
|
}, this.sizeFormClass, this.stateClass],
|
|
attrs: this.computedAttrs,
|
|
on: {
|
|
keydown: this.onKeydown,
|
|
keyup: this.onKeyup,
|
|
// We use capture phase (`!` prefix) since focus and blur do not bubble
|
|
'!focus': this.onFocusBlur,
|
|
'!blur': this.onFocusBlur
|
|
}
|
|
}, vertical ? [$increment, $hidden, $spin, $decrement] : [$decrement, $hidden, $spin, $increment]);
|
|
}
|
|
});
|
|
|
|
var FormSpinbuttonPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormSpinbutton: BFormSpinbutton,
|
|
BSpinbutton: BFormSpinbutton
|
|
}
|
|
});
|
|
|
|
var BFormTag = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_TAG,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
props: makePropsConfigurable({
|
|
variant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
title: {
|
|
type: String // default: null
|
|
|
|
},
|
|
pill: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
removeLabel: {
|
|
type: String,
|
|
default: 'Remove tag'
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'span'
|
|
}
|
|
}, NAME_FORM_TAG),
|
|
methods: {
|
|
onDelete: function onDelete(evt) {
|
|
var type = evt.type,
|
|
keyCode = evt.keyCode;
|
|
|
|
if (!this.disabled && (type === 'click' || type === 'keydown' && keyCode === CODE_DELETE)) {
|
|
this.$emit('remove');
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var tagId = this.safeId();
|
|
var tagLabelId = this.safeId('_taglabel_');
|
|
var $remove = h();
|
|
|
|
if (!this.disabled) {
|
|
$remove = h(BButtonClose, {
|
|
staticClass: 'b-form-tag-remove',
|
|
props: {
|
|
ariaLabel: this.removeLabel
|
|
},
|
|
attrs: {
|
|
'aria-controls': tagId,
|
|
'aria-describedby': tagLabelId,
|
|
'aria-keyshortcuts': 'Delete'
|
|
},
|
|
on: {
|
|
click: this.onDelete,
|
|
keydown: this.onDelete
|
|
}
|
|
});
|
|
}
|
|
|
|
var $tag = h('span', {
|
|
staticClass: 'b-form-tag-content flex-grow-1 text-truncate',
|
|
attrs: {
|
|
id: tagLabelId
|
|
}
|
|
}, this.normalizeSlot() || this.title || [h()]);
|
|
return h(BBadge, {
|
|
staticClass: 'b-form-tag d-inline-flex align-items-baseline mw-100',
|
|
class: {
|
|
disabled: this.disabled
|
|
},
|
|
attrs: {
|
|
id: tagId,
|
|
title: this.title || null,
|
|
'aria-labelledby': tagLabelId
|
|
},
|
|
props: {
|
|
tag: this.tag,
|
|
variant: this.variant,
|
|
pill: this.pill
|
|
}
|
|
}, [$tag, $remove]);
|
|
}
|
|
});
|
|
|
|
// Supported input types (for built in input)
|
|
|
|
var TYPES$2 = ['text', 'email', 'tel', 'url', 'number']; // --- Utility methods ---
|
|
// Escape special chars in string and replace
|
|
// contiguous spaces with a whitespace match
|
|
|
|
var escapeRegExpChars = function escapeRegExpChars(str) {
|
|
return escapeRegExp(str).replace(RX_SPACES, '\\s');
|
|
}; // Remove leading/trailing spaces from array of tags and remove duplicates
|
|
|
|
|
|
var cleanTags = function cleanTags(tags) {
|
|
return concat(tags).map(function (tag) {
|
|
return trim(toString$1(tag));
|
|
}).filter(function (tag, index, arr) {
|
|
return tag.length > 0 && arr.indexOf(tag) === index;
|
|
});
|
|
}; // Processes an input/change event, normalizing string or event argument
|
|
|
|
|
|
var processEventValue = function processEventValue(evt) {
|
|
return isString(evt) ? evt : isEvent(evt) ? evt.target.value || '' : '';
|
|
}; // Returns a fresh empty `tagsState` object
|
|
|
|
|
|
var cleanTagsState = function cleanTagsState() {
|
|
return {
|
|
all: [],
|
|
valid: [],
|
|
invalid: [],
|
|
duplicate: []
|
|
};
|
|
}; // --- Main component ---
|
|
// @vue/component
|
|
|
|
|
|
var BFormTags = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_TAGS,
|
|
mixins: [idMixin, formControlMixin, formSizeMixin, formStateMixin, normalizeSlotMixin],
|
|
model: {
|
|
// Even though this is the default that Vue assumes, we need
|
|
// to add it for the docs to reflect that this is the model
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$D), props$E), {}, {
|
|
value: {
|
|
// The v-model prop
|
|
type: Array,
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: 'Add tag...'
|
|
},
|
|
inputId: {
|
|
type: String // default: null
|
|
|
|
},
|
|
inputType: {
|
|
type: String,
|
|
default: 'text',
|
|
validator: function validator(value) {
|
|
return arrayIncludes(TYPES$2, value);
|
|
}
|
|
},
|
|
inputClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
inputAttrs: {
|
|
// Additional attributes to add to the input element
|
|
type: Object,
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
},
|
|
addButtonText: {
|
|
type: String,
|
|
default: 'Add'
|
|
},
|
|
addButtonVariant: {
|
|
type: String,
|
|
default: 'outline-secondary'
|
|
},
|
|
tagVariant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
tagClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
tagPills: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tagRemoveLabel: {
|
|
type: String,
|
|
default: 'Remove tag'
|
|
},
|
|
tagRemovedLabel: {
|
|
type: String,
|
|
default: 'Tag removed'
|
|
},
|
|
tagValidator: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
duplicateTagText: {
|
|
type: String,
|
|
default: 'Duplicate tag(s)'
|
|
},
|
|
invalidTagText: {
|
|
type: String,
|
|
default: 'Invalid tag(s)'
|
|
},
|
|
limitTagsText: {
|
|
type: String,
|
|
default: 'Tag limit reached'
|
|
},
|
|
limit: {
|
|
type: Number // default: null
|
|
|
|
},
|
|
separator: {
|
|
// Character (or characters) that trigger adding tags
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
removeOnDelete: {
|
|
// Enable deleting last tag in list when CODE_BACKSPACE is
|
|
// pressed and input is empty
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
addOnChange: {
|
|
// Enable change event triggering tag addition
|
|
// Handy if using <select> as the input
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noAddOnEnter: {
|
|
// Disable ENTER key from triggering tag addition
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noOuterFocus: {
|
|
// Disable the focus ring on the root element
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ignoreInputFocusSelector: {
|
|
// Disable the input focus behavior when clicking
|
|
// on element matching the selector (or selectors)
|
|
type: [Array, String],
|
|
default: function _default() {
|
|
return ['.b-form-tag', 'button', 'input', 'select'];
|
|
}
|
|
}
|
|
}), NAME_FORM_TAGS),
|
|
data: function data() {
|
|
return {
|
|
hasFocus: false,
|
|
newTag: '',
|
|
tags: [],
|
|
// Tags that were removed
|
|
removedTags: [],
|
|
// Populated when tags are parsed
|
|
tagsState: cleanTagsState()
|
|
};
|
|
},
|
|
computed: {
|
|
computedInputId: function computedInputId() {
|
|
return this.inputId || this.safeId('__input__');
|
|
},
|
|
computedInputType: function computedInputType() {
|
|
// We only allow certain types
|
|
return arrayIncludes(TYPES$2, this.inputType) ? this.inputType : 'text';
|
|
},
|
|
computedInputAttrs: function computedInputAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.inputAttrs), {}, {
|
|
// Must have attributes
|
|
id: this.computedInputId,
|
|
value: this.newTag,
|
|
disabled: this.disabled || null,
|
|
form: this.form || null
|
|
});
|
|
},
|
|
computedInputHandlers: function computedInputHandlers() {
|
|
return {
|
|
input: this.onInputInput,
|
|
change: this.onInputChange,
|
|
keydown: this.onInputKeydown
|
|
};
|
|
},
|
|
computedSeparator: function computedSeparator() {
|
|
// Merge the array into a string
|
|
return concat(this.separator).filter(isString).filter(identity).join('');
|
|
},
|
|
computedSeparatorRegExp: function computedSeparatorRegExp() {
|
|
// We use a computed prop here to precompile the RegExp
|
|
// The RegExp is a character class RE in the form of `/[abc]+/`
|
|
// where a, b, and c are the valid separator characters
|
|
// -> `tags = str.split(/[abc]+/).filter(t => t)`
|
|
var separator = this.computedSeparator;
|
|
return separator ? new RegExp("[".concat(escapeRegExpChars(separator), "]+")) : null;
|
|
},
|
|
computedJoiner: function computedJoiner() {
|
|
// When tag(s) are invalid or duplicate, we leave them
|
|
// in the input so that the user can see them
|
|
// If there are more than one tag in the input, we use the
|
|
// first separator character as the separator in the input
|
|
// We append a space if the first separator is not a space
|
|
var joiner = this.computedSeparator.charAt(0);
|
|
return joiner !== ' ' ? "".concat(joiner, " ") : joiner;
|
|
},
|
|
computeIgnoreInputFocusSelector: function computeIgnoreInputFocusSelector() {
|
|
// Normalize to an single selector with selectors separated by `,`
|
|
return concat(this.ignoreInputFocusSelector).filter(identity).join(',').trim();
|
|
},
|
|
disableAddButton: function disableAddButton() {
|
|
var _this = this;
|
|
|
|
// If 'Add' button should be disabled
|
|
// If the input contains at least one tag that can
|
|
// be added, then the 'Add' button should be enabled
|
|
var newTag = trim(this.newTag);
|
|
return newTag === '' || !this.splitTags(newTag).some(function (t) {
|
|
return !arrayIncludes(_this.tags, t) && _this.validateTag(t);
|
|
});
|
|
},
|
|
duplicateTags: function duplicateTags() {
|
|
return this.tagsState.duplicate;
|
|
},
|
|
hasDuplicateTags: function hasDuplicateTags() {
|
|
return this.duplicateTags.length > 0;
|
|
},
|
|
invalidTags: function invalidTags() {
|
|
return this.tagsState.invalid;
|
|
},
|
|
hasInvalidTags: function hasInvalidTags() {
|
|
return this.invalidTags.length > 0;
|
|
},
|
|
isLimitReached: function isLimitReached() {
|
|
var limit = this.limit;
|
|
return isNumber(limit) && limit >= 0 && this.tags.length >= limit;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal) {
|
|
this.tags = cleanTags(newVal);
|
|
},
|
|
tags: function tags(newVal, oldVal) {
|
|
// Update the `v-model` (if it differs from the value prop)
|
|
if (!looseEqual(newVal, this.value)) {
|
|
this.$emit('input', newVal);
|
|
}
|
|
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
newVal = concat(newVal).filter(identity);
|
|
oldVal = concat(oldVal).filter(identity);
|
|
this.removedTags = oldVal.filter(function (old) {
|
|
return !arrayIncludes(newVal, old);
|
|
});
|
|
}
|
|
},
|
|
tagsState: function tagsState(newVal, oldVal) {
|
|
// Emit a tag-state event when the `tagsState` object changes
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$emit('tag-state', newVal.valid, newVal.invalid, newVal.duplicate);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// We do this in created to make sure an input event emits
|
|
// if the cleaned tags are not equal to the value prop
|
|
this.tags = cleanTags(this.value);
|
|
},
|
|
methods: {
|
|
addTag: function addTag(newTag) {
|
|
newTag = isString(newTag) ? newTag : this.newTag;
|
|
/* istanbul ignore next */
|
|
|
|
if (this.disabled || trim(newTag) === '' || this.isLimitReached) {
|
|
// Early exit
|
|
return;
|
|
}
|
|
|
|
var parsed = this.parseTags(newTag); // Add any new tags to the `tags` array, or if the
|
|
// array of `allTags` is empty, we clear the input
|
|
|
|
if (parsed.valid.length > 0 || parsed.all.length === 0) {
|
|
// Clear the user input element (and leave in any invalid/duplicate tag(s)
|
|
|
|
/* istanbul ignore if: full testing to be added later */
|
|
if (matches(this.getInput(), 'select')) {
|
|
// The following is needed to properly
|
|
// work with `<select>` elements
|
|
this.newTag = '';
|
|
} else {
|
|
var invalidAndDuplicates = [].concat(_toConsumableArray(parsed.invalid), _toConsumableArray(parsed.duplicate));
|
|
this.newTag = parsed.all.filter(function (tag) {
|
|
return arrayIncludes(invalidAndDuplicates, tag);
|
|
}).join(this.computedJoiner).concat(invalidAndDuplicates.length > 0 ? this.computedJoiner.charAt(0) : '');
|
|
}
|
|
}
|
|
|
|
if (parsed.valid.length > 0) {
|
|
// We add the new tags in one atomic operation
|
|
// to trigger reactivity once (instead of once per tag)
|
|
// We do this after we update the new tag input value
|
|
// `concat()` can be faster than array spread, when both args are arrays
|
|
this.tags = concat(this.tags, parsed.valid);
|
|
}
|
|
|
|
this.tagsState = parsed; // Attempt to re-focus the input (specifically for when using the Add
|
|
// button, as the button disappears after successfully adding a tag
|
|
|
|
this.focus();
|
|
},
|
|
removeTag: function removeTag(tag) {
|
|
var _this2 = this;
|
|
|
|
/* istanbul ignore next */
|
|
if (this.disabled) {
|
|
return;
|
|
} // TODO:
|
|
// Add `onRemoveTag(tag)` user method, which if returns `false`
|
|
// will prevent the tag from being removed (i.e. confirmation)
|
|
// Or emit cancelable `BvEvent`
|
|
|
|
|
|
this.tags = this.tags.filter(function (t) {
|
|
return t !== tag;
|
|
}); // Return focus to the input (if possible)
|
|
|
|
this.$nextTick(function () {
|
|
_this2.focus();
|
|
});
|
|
},
|
|
// --- Input element event handlers ---
|
|
onInputInput: function onInputInput(evt) {
|
|
/* istanbul ignore next: hard to test composition events */
|
|
if (this.disabled || isEvent(evt) && evt.target.composing) {
|
|
// `evt.target.composing` is set by Vue (`v-model` directive)
|
|
// https://github.com/vuejs/vue/blob/dev/src/platforms/web/runtime/directives/model.js
|
|
return;
|
|
}
|
|
|
|
var newTag = processEventValue(evt);
|
|
var separatorRe = this.computedSeparatorRegExp;
|
|
|
|
if (this.newTag !== newTag) {
|
|
this.newTag = newTag;
|
|
} // We ignore leading whitespace for the following
|
|
|
|
|
|
newTag = trimLeft(newTag);
|
|
|
|
if (separatorRe && separatorRe.test(newTag.slice(-1))) {
|
|
// A trailing separator character was entered, so add the tag(s)
|
|
// Note: More than one tag on input event is possible via copy/paste
|
|
this.addTag();
|
|
} else {
|
|
// Validate (parse tags) on input event
|
|
this.tagsState = newTag === '' ? cleanTagsState() : this.parseTags(newTag);
|
|
}
|
|
},
|
|
onInputChange: function onInputChange(evt) {
|
|
// Change is triggered on `<input>` blur, or `<select>` selected
|
|
// This event is opt-in
|
|
if (!this.disabled && this.addOnChange) {
|
|
var newTag = processEventValue(evt);
|
|
/* istanbul ignore next */
|
|
|
|
if (this.newTag !== newTag) {
|
|
this.newTag = newTag;
|
|
}
|
|
|
|
this.addTag();
|
|
}
|
|
},
|
|
onInputKeydown: function onInputKeydown(evt) {
|
|
// Early exit
|
|
|
|
/* istanbul ignore next */
|
|
if (this.disabled || !isEvent(evt)) {
|
|
return;
|
|
}
|
|
|
|
var keyCode = evt.keyCode;
|
|
var value = evt.target.value || '';
|
|
/* istanbul ignore else: testing to be added later */
|
|
|
|
if (!this.noAddOnEnter && keyCode === CODE_ENTER) {
|
|
// Attempt to add the tag when user presses enter
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
this.addTag();
|
|
} else if (this.removeOnDelete && (keyCode === CODE_BACKSPACE || keyCode === CODE_DELETE) && value === '') {
|
|
// Remove the last tag if the user pressed backspace/delete and the input is empty
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
this.tags = this.tags.slice(0, -1);
|
|
}
|
|
},
|
|
// --- Wrapper event handlers ---
|
|
onClick: function onClick(evt) {
|
|
var _this3 = this;
|
|
|
|
var ignoreFocusSelector = this.computeIgnoreInputFocusSelector;
|
|
var target = evt.target;
|
|
|
|
if (!this.disabled && !isActiveElement(target) && (!ignoreFocusSelector || !closest(ignoreFocusSelector, target, true))) {
|
|
this.$nextTick(function () {
|
|
_this3.focus();
|
|
});
|
|
}
|
|
},
|
|
onFocusin: function onFocusin() {
|
|
this.hasFocus = true;
|
|
},
|
|
onFocusout: function onFocusout() {
|
|
this.hasFocus = false;
|
|
},
|
|
handleAutofocus: function handleAutofocus() {
|
|
var _this4 = this;
|
|
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
if (_this4.autofocus && !_this4.disabled) {
|
|
_this4.focus();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
// --- Public methods ---
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.getInput());
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.getInput());
|
|
}
|
|
},
|
|
// --- Private methods ---
|
|
splitTags: function splitTags(newTag) {
|
|
// Split the input into an array of raw tags
|
|
newTag = toString$1(newTag);
|
|
var separatorRe = this.computedSeparatorRegExp; // Split the tag(s) via the optional separator
|
|
// Normally only a single tag is provided, but copy/paste
|
|
// can enter multiple tags in a single operation
|
|
|
|
return (separatorRe ? newTag.split(separatorRe) : [newTag]).map(trim).filter(identity);
|
|
},
|
|
parseTags: function parseTags(newTag) {
|
|
var _this5 = this;
|
|
|
|
// Takes `newTag` value and parses it into `validTags`,
|
|
// `invalidTags`, and duplicate tags as an object
|
|
// Split the input into raw tags
|
|
var tags = this.splitTags(newTag); // Base results
|
|
|
|
var parsed = {
|
|
all: tags,
|
|
valid: [],
|
|
invalid: [],
|
|
duplicate: []
|
|
}; // Parse the unique tags
|
|
|
|
tags.forEach(function (tag) {
|
|
if (arrayIncludes(_this5.tags, tag) || arrayIncludes(parsed.valid, tag)) {
|
|
// Unique duplicate tags
|
|
if (!arrayIncludes(parsed.duplicate, tag)) {
|
|
parsed.duplicate.push(tag);
|
|
}
|
|
} else if (_this5.validateTag(tag)) {
|
|
// We only add unique/valid tags
|
|
parsed.valid.push(tag);
|
|
} else {
|
|
// Unique invalid tags
|
|
if (!arrayIncludes(parsed.invalid, tag)) {
|
|
parsed.invalid.push(tag);
|
|
}
|
|
}
|
|
});
|
|
return parsed;
|
|
},
|
|
validateTag: function validateTag(tag) {
|
|
var tagValidator = this.tagValidator;
|
|
var result = null;
|
|
|
|
try {
|
|
result = tagValidator();
|
|
} catch (_unused) {}
|
|
|
|
return isUndefined(result) ? true : tagValidator(tag);
|
|
},
|
|
getInput: function getInput() {
|
|
// Returns the input element reference (or null if not found)
|
|
// We need to escape `computedInputId` since it can be user-provided
|
|
return select("#".concat(cssEscape(this.computedInputId)), this.$el);
|
|
},
|
|
// Default User Interface render
|
|
defaultRender: function defaultRender(_ref) {
|
|
var tags = _ref.tags,
|
|
inputAttrs = _ref.inputAttrs,
|
|
inputType = _ref.inputType,
|
|
inputHandlers = _ref.inputHandlers,
|
|
removeTag = _ref.removeTag,
|
|
addTag = _ref.addTag,
|
|
isInvalid = _ref.isInvalid,
|
|
isDuplicate = _ref.isDuplicate,
|
|
isLimitReached = _ref.isLimitReached,
|
|
disableAddButton = _ref.disableAddButton,
|
|
disabled = _ref.disabled,
|
|
placeholder = _ref.placeholder,
|
|
inputClass = _ref.inputClass,
|
|
tagRemoveLabel = _ref.tagRemoveLabel,
|
|
tagVariant = _ref.tagVariant,
|
|
tagPills = _ref.tagPills,
|
|
tagClass = _ref.tagClass,
|
|
addButtonText = _ref.addButtonText,
|
|
addButtonVariant = _ref.addButtonVariant,
|
|
invalidTagText = _ref.invalidTagText,
|
|
duplicateTagText = _ref.duplicateTagText,
|
|
limitTagsText = _ref.limitTagsText;
|
|
var h = this.$createElement; // Make the list of tags
|
|
|
|
var $tags = tags.map(function (tag) {
|
|
tag = toString$1(tag);
|
|
return h(BFormTag, {
|
|
class: tagClass,
|
|
props: {
|
|
// `BFormTag` will auto generate an ID
|
|
// so we do not need to set the ID prop
|
|
tag: 'li',
|
|
title: tag,
|
|
disabled: disabled,
|
|
variant: tagVariant,
|
|
pill: tagPills,
|
|
removeLabel: tagRemoveLabel
|
|
},
|
|
on: {
|
|
remove: function remove() {
|
|
return removeTag(tag);
|
|
}
|
|
},
|
|
key: "tags_".concat(tag)
|
|
}, tag);
|
|
}); // Feedback IDs if needed
|
|
|
|
var invalidFeedbackId = invalidTagText && isInvalid ? this.safeId('__invalid_feedback__') : null;
|
|
var duplicateFeedbackId = duplicateTagText && isDuplicate ? this.safeId('__duplicate_feedback__') : null;
|
|
var limitFeedbackId = limitTagsText && isLimitReached ? this.safeId('__limit_feedback__') : null; // Compute the `aria-describedby` attribute value
|
|
|
|
var ariaDescribedby = [inputAttrs['aria-describedby'], invalidFeedbackId, duplicateFeedbackId, limitFeedbackId].filter(identity).join(' '); // Input
|
|
|
|
var $input = h('input', {
|
|
ref: 'input',
|
|
// Directive needed to get `evt.target.composing` set (if needed)
|
|
directives: [{
|
|
name: 'model',
|
|
value: inputAttrs.value
|
|
}],
|
|
staticClass: 'b-form-tags-input w-100 flex-grow-1 p-0 m-0 bg-transparent border-0',
|
|
class: inputClass,
|
|
style: {
|
|
outline: 0,
|
|
minWidth: '5rem'
|
|
},
|
|
attrs: _objectSpread2(_objectSpread2({}, inputAttrs), {}, {
|
|
'aria-describedby': ariaDescribedby || null,
|
|
type: inputType,
|
|
placeholder: placeholder || null
|
|
}),
|
|
domProps: {
|
|
value: inputAttrs.value
|
|
},
|
|
on: inputHandlers
|
|
}); // Add button
|
|
|
|
var $button = h(BButton, {
|
|
ref: 'button',
|
|
staticClass: 'b-form-tags-button py-0',
|
|
class: {
|
|
// Only show the button if the tag can be added
|
|
// We use the `invisible` class instead of not rendering
|
|
// the button, so that we maintain layout to prevent
|
|
// the user input from jumping around
|
|
invisible: disableAddButton
|
|
},
|
|
style: {
|
|
fontSize: '90%'
|
|
},
|
|
props: {
|
|
variant: addButtonVariant,
|
|
disabled: disableAddButton || isLimitReached
|
|
},
|
|
on: {
|
|
click: function click() {
|
|
return addTag();
|
|
}
|
|
}
|
|
}, [this.normalizeSlot('add-button-text') || addButtonText]); // ID of the tags + input `<ul>` list
|
|
// Note we could concatenate `inputAttrs.id` with '__tag_list__'
|
|
// but `inputId` may be `null` until after mount
|
|
// `safeId()` returns `null`, if no user provided ID,
|
|
// until after mount when a unique ID is generated
|
|
|
|
var tagListId = this.safeId('__tag_list__');
|
|
var $field = h('li', {
|
|
staticClass: 'b-from-tags-field flex-grow-1',
|
|
attrs: {
|
|
role: 'none',
|
|
'aria-live': 'off',
|
|
'aria-controls': tagListId
|
|
},
|
|
key: 'tags_field'
|
|
}, [h('div', {
|
|
staticClass: 'd-flex',
|
|
attrs: {
|
|
role: 'group'
|
|
}
|
|
}, [$input, $button])]); // Wrap in an unordered list element (we use a list for accessibility)
|
|
|
|
var $ul = h('ul', {
|
|
staticClass: 'b-form-tags-list list-unstyled mb-0 d-flex flex-wrap align-items-center',
|
|
attrs: {
|
|
id: tagListId
|
|
},
|
|
key: 'tags_list'
|
|
}, [$tags, $field]); // Assemble the feedback
|
|
|
|
var $feedback = h();
|
|
|
|
if (invalidTagText || duplicateTagText || limitTagsText) {
|
|
// Add an aria live region for the invalid/duplicate tag
|
|
// messages if the user has not disabled the messages
|
|
var joiner = this.computedJoiner; // Invalid tag feedback if needed (error)
|
|
|
|
var $invalid = h();
|
|
|
|
if (invalidFeedbackId) {
|
|
$invalid = h(BFormInvalidFeedback, {
|
|
props: {
|
|
id: invalidFeedbackId,
|
|
forceShow: true
|
|
},
|
|
key: 'tags_invalid_feedback'
|
|
}, [this.invalidTagText, ': ', this.invalidTags.join(joiner)]);
|
|
} // Duplicate tag feedback if needed (warning, not error)
|
|
|
|
|
|
var $duplicate = h();
|
|
|
|
if (duplicateFeedbackId) {
|
|
$duplicate = h(BFormText, {
|
|
props: {
|
|
id: duplicateFeedbackId
|
|
},
|
|
key: 'tags_duplicate_feedback'
|
|
}, [this.duplicateTagText, ': ', this.duplicateTags.join(joiner)]);
|
|
} // Limit tags feedback if needed (warning, not error)
|
|
|
|
|
|
var $limit = h();
|
|
|
|
if (limitFeedbackId) {
|
|
$limit = h(BFormText, {
|
|
props: {
|
|
id: limitFeedbackId
|
|
},
|
|
key: 'tags_limit_feedback'
|
|
}, [limitTagsText]);
|
|
}
|
|
|
|
$feedback = h('div', {
|
|
attrs: {
|
|
'aria-live': 'polite',
|
|
'aria-atomic': 'true'
|
|
},
|
|
key: 'tags_feedback'
|
|
}, [$invalid, $duplicate, $limit]);
|
|
} // Return the content
|
|
|
|
|
|
return [$ul, $feedback];
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this6 = this;
|
|
|
|
var name = this.name,
|
|
disabled = this.disabled,
|
|
tags = this.tags,
|
|
computedInputId = this.computedInputId,
|
|
hasFocus = this.hasFocus,
|
|
noOuterFocus = this.noOuterFocus; // Scoped slot properties
|
|
|
|
var scope = _objectSpread2({
|
|
// Array of tags (shallow copy to prevent mutations)
|
|
tags: tags.slice(),
|
|
// <input> v-bind:inputAttrs
|
|
inputAttrs: this.computedInputAttrs,
|
|
// We don't include this in the attrs, as users may want to override this
|
|
inputType: this.computedInputType,
|
|
// <input> v-on:inputHandlers
|
|
inputHandlers: this.computedInputHandlers,
|
|
// Methods
|
|
removeTag: this.removeTag,
|
|
addTag: this.addTag,
|
|
// <input> :id="inputId"
|
|
inputId: computedInputId,
|
|
// Invalid/Duplicate state information
|
|
isInvalid: this.hasInvalidTags,
|
|
invalidTags: this.invalidTags.slice(),
|
|
isDuplicate: this.hasDuplicateTags,
|
|
duplicateTags: this.duplicateTags.slice(),
|
|
isLimitReached: this.isLimitReached,
|
|
// If the 'Add' button should be disabled
|
|
disableAddButton: this.disableAddButton
|
|
}, pick(this.$props, ['disabled', 'state', 'size', 'limit', 'separator', 'placeholder', 'inputClass', 'tagRemoveLabel', 'tagVariant', 'tagPills', 'tagClass', 'addButtonText', 'addButtonVariant', 'invalidTagText', 'duplicateTagText', 'limitTagsText'])); // Generate the user interface
|
|
|
|
|
|
var $content = this.normalizeSlot(SLOT_NAME_DEFAULT, scope) || this.defaultRender(scope); // Generate the `aria-live` region for the current value(s)
|
|
|
|
var $output = h('output', {
|
|
staticClass: 'sr-only',
|
|
attrs: {
|
|
id: this.safeId('__selected_tags__'),
|
|
role: 'status',
|
|
for: computedInputId,
|
|
'aria-live': hasFocus ? 'polite' : 'off',
|
|
'aria-atomic': 'true',
|
|
'aria-relevant': 'additions text'
|
|
}
|
|
}, this.tags.join(', ')); // Removed tag live region
|
|
|
|
var $removed = h('div', {
|
|
staticClass: 'sr-only',
|
|
attrs: {
|
|
id: this.safeId('__removed_tags__'),
|
|
role: 'status',
|
|
'aria-live': hasFocus ? 'assertive' : 'off',
|
|
'aria-atomic': 'true'
|
|
}
|
|
}, this.removedTags.length > 0 ? "(".concat(this.tagRemovedLabel, ") ").concat(this.removedTags.join(', ')) : ''); // Add hidden inputs for form submission
|
|
|
|
var $hidden = h();
|
|
|
|
if (name && !disabled) {
|
|
// We add hidden inputs for each tag if a name is provided
|
|
// for native submission of forms
|
|
$hidden = tags.map(function (tag) {
|
|
return h('input', {
|
|
attrs: {
|
|
type: 'hidden',
|
|
value: tag,
|
|
name: name,
|
|
form: _this6.form || null
|
|
},
|
|
key: "tag_input_".concat(tag)
|
|
});
|
|
});
|
|
} // Return the rendered output
|
|
|
|
|
|
return h('div', {
|
|
staticClass: 'b-form-tags form-control h-auto',
|
|
class: [{
|
|
focus: hasFocus && !noOuterFocus && !disabled,
|
|
disabled: disabled
|
|
}, this.sizeFormClass, this.stateClass],
|
|
attrs: {
|
|
id: this.safeId(),
|
|
role: 'group',
|
|
tabindex: disabled || noOuterFocus ? null : '-1',
|
|
'aria-describedby': this.safeId('__selected_tags__')
|
|
},
|
|
on: {
|
|
click: this.onClick,
|
|
focusin: this.onFocusin,
|
|
focusout: this.onFocusout
|
|
}
|
|
}, [$output, $removed, $content, $hidden]);
|
|
}
|
|
});
|
|
|
|
var FormTagsPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormTags: BFormTags,
|
|
BTags: BFormTags,
|
|
BFormTag: BFormTag,
|
|
BTag: BFormTag
|
|
}
|
|
});
|
|
|
|
var BFormTextarea = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_TEXTAREA,
|
|
directives: {
|
|
'b-visible': VBVisible
|
|
},
|
|
// Mixin order is important!
|
|
mixins: [listenersMixin, idMixin, listenOnRootMixin, formControlMixin, formSizeMixin, formStateMixin, formTextMixin, formSelectionMixin, formValidityMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, props$A), props$D), props$E), props$I), {}, {
|
|
rows: {
|
|
type: [Number, String],
|
|
default: 2
|
|
},
|
|
maxRows: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
wrap: {
|
|
// 'soft', 'hard' or 'off'. Browser default is 'soft'
|
|
type: String,
|
|
default: 'soft'
|
|
},
|
|
noResize: {
|
|
// Disable the resize handle of textarea
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noAutoShrink: {
|
|
// When in auto resize mode, disable shrinking to content height
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_FORM_TEXTAREA),
|
|
data: function data() {
|
|
return {
|
|
heightInPx: null
|
|
};
|
|
},
|
|
computed: {
|
|
computedStyle: function computedStyle() {
|
|
var styles = {
|
|
// Setting `noResize` to true will disable the ability for the user to
|
|
// manually resize the textarea. We also disable when in auto height mode
|
|
resize: !this.computedRows || this.noResize ? 'none' : null
|
|
};
|
|
|
|
if (!this.computedRows) {
|
|
// Conditionally set the computed CSS height when auto rows/height is enabled
|
|
// We avoid setting the style to `null`, which can override user manual resize handle
|
|
styles.height = this.heightInPx; // We always add a vertical scrollbar to the textarea when auto-height is
|
|
// enabled so that the computed height calculation returns a stable value
|
|
|
|
styles.overflowY = 'scroll';
|
|
}
|
|
|
|
return styles;
|
|
},
|
|
computedMinRows: function computedMinRows() {
|
|
// Ensure rows is at least 2 and positive (2 is the native textarea value)
|
|
// A value of 1 can cause issues in some browsers, and most browsers
|
|
// only support 2 as the smallest value
|
|
return mathMax(toInteger(this.rows, 2), 2);
|
|
},
|
|
computedMaxRows: function computedMaxRows() {
|
|
return mathMax(this.computedMinRows, toInteger(this.maxRows, 0));
|
|
},
|
|
computedRows: function computedRows() {
|
|
// This is used to set the attribute 'rows' on the textarea
|
|
// If auto-height is enabled, then we return `null` as we use CSS to control height
|
|
return this.computedMinRows === this.computedMaxRows ? this.computedMinRows : null;
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
var disabled = this.disabled,
|
|
required = this.required;
|
|
return {
|
|
id: this.safeId(),
|
|
name: this.name || null,
|
|
form: this.form || null,
|
|
disabled: disabled,
|
|
placeholder: this.placeholder || null,
|
|
required: required,
|
|
autocomplete: this.autocomplete || null,
|
|
readonly: this.readonly || this.plaintext,
|
|
rows: this.computedRows,
|
|
wrap: this.wrap || null,
|
|
'aria-required': this.required ? 'true' : null,
|
|
'aria-invalid': this.computedAriaInvalid
|
|
};
|
|
},
|
|
computedListeners: function computedListeners() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvListeners), {}, {
|
|
input: this.onInput,
|
|
change: this.onChange,
|
|
blur: this.onBlur
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
localValue: function localValue() {
|
|
this.setHeight();
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
this.setHeight();
|
|
},
|
|
methods: {
|
|
// Called by intersection observer directive
|
|
|
|
/* istanbul ignore next */
|
|
visibleCallback: function visibleCallback(visible) {
|
|
if (visible) {
|
|
// We use a `$nextTick()` here just to make sure any
|
|
// transitions or portalling have completed
|
|
this.$nextTick(this.setHeight);
|
|
}
|
|
},
|
|
setHeight: function setHeight() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this.heightInPx = _this.computeHeight();
|
|
});
|
|
});
|
|
},
|
|
|
|
/* istanbul ignore next: can't test getComputedStyle in JSDOM */
|
|
computeHeight: function computeHeight() {
|
|
if (this.$isServer || !isNull(this.computedRows)) {
|
|
return null;
|
|
}
|
|
|
|
var el = this.$el; // Element must be visible (not hidden) and in document
|
|
// Must be checked after above checks
|
|
|
|
if (!isVisible(el)) {
|
|
return null;
|
|
} // Get current computed styles
|
|
|
|
|
|
var computedStyle = getCS(el); // Height of one line of text in px
|
|
|
|
var lineHeight = toFloat(computedStyle.lineHeight, 1); // Calculate height of border and padding
|
|
|
|
var border = toFloat(computedStyle.borderTopWidth, 0) + toFloat(computedStyle.borderBottomWidth, 0);
|
|
var padding = toFloat(computedStyle.paddingTop, 0) + toFloat(computedStyle.paddingBottom, 0); // Calculate offset
|
|
|
|
var offset = border + padding; // Minimum height for min rows (which must be 2 rows or greater for cross-browser support)
|
|
|
|
var minHeight = lineHeight * this.computedMinRows + offset; // Get the current style height (with `px` units)
|
|
|
|
var oldHeight = getStyle(el, 'height') || computedStyle.height; // Probe scrollHeight by temporarily changing the height to `auto`
|
|
|
|
setStyle(el, 'height', 'auto');
|
|
var scrollHeight = el.scrollHeight; // Place the original old height back on the element, just in case `computedProp`
|
|
// returns the same value as before
|
|
|
|
setStyle(el, 'height', oldHeight); // Calculate content height in 'rows' (scrollHeight includes padding but not border)
|
|
|
|
var contentRows = mathMax((scrollHeight - padding) / lineHeight, 2); // Calculate number of rows to display (limited within min/max rows)
|
|
|
|
var rows = mathMin(mathMax(contentRows, this.computedMinRows), this.computedMaxRows); // Calculate the required height of the textarea including border and padding (in pixels)
|
|
|
|
var height = mathMax(mathCeil(rows * lineHeight + offset), minHeight); // Computed height remains the larger of `oldHeight` and new `height`,
|
|
// when height is in `sticky` mode (prop `no-auto-shrink` is true)
|
|
|
|
if (this.noAutoShrink && toFloat(oldHeight, 0) > height) {
|
|
return oldHeight;
|
|
} // Return the new computed CSS height in px units
|
|
|
|
|
|
return "".concat(height, "px");
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('textarea', {
|
|
ref: 'input',
|
|
class: this.computedClass,
|
|
style: this.computedStyle,
|
|
directives: [{
|
|
name: 'b-visible',
|
|
value: this.visibleCallback,
|
|
// If textarea is within 640px of viewport, consider it visible
|
|
modifiers: {
|
|
'640': true
|
|
}
|
|
}],
|
|
attrs: this.computedAttrs,
|
|
domProps: {
|
|
value: this.localValue
|
|
},
|
|
on: this.computedListeners
|
|
});
|
|
}
|
|
});
|
|
|
|
var FormTextareaPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormTextarea: BFormTextarea,
|
|
BTextarea: BFormTextarea
|
|
}
|
|
});
|
|
|
|
var NUMERIC = 'numeric'; // --- Helper methods ---
|
|
|
|
var padLeftZeros = function padLeftZeros(num) {
|
|
return "00".concat(num || '').slice(-2);
|
|
};
|
|
|
|
var parseHMS = function parseHMS(hms) {
|
|
hms = toString$1(hms);
|
|
var hh = null,
|
|
mm = null,
|
|
ss = null;
|
|
|
|
if (RX_TIME.test(hms)) {
|
|
|
|
var _hms$split$map = hms.split(':').map(function (v) {
|
|
return toInteger(v, null);
|
|
});
|
|
|
|
var _hms$split$map2 = _slicedToArray(_hms$split$map, 3);
|
|
|
|
hh = _hms$split$map2[0];
|
|
mm = _hms$split$map2[1];
|
|
ss = _hms$split$map2[2];
|
|
}
|
|
|
|
return {
|
|
hours: isUndefinedOrNull(hh) ? null : hh,
|
|
minutes: isUndefinedOrNull(mm) ? null : mm,
|
|
seconds: isUndefinedOrNull(ss) ? null : ss,
|
|
ampm: isUndefinedOrNull(hh) || hh < 12 ? 0 : 1
|
|
};
|
|
};
|
|
|
|
var formatHMS = function formatHMS(_ref) {
|
|
var hours = _ref.hours,
|
|
minutes = _ref.minutes,
|
|
seconds = _ref.seconds;
|
|
var requireSeconds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
if (isNull(hours) || isNull(minutes) || requireSeconds && isNull(seconds)) {
|
|
return '';
|
|
}
|
|
|
|
var hms = [hours, minutes, requireSeconds ? seconds : 0];
|
|
return hms.map(padLeftZeros).join(':');
|
|
}; // --- Props ---
|
|
|
|
|
|
var props$M = makePropsConfigurable(_objectSpread2(_objectSpread2({
|
|
value: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
showSeconds: {
|
|
// If true, show the second spinbutton
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hour12: {
|
|
// Explicitly force 12 or 24 hour time
|
|
// Default is to use resolved locale for 12/24 hour display
|
|
// Tri-state: `true` = 12, `false` = 24, `null` = auto
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
locale: {
|
|
type: [String, Array] // default: null
|
|
|
|
},
|
|
ariaLabelledby: {
|
|
// ID of label element
|
|
type: String // default: null
|
|
|
|
},
|
|
secondsStep: {
|
|
type: [Number, String],
|
|
default: 1
|
|
},
|
|
minutesStep: {
|
|
type: [Number, String],
|
|
default: 1
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hideHeader: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelNoTimeSelected: {
|
|
type: String,
|
|
default: 'No time selected'
|
|
},
|
|
labelSelected: {
|
|
type: String,
|
|
default: 'Selected time'
|
|
},
|
|
labelHours: {
|
|
type: String,
|
|
default: 'Hours'
|
|
},
|
|
labelMinutes: {
|
|
type: String,
|
|
default: 'Minutes'
|
|
},
|
|
labelSeconds: {
|
|
type: String,
|
|
default: 'Seconds'
|
|
},
|
|
labelAmpm: {
|
|
type: String,
|
|
default: 'AM/PM'
|
|
},
|
|
labelAm: {
|
|
type: String,
|
|
default: 'AM'
|
|
},
|
|
labelPm: {
|
|
type: String,
|
|
default: 'PM'
|
|
}
|
|
}, pick(props$L, ['labelIncrement', 'labelDecrement'])), {}, {
|
|
hidden: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_TIME); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BTime = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TIME,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: props$M,
|
|
data: function data() {
|
|
var parsed = parseHMS(this.value || '');
|
|
return {
|
|
// Spin button models
|
|
modelHours: parsed.hours,
|
|
modelMinutes: parsed.minutes,
|
|
modelSeconds: parsed.seconds,
|
|
modelAmpm: parsed.ampm,
|
|
// Internal flag to enable aria-live regions
|
|
isLive: false
|
|
};
|
|
},
|
|
computed: {
|
|
computedHMS: function computedHMS() {
|
|
var hours = this.modelHours;
|
|
var minutes = this.modelMinutes;
|
|
var seconds = this.modelSeconds;
|
|
return formatHMS({
|
|
hours: hours,
|
|
minutes: minutes,
|
|
seconds: seconds
|
|
}, this.showSeconds);
|
|
},
|
|
resolvedOptions: function resolvedOptions() {
|
|
// Resolved locale options
|
|
var locale = concat(this.locale).filter(identity);
|
|
var options = {
|
|
hour: NUMERIC,
|
|
minute: NUMERIC,
|
|
second: NUMERIC
|
|
};
|
|
|
|
if (!isUndefinedOrNull(this.hour12)) {
|
|
// Force 12 or 24 hour clock
|
|
options.hour12 = !!this.hour12;
|
|
}
|
|
|
|
var dtf = new Intl.DateTimeFormat(locale, options);
|
|
var resolved = dtf.resolvedOptions();
|
|
var hour12 = resolved.hour12 || false; // IE 11 doesn't resolve the hourCycle, so we make
|
|
// an assumption and fall back to common values
|
|
|
|
var hourCycle = resolved.hourCycle || (hour12 ? 'h12' : 'h23');
|
|
return {
|
|
locale: resolved.locale,
|
|
hour12: hour12,
|
|
hourCycle: hourCycle
|
|
};
|
|
},
|
|
computedLocale: function computedLocale() {
|
|
return this.resolvedOptions.locale;
|
|
},
|
|
computedLang: function computedLang() {
|
|
return (this.computedLocale || '').replace(/-u-.*$/, '');
|
|
},
|
|
computedRTL: function computedRTL() {
|
|
return isLocaleRTL(this.computedLang);
|
|
},
|
|
computedHourCycle: function computedHourCycle() {
|
|
// h11, h12, h23, or h24
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Locale/hourCycle
|
|
// h12 - Hour system using 1–12. Corresponds to 'h' in patterns. The 12 hour clock, with midnight starting at 12:00 am
|
|
// h23 - Hour system using 0–23. Corresponds to 'H' in patterns. The 24 hour clock, with midnight starting at 0:00
|
|
// h11 - Hour system using 0–11. Corresponds to 'K' in patterns. The 12 hour clock, with midnight starting at 0:00 am
|
|
// h24 - Hour system using 1–24. Corresponds to 'k' in pattern. The 24 hour clock, with midnight starting at 24:00
|
|
// For h12 or h24, we visually format 00 hours as 12
|
|
return this.resolvedOptions.hourCycle;
|
|
},
|
|
is12Hour: function is12Hour() {
|
|
return !!this.resolvedOptions.hour12;
|
|
},
|
|
context: function context() {
|
|
return {
|
|
locale: this.computedLocale,
|
|
isRTL: this.computedRTL,
|
|
hourCycle: this.computedHourCycle,
|
|
hour12: this.is12Hour,
|
|
hours: this.modelHours,
|
|
minutes: this.modelMinutes,
|
|
seconds: this.showSeconds ? this.modelSeconds : 0,
|
|
value: this.computedHMS,
|
|
formatted: this.formattedTimeString
|
|
};
|
|
},
|
|
valueId: function valueId() {
|
|
return this.safeId() || null;
|
|
},
|
|
computedAriaLabelledby: function computedAriaLabelledby() {
|
|
return [this.ariaLabelledby, this.valueId].filter(identity).join(' ') || null;
|
|
},
|
|
timeFormatter: function timeFormatter() {
|
|
// Returns a formatter function reference
|
|
// The formatter converts the time to a localized string
|
|
var options = {
|
|
hour12: this.is12Hour,
|
|
hourCycle: this.computedHourCycle,
|
|
hour: NUMERIC,
|
|
minute: NUMERIC,
|
|
timeZone: 'UTC'
|
|
};
|
|
|
|
if (this.showSeconds) {
|
|
options.second = NUMERIC;
|
|
} // Formats the time as a localized string
|
|
|
|
|
|
return createDateFormatter(this.computedLocale, options);
|
|
},
|
|
numberFormatter: function numberFormatter() {
|
|
// Returns a formatter function reference
|
|
// The formatter always formats as 2 digits and is localized
|
|
var nf = new Intl.NumberFormat(this.computedLocale, {
|
|
style: 'decimal',
|
|
minimumIntegerDigits: 2,
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
notation: 'standard'
|
|
});
|
|
return nf.format;
|
|
},
|
|
formattedTimeString: function formattedTimeString() {
|
|
var hours = this.modelHours;
|
|
var minutes = this.modelMinutes;
|
|
var seconds = this.showSeconds ? this.modelSeconds || 0 : 0;
|
|
|
|
if (this.computedHMS) {
|
|
return this.timeFormatter(createDate(Date.UTC(0, 0, 1, hours, minutes, seconds)));
|
|
}
|
|
|
|
return this.labelNoTimeSelected || ' ';
|
|
},
|
|
spinScopedSlots: function spinScopedSlots() {
|
|
var h = this.$createElement;
|
|
return {
|
|
increment: function increment(_ref2) {
|
|
var hasFocus = _ref2.hasFocus;
|
|
return h(BIconChevronUp, {
|
|
props: {
|
|
scale: hasFocus ? 1.5 : 1.25
|
|
},
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
});
|
|
},
|
|
decrement: function decrement(_ref3) {
|
|
var hasFocus = _ref3.hasFocus;
|
|
return h(BIconChevronUp, {
|
|
props: {
|
|
flipV: true,
|
|
scale: hasFocus ? 1.5 : 1.25
|
|
},
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal, oldVal) {
|
|
if (newVal !== oldVal && !looseEqual(parseHMS(newVal), parseHMS(this.computedHMS))) {
|
|
var _parseHMS = parseHMS(newVal),
|
|
hours = _parseHMS.hours,
|
|
minutes = _parseHMS.minutes,
|
|
seconds = _parseHMS.seconds,
|
|
ampm = _parseHMS.ampm;
|
|
|
|
this.modelHours = hours;
|
|
this.modelMinutes = minutes;
|
|
this.modelSeconds = seconds;
|
|
this.modelAmpm = ampm;
|
|
}
|
|
},
|
|
computedHMS: function computedHMS(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.$emit('input', newVal);
|
|
}
|
|
},
|
|
context: function context(newVal, oldVal) {
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$emit('context', newVal);
|
|
}
|
|
},
|
|
modelAmpm: function modelAmpm(newVal, oldVal) {
|
|
var _this = this;
|
|
|
|
if (newVal !== oldVal) {
|
|
var hours = isNull(this.modelHours) ? 0 : this.modelHours;
|
|
this.$nextTick(function () {
|
|
if (newVal === 0 && hours > 11) {
|
|
// Switched to AM
|
|
_this.modelHours = hours - 12;
|
|
} else if (newVal === 1 && hours < 12) {
|
|
// Switched to PM
|
|
_this.modelHours = hours + 12;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
modelHours: function modelHours(newHours, oldHours) {
|
|
if (newHours !== oldHours) {
|
|
this.modelAmpm = newHours > 11 ? 1 : 0;
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this2 = this;
|
|
|
|
this.$nextTick(function () {
|
|
_this2.$emit('context', _this2.context);
|
|
});
|
|
},
|
|
mounted: function mounted() {
|
|
this.setLive(true);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
this.setLive(true);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
this.setLive(false);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.setLive(false);
|
|
},
|
|
methods: {
|
|
// Public methods
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
// We focus the first spin button
|
|
attemptFocus(this.$refs.spinners[0]);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
var activeElement = getActiveElement();
|
|
|
|
if (contains(this.$el, activeElement)) {
|
|
attemptBlur(activeElement);
|
|
}
|
|
}
|
|
},
|
|
// Formatters for the spin buttons
|
|
formatHours: function formatHours(hh) {
|
|
var hourCycle = this.computedHourCycle; // We always store 0-23, but format based on h11/h12/h23/h24 formats
|
|
|
|
hh = this.is12Hour && hh > 12 ? hh - 12 : hh; // Determine how 00:00 and 12:00 are shown
|
|
|
|
hh = hh === 0 && hourCycle === 'h12' ? 12 : hh === 0 && hourCycle === 'h24' ?
|
|
/* istanbul ignore next */
|
|
24 : hh === 12 && hourCycle === 'h11' ?
|
|
/* istanbul ignore next */
|
|
0 : hh;
|
|
return this.numberFormatter(hh);
|
|
},
|
|
formatMinutes: function formatMinutes(mm) {
|
|
return this.numberFormatter(mm);
|
|
},
|
|
formatSeconds: function formatSeconds(ss) {
|
|
return this.numberFormatter(ss);
|
|
},
|
|
formatAmpm: function formatAmpm(ampm) {
|
|
// These should come from label props???
|
|
// `ampm` should always be a value of `0` or `1`
|
|
return ampm === 0 ? this.labelAm : ampm === 1 ? this.labelPm : '';
|
|
},
|
|
// Spinbutton on change handlers
|
|
setHours: function setHours(value) {
|
|
this.modelHours = value;
|
|
},
|
|
setMinutes: function setMinutes(value) {
|
|
this.modelMinutes = value;
|
|
},
|
|
setSeconds: function setSeconds(value) {
|
|
this.modelSeconds = value;
|
|
},
|
|
setAmpm: function setAmpm(value) {
|
|
this.modelAmpm = value;
|
|
},
|
|
onSpinLeftRight: function onSpinLeftRight() {
|
|
var evt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var type = evt.type,
|
|
keyCode = evt.keyCode;
|
|
|
|
if (!this.disabled && type === 'keydown' && (keyCode === CODE_LEFT || keyCode === CODE_RIGHT)) {
|
|
stopEvent(evt);
|
|
var spinners = this.$refs.spinners || [];
|
|
var index = spinners.map(function (cmp) {
|
|
return !!cmp.hasFocus;
|
|
}).indexOf(true);
|
|
index = index + (keyCode === CODE_LEFT ? -1 : 1);
|
|
index = index >= spinners.length ? 0 : index < 0 ? spinners.length - 1 : index;
|
|
attemptFocus(spinners[index]);
|
|
}
|
|
},
|
|
setLive: function setLive(on) {
|
|
var _this3 = this;
|
|
|
|
if (on) {
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this3.isLive = true;
|
|
});
|
|
});
|
|
} else {
|
|
this.isLive = false;
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this4 = this;
|
|
|
|
/* istanbul ignore if */
|
|
if (this.hidden) {
|
|
// If hidden, we just render a placeholder comment
|
|
return h();
|
|
}
|
|
|
|
var valueId = this.valueId;
|
|
var computedAriaLabelledby = this.computedAriaLabelledby;
|
|
var spinIds = []; // Helper method to render a spinbutton
|
|
|
|
var makeSpinbutton = function makeSpinbutton(handler, key, classes) {
|
|
var spinbuttonProps = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
var id = _this4.safeId("_spinbutton_".concat(key, "_")) || null;
|
|
spinIds.push(id);
|
|
return h(BFormSpinbutton, {
|
|
key: key,
|
|
ref: 'spinners',
|
|
refInFor: true,
|
|
class: classes,
|
|
props: _objectSpread2({
|
|
id: id,
|
|
placeholder: '--',
|
|
vertical: true,
|
|
required: true,
|
|
disabled: _this4.disabled,
|
|
readonly: _this4.readonly,
|
|
locale: _this4.computedLocale,
|
|
labelIncrement: _this4.labelIncrement,
|
|
labelDecrement: _this4.labelDecrement,
|
|
wrap: true,
|
|
ariaControls: valueId,
|
|
min: 0
|
|
}, spinbuttonProps),
|
|
scopedSlots: _this4.spinScopedSlots,
|
|
on: {
|
|
// We use `change` event to minimize SR verbosity
|
|
// As the spinbutton will announce each value change
|
|
// and we don't want the formatted time to be announced
|
|
// on each value input if repeat is happening
|
|
change: handler
|
|
}
|
|
});
|
|
}; // Helper method to return a "colon" separator
|
|
|
|
|
|
var makeColon = function makeColon() {
|
|
return h('div', {
|
|
staticClass: 'd-flex flex-column',
|
|
class: {
|
|
'text-muted': _this4.disabled || _this4.readonly
|
|
},
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
}, [h(BIconCircleFill, {
|
|
props: {
|
|
shiftV: 4,
|
|
scale: 0.5
|
|
}
|
|
}), h(BIconCircleFill, {
|
|
props: {
|
|
shiftV: -4,
|
|
scale: 0.5
|
|
}
|
|
})]);
|
|
};
|
|
|
|
var $spinners = []; // Hours
|
|
|
|
$spinners.push(makeSpinbutton(this.setHours, 'hours', 'b-time-hours', {
|
|
value: this.modelHours,
|
|
max: 23,
|
|
step: 1,
|
|
formatterFn: this.formatHours,
|
|
ariaLabel: this.labelHours
|
|
})); // Spacer
|
|
|
|
$spinners.push(makeColon()); // Minutes
|
|
|
|
$spinners.push(makeSpinbutton(this.setMinutes, 'minutes', 'b-time-minutes', {
|
|
value: this.modelMinutes,
|
|
max: 59,
|
|
step: this.minutesStep || 1,
|
|
formatterFn: this.formatMinutes,
|
|
ariaLabel: this.labelMinutes
|
|
}));
|
|
|
|
if (this.showSeconds) {
|
|
// Spacer
|
|
$spinners.push(makeColon()); // Seconds
|
|
|
|
$spinners.push(makeSpinbutton(this.setSeconds, 'seconds', 'b-time-seconds', {
|
|
value: this.modelSeconds,
|
|
max: 59,
|
|
step: this.secondsStep || 1,
|
|
formatterFn: this.formatSeconds,
|
|
ariaLabel: this.labelSeconds
|
|
}));
|
|
} // AM/PM ?
|
|
|
|
|
|
if (this.is12Hour) {
|
|
// TODO:
|
|
// If locale is RTL, unshift this instead of push?
|
|
// And switch class `ml-2` to `mr-2`
|
|
// Note some LTR locales (i.e. zh) also place AM/PM to the left
|
|
$spinners.push(makeSpinbutton(this.setAmpm, 'ampm', 'b-time-ampm', {
|
|
value: this.modelAmpm,
|
|
max: 1,
|
|
formatterFn: this.formatAmpm,
|
|
ariaLabel: this.labelAmpm,
|
|
// We set `required` as `false`, since this always has a value
|
|
required: false
|
|
}));
|
|
} // Assemble spinners
|
|
|
|
|
|
$spinners = h('div', {
|
|
staticClass: 'd-flex align-items-center justify-content-center mx-auto',
|
|
attrs: {
|
|
role: 'group',
|
|
tabindex: this.disabled || this.readonly ? null : '-1',
|
|
'aria-labelledby': computedAriaLabelledby
|
|
},
|
|
on: {
|
|
keydown: this.onSpinLeftRight,
|
|
click
|
|
/* istanbul ignore next */
|
|
: function click(evt)
|
|
/* istanbul ignore next */
|
|
{
|
|
if (evt.target === evt.currentTarget) {
|
|
_this4.focus();
|
|
}
|
|
}
|
|
}
|
|
}, $spinners); // Selected type display
|
|
|
|
var $value = h('output', {
|
|
staticClass: 'form-control form-control-sm text-center',
|
|
class: {
|
|
disabled: this.disabled || this.readonly
|
|
},
|
|
attrs: {
|
|
id: valueId,
|
|
role: 'status',
|
|
for: spinIds.filter(identity).join(' ') || null,
|
|
tabindex: this.disabled ? null : '-1',
|
|
'aria-live': this.isLive ? 'polite' : 'off',
|
|
'aria-atomic': 'true'
|
|
},
|
|
on: {
|
|
// Transfer focus/click to focus hours spinner
|
|
click: this.focus,
|
|
focus: this.focus
|
|
}
|
|
}, [h('bdi', this.formattedTimeString), this.computedHMS ? h('span', {
|
|
staticClass: 'sr-only'
|
|
}, " (".concat(this.labelSelected, ") ")) : '']);
|
|
var $header = h('header', {
|
|
staticClass: 'b-time-header',
|
|
class: {
|
|
'sr-only': this.hideHeader
|
|
}
|
|
}, [$value]); // Optional bottom slot
|
|
|
|
var $slot = this.normalizeSlot();
|
|
$slot = $slot ? h('footer', {
|
|
staticClass: 'b-time-footer'
|
|
}, $slot) : h();
|
|
return h('div', {
|
|
staticClass: 'b-time d-inline-flex flex-column text-center',
|
|
attrs: {
|
|
role: 'group',
|
|
lang: this.computedLang || null,
|
|
'aria-labelledby': computedAriaLabelledby || null,
|
|
'aria-disabled': this.disabled ? 'true' : null,
|
|
'aria-readonly': this.readonly && !this.disabled ? 'true' : null
|
|
}
|
|
}, [$header, $spinners, $slot]);
|
|
}
|
|
});
|
|
|
|
// @vue/component
|
|
|
|
var BFormTimepicker = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_FORM_TIMEPICKER,
|
|
// The mixins order determines the order of appearance in the props reference section
|
|
mixins: [idMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2({}, props$M), omit(props$H, ['id', 'value', 'formattedValue', 'rtl', 'lang'])), {}, {
|
|
resetValue: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
buttonOnly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonVariant: {
|
|
// Applicable in button only mode
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
nowButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelNowButton: {
|
|
type: String,
|
|
default: 'Select now'
|
|
},
|
|
nowButtonVariant: {
|
|
type: String,
|
|
default: 'outline-primary'
|
|
},
|
|
resetButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelResetButton: {
|
|
type: String,
|
|
default: 'Reset'
|
|
},
|
|
resetButtonVariant: {
|
|
type: String,
|
|
default: 'outline-danger'
|
|
},
|
|
noCloseButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelCloseButton: {
|
|
type: String,
|
|
default: 'Close'
|
|
},
|
|
closeButtonVariant: {
|
|
type: String,
|
|
default: 'outline-secondary'
|
|
}
|
|
}), NAME_FORM_TIMEPICKER),
|
|
data: function data() {
|
|
return {
|
|
// We always use `HH:mm:ss` value internally
|
|
localHMS: this.value || '',
|
|
// Context data from BTime
|
|
localLocale: null,
|
|
isRTL: false,
|
|
formattedValue: '',
|
|
// If the menu is opened
|
|
isVisible: false
|
|
};
|
|
},
|
|
computed: {
|
|
computedLang: function computedLang() {
|
|
return (this.localLocale || '').replace(/-u-.*$/i, '') || null;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newVal) {
|
|
this.localHMS = newVal || '';
|
|
},
|
|
localHMS: function localHMS(newVal) {
|
|
// We only update the v-model value when the timepicker
|
|
// is open, to prevent cursor jumps when bound to a
|
|
// text input in button only mode
|
|
if (this.isVisible) {
|
|
this.$emit('input', newVal || '');
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// Public methods
|
|
focus: function focus() {
|
|
if (!this.disabled) {
|
|
attemptFocus(this.$refs.control);
|
|
}
|
|
},
|
|
blur: function blur() {
|
|
if (!this.disabled) {
|
|
attemptBlur(this.$refs.control);
|
|
}
|
|
},
|
|
// Private methods
|
|
setAndClose: function setAndClose(value) {
|
|
var _this = this;
|
|
|
|
this.localHMS = value;
|
|
this.$nextTick(function () {
|
|
_this.$refs.control.hide(true);
|
|
});
|
|
},
|
|
onInput: function onInput(hms) {
|
|
if (this.localHMS !== hms) {
|
|
this.localHMS = hms;
|
|
}
|
|
},
|
|
onContext: function onContext(ctx) {
|
|
var isRTL = ctx.isRTL,
|
|
locale = ctx.locale,
|
|
value = ctx.value,
|
|
formatted = ctx.formatted;
|
|
this.isRTL = isRTL;
|
|
this.localLocale = locale;
|
|
this.formattedValue = formatted;
|
|
this.localHMS = value || ''; // Re-emit the context event
|
|
|
|
this.$emit('context', ctx);
|
|
},
|
|
onNowButton: function onNowButton() {
|
|
var now = new Date();
|
|
var hours = now.getHours();
|
|
var minutes = now.getMinutes();
|
|
var seconds = this.showSeconds ? now.getSeconds() : 0;
|
|
var value = [hours, minutes, seconds].map(function (v) {
|
|
return "00".concat(v || '').slice(-2);
|
|
}).join(':');
|
|
this.setAndClose(value);
|
|
},
|
|
onResetButton: function onResetButton() {
|
|
this.setAndClose(this.resetValue);
|
|
},
|
|
onCloseButton: function onCloseButton() {
|
|
this.$refs.control.hide(true);
|
|
},
|
|
onShow: function onShow() {
|
|
this.isVisible = true;
|
|
},
|
|
onShown: function onShown() {
|
|
var _this2 = this;
|
|
|
|
this.$nextTick(function () {
|
|
attemptFocus(_this2.$refs.time);
|
|
|
|
_this2.$emit('shown');
|
|
});
|
|
},
|
|
onHidden: function onHidden() {
|
|
this.isVisible = false;
|
|
this.$emit('hidden');
|
|
},
|
|
// Render function helpers
|
|
defaultButtonFn: function defaultButtonFn(_ref) {
|
|
var isHovered = _ref.isHovered,
|
|
hasFocus = _ref.hasFocus;
|
|
return this.$createElement(isHovered || hasFocus ? BIconClockFill : BIconClock, {
|
|
attrs: {
|
|
'aria-hidden': 'true'
|
|
}
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var localHMS = this.localHMS,
|
|
disabled = this.disabled,
|
|
readonly = this.readonly,
|
|
$props = this.$props;
|
|
var placeholder = isUndefinedOrNull(this.placeholder) ? this.labelNoTimeSelected : this.placeholder; // Footer buttons
|
|
|
|
var $footer = [];
|
|
|
|
if (this.nowButton) {
|
|
var label = this.labelNowButton;
|
|
$footer.push(h(BButton, {
|
|
key: 'now-btn',
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled || readonly,
|
|
variant: this.nowButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': label || null
|
|
},
|
|
on: {
|
|
click: this.onNowButton
|
|
}
|
|
}, label));
|
|
}
|
|
|
|
if (this.resetButton) {
|
|
if ($footer.length > 0) {
|
|
// Add a "spacer" between buttons (' ')
|
|
$footer.push(h('span', "\xA0"));
|
|
}
|
|
|
|
var _label = this.labelResetButton;
|
|
$footer.push(h(BButton, {
|
|
key: 'reset-btn',
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled || readonly,
|
|
variant: this.resetButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': _label || null
|
|
},
|
|
on: {
|
|
click: this.onResetButton
|
|
}
|
|
}, _label));
|
|
}
|
|
|
|
if (!this.noCloseButton) {
|
|
if ($footer.length > 0) {
|
|
// Add a "spacer" between buttons (' ')
|
|
$footer.push(h('span', "\xA0"));
|
|
}
|
|
|
|
var _label2 = this.labelCloseButton;
|
|
$footer.push(h(BButton, {
|
|
key: 'close-btn',
|
|
props: {
|
|
size: 'sm',
|
|
disabled: disabled,
|
|
variant: this.closeButtonVariant
|
|
},
|
|
attrs: {
|
|
'aria-label': _label2 || null
|
|
},
|
|
on: {
|
|
click: this.onCloseButton
|
|
}
|
|
}, _label2));
|
|
}
|
|
|
|
if ($footer.length > 0) {
|
|
$footer = [h('div', {
|
|
staticClass: 'b-form-date-controls d-flex flex-wrap',
|
|
class: {
|
|
'justify-content-between': $footer.length > 1,
|
|
'justify-content-end': $footer.length < 2
|
|
}
|
|
}, $footer)];
|
|
}
|
|
|
|
var $time = h(BTime, {
|
|
ref: 'time',
|
|
staticClass: 'b-form-time-control',
|
|
props: _objectSpread2(_objectSpread2({}, pluckProps(props$M, $props)), {}, {
|
|
value: localHMS,
|
|
hidden: !this.isVisible
|
|
}),
|
|
on: {
|
|
input: this.onInput,
|
|
context: this.onContext
|
|
}
|
|
}, $footer);
|
|
return h(BVFormBtnLabelControl, {
|
|
ref: 'control',
|
|
staticClass: 'b-form-timepicker',
|
|
props: _objectSpread2(_objectSpread2({}, pluckProps(props$H, $props)), {}, {
|
|
id: this.safeId(),
|
|
value: localHMS,
|
|
formattedValue: localHMS ? this.formattedValue : '',
|
|
placeholder: placeholder,
|
|
rtl: this.isRTL,
|
|
lang: this.computedLang
|
|
}),
|
|
on: {
|
|
show: this.onShow,
|
|
shown: this.onShown,
|
|
hidden: this.onHidden
|
|
},
|
|
scopedSlots: {
|
|
'button-content': this.$scopedSlots['button-content'] || this.defaultButtonFn
|
|
}
|
|
}, [$time]);
|
|
}
|
|
});
|
|
|
|
var FormTimepickerPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BFormTimepicker: BFormTimepicker,
|
|
BTimepicker: BFormTimepicker
|
|
}
|
|
});
|
|
|
|
var ImagePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BImg: BImg,
|
|
BImgLazy: BImgLazy
|
|
}
|
|
});
|
|
|
|
var props$N = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_INPUT_GROUP_TEXT); // @vue/component
|
|
|
|
var BInputGroupText = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_INPUT_GROUP_TEXT,
|
|
functional: true,
|
|
props: props$N,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'input-group-text'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var commonProps$1 = {
|
|
id: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
isText: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}; // @vue/component
|
|
|
|
var BInputGroupAddon = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_INPUT_GROUP_ADDON,
|
|
functional: true,
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2({}, commonProps$1), {}, {
|
|
append: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_INPUT_GROUP_ADDON),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
class: {
|
|
'input-group-append': props.append,
|
|
'input-group-prepend': !props.append
|
|
},
|
|
attrs: {
|
|
id: props.id
|
|
}
|
|
}), props.isText ? [h(BInputGroupText, children)] : children);
|
|
}
|
|
});
|
|
|
|
var BInputGroupAppend = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_INPUT_GROUP_APPEND,
|
|
functional: true,
|
|
props: makePropsConfigurable(commonProps$1, NAME_INPUT_GROUP_APPEND),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
// Pass all our data down to child, and set `append` to `true`
|
|
return h(BInputGroupAddon, a(data, {
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
append: true
|
|
})
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var BInputGroupPrepend = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_INPUT_GROUP_PREPEND,
|
|
functional: true,
|
|
props: makePropsConfigurable(commonProps$1, NAME_INPUT_GROUP_PREPEND),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
// pass all our props/attrs down to child, and set`append` to false
|
|
return h(BInputGroupAddon, a(data, {
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
append: false
|
|
})
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$O = makePropsConfigurable({
|
|
id: {
|
|
type: String
|
|
},
|
|
size: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
prepend: {
|
|
type: String
|
|
},
|
|
prependHtml: {
|
|
type: String
|
|
},
|
|
append: {
|
|
type: String
|
|
},
|
|
appendHtml: {
|
|
type: String
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_INPUT_GROUP); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BInputGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_INPUT_GROUP,
|
|
functional: true,
|
|
props: props$O,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var prepend = props.prepend,
|
|
prependHtml = props.prependHtml,
|
|
append = props.append,
|
|
appendHtml = props.appendHtml,
|
|
size = props.size;
|
|
var $scopedSlots = scopedSlots || {};
|
|
var $slots = slots();
|
|
var slotScope = {};
|
|
var $prepend = h();
|
|
var hasPrependSlot = hasNormalizedSlot(SLOT_NAME_PREPEND, $scopedSlots, $slots);
|
|
|
|
if (hasPrependSlot || prepend || prependHtml) {
|
|
$prepend = h(BInputGroupPrepend, [hasPrependSlot ? normalizeSlot(SLOT_NAME_PREPEND, slotScope, $scopedSlots, $slots) : h(BInputGroupText, {
|
|
domProps: htmlOrText(prependHtml, prepend)
|
|
})]);
|
|
}
|
|
|
|
var $append = h();
|
|
var hasAppendSlot = hasNormalizedSlot(SLOT_NAME_APPEND, $scopedSlots, $slots);
|
|
|
|
if (hasAppendSlot || append || appendHtml) {
|
|
$append = h(BInputGroupAppend, [hasAppendSlot ? normalizeSlot(SLOT_NAME_APPEND, slotScope, $scopedSlots, $slots) : h(BInputGroupText, {
|
|
domProps: htmlOrText(appendHtml, append)
|
|
})]);
|
|
}
|
|
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'input-group',
|
|
class: _defineProperty({}, "input-group-".concat(size), size),
|
|
attrs: {
|
|
id: props.id || null,
|
|
role: 'group'
|
|
}
|
|
}), [$prepend, normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots), $append]);
|
|
}
|
|
});
|
|
|
|
var InputGroupPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BInputGroup: BInputGroup,
|
|
BInputGroupAddon: BInputGroupAddon,
|
|
BInputGroupPrepend: BInputGroupPrepend,
|
|
BInputGroupAppend: BInputGroupAppend,
|
|
BInputGroupText: BInputGroupText
|
|
}
|
|
});
|
|
|
|
var props$P = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
fluid: {
|
|
// String breakpoint name new in Bootstrap v4.4.x
|
|
type: [Boolean, String],
|
|
default: false
|
|
}
|
|
}, NAME_CONTAINER); // @vue/component
|
|
|
|
var BContainer = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_CONTAINER,
|
|
functional: true,
|
|
props: props$P,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
class: _defineProperty({
|
|
container: !(props.fluid || props.fluid === ''),
|
|
'container-fluid': props.fluid === true || props.fluid === ''
|
|
}, "container-".concat(props.fluid), props.fluid && props.fluid !== true)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$Q = makePropsConfigurable({
|
|
fluid: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
containerFluid: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
header: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerHtml: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerTag: {
|
|
type: String,
|
|
default: 'h1'
|
|
},
|
|
headerLevel: {
|
|
type: [Number, String],
|
|
default: '3'
|
|
},
|
|
lead: {
|
|
type: String // default: null
|
|
|
|
},
|
|
leadHtml: {
|
|
type: String // default: null
|
|
|
|
},
|
|
leadTag: {
|
|
type: String,
|
|
default: 'p'
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
bgVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
borderVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
textVariant: {
|
|
type: String // default: undefined
|
|
|
|
}
|
|
}, NAME_JUMBOTRON); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BJumbotron = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_JUMBOTRON,
|
|
functional: true,
|
|
props: props$Q,
|
|
render: function render(h, _ref) {
|
|
var _class2;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var header = props.header,
|
|
headerHtml = props.headerHtml,
|
|
lead = props.lead,
|
|
leadHtml = props.leadHtml,
|
|
textVariant = props.textVariant,
|
|
bgVariant = props.bgVariant,
|
|
borderVariant = props.borderVariant;
|
|
var $scopedSlots = scopedSlots || {};
|
|
var $slots = slots();
|
|
var slotScope = {};
|
|
var $header = h();
|
|
var hasHeaderSlot = hasNormalizedSlot(SLOT_NAME_HEADER, $scopedSlots, $slots);
|
|
|
|
if (hasHeaderSlot || header || headerHtml) {
|
|
var headerLevel = props.headerLevel;
|
|
$header = h(props.headerTag, {
|
|
class: _defineProperty({}, "display-".concat(headerLevel), headerLevel),
|
|
domProps: hasHeaderSlot ? {} : htmlOrText(headerHtml, header)
|
|
}, normalizeSlot(SLOT_NAME_HEADER, slotScope, $scopedSlots, $slots));
|
|
}
|
|
|
|
var $lead = h();
|
|
var hasLeadSlot = hasNormalizedSlot(SLOT_NAME_LEAD, $scopedSlots, $slots);
|
|
|
|
if (hasLeadSlot || lead || leadHtml) {
|
|
$lead = h(props.leadTag, {
|
|
staticClass: 'lead',
|
|
domProps: hasLeadSlot ? {} : htmlOrText(leadHtml, lead)
|
|
}, normalizeSlot(SLOT_NAME_LEAD, slotScope, $scopedSlots, $slots));
|
|
}
|
|
|
|
var $children = [$header, $lead, normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots)]; // If fluid, wrap content in a container
|
|
|
|
if (props.fluid) {
|
|
$children = [h(BContainer, {
|
|
props: {
|
|
fluid: props.containerFluid
|
|
}
|
|
}, $children)];
|
|
}
|
|
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'jumbotron',
|
|
class: (_class2 = {
|
|
'jumbotron-fluid': props.fluid
|
|
}, _defineProperty(_class2, "text-".concat(textVariant), textVariant), _defineProperty(_class2, "bg-".concat(bgVariant), bgVariant), _defineProperty(_class2, "border-".concat(borderVariant), borderVariant), _defineProperty(_class2, "border", borderVariant), _class2)
|
|
}), $children);
|
|
}
|
|
});
|
|
|
|
var JumbotronPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BJumbotron: BJumbotron
|
|
}
|
|
});
|
|
|
|
var COMMON_ALIGNMENT = ['start', 'end', 'center']; // Generates a prop object with a type of `[String, Number]`
|
|
|
|
var strNum$1 = function strNum() {
|
|
return {
|
|
type: [String, Number],
|
|
default: null
|
|
};
|
|
}; // Compute a `row-cols-{breakpoint}-{cols}` class name
|
|
// Memoized function for better performance on generating class names
|
|
|
|
|
|
var computeRowColsClass = memoize(function (breakpoint, cols) {
|
|
cols = trim(toString$1(cols));
|
|
return cols ? lowerCase(['row-cols', breakpoint, cols].filter(identity).join('-')) : null;
|
|
}); // Get the breakpoint name from the `rowCols` prop name
|
|
// Memoized function for better performance on extracting breakpoint names
|
|
|
|
var computeRowColsBreakpoint = memoize(function (prop) {
|
|
return lowerCase(prop.replace('cols', ''));
|
|
}); // Cached copy of the `row-cols` breakpoint prop names
|
|
// Will be populated when the props are generated
|
|
|
|
var rowColsPropList = []; // Lazy evaled props factory for <b-row> (called only once,
|
|
// the first time the component is used)
|
|
|
|
var generateProps$2 = function generateProps() {
|
|
// Grab the breakpoints from the cached config (including the '' (xs) breakpoint)
|
|
var breakpoints = getBreakpointsUpCached(); // Supports classes like: `row-cols-2`, `row-cols-md-4`, `row-cols-xl-6`
|
|
|
|
var rowColsProps = breakpoints.reduce(function (props, breakpoint) {
|
|
props[suffixPropName(breakpoint, 'cols')] = strNum$1();
|
|
return props;
|
|
}, create(null)); // Cache the row-cols prop names
|
|
|
|
rowColsPropList = keys(rowColsProps); // Return the generated props
|
|
|
|
return makePropsConfigurable(_objectSpread2({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
noGutters: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
alignV: {
|
|
type: String,
|
|
default: null,
|
|
validator: function validator(value) {
|
|
return arrayIncludes(concat(COMMON_ALIGNMENT, 'baseline', 'stretch'), value);
|
|
}
|
|
},
|
|
alignH: {
|
|
type: String,
|
|
default: null,
|
|
validator: function validator(value) {
|
|
return arrayIncludes(concat(COMMON_ALIGNMENT, 'between', 'around'), value);
|
|
}
|
|
},
|
|
alignContent: {
|
|
type: String,
|
|
default: null,
|
|
validator: function validator(value) {
|
|
return arrayIncludes(concat(COMMON_ALIGNMENT, 'between', 'around', 'stretch'), value);
|
|
}
|
|
}
|
|
}, rowColsProps), NAME_ROW);
|
|
}; // We do not use `Vue.extend()` here as that would evaluate the props
|
|
// immediately, which we do not want to happen
|
|
// @vue/component
|
|
|
|
|
|
var BRow = {
|
|
name: NAME_ROW,
|
|
functional: true,
|
|
|
|
get props() {
|
|
// Allow props to be lazy evaled on first access and
|
|
// then they become a non-getter afterwards
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
|
|
delete this.props;
|
|
this.props = generateProps$2();
|
|
return this.props;
|
|
},
|
|
|
|
render: function render(h, _ref) {
|
|
var _classList$push;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var classList = []; // Loop through row-cols breakpoint props and generate the classes
|
|
|
|
rowColsPropList.forEach(function (prop) {
|
|
var c = computeRowColsClass(computeRowColsBreakpoint(prop), props[prop]); // If a class is returned, push it onto the array
|
|
|
|
if (c) {
|
|
classList.push(c);
|
|
}
|
|
});
|
|
classList.push((_classList$push = {
|
|
'no-gutters': props.noGutters
|
|
}, _defineProperty(_classList$push, "align-items-".concat(props.alignV), props.alignV), _defineProperty(_classList$push, "justify-content-".concat(props.alignH), props.alignH), _defineProperty(_classList$push, "align-content-".concat(props.alignContent), props.alignContent), _classList$push));
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'row',
|
|
class: classList
|
|
}), children);
|
|
}
|
|
};
|
|
|
|
var LayoutPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BContainer: BContainer,
|
|
BRow: BRow,
|
|
BCol: BCol,
|
|
BFormRow: BFormRow
|
|
}
|
|
});
|
|
|
|
var LinkPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BLink: BLink
|
|
}
|
|
});
|
|
|
|
var props$R = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
flush: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
horizontal: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
}
|
|
}, NAME_LIST_GROUP); // @vue/component
|
|
|
|
var BListGroup = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_LIST_GROUP,
|
|
functional: true,
|
|
props: props$R,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var horizontal = props.horizontal === '' ? true : props.horizontal;
|
|
horizontal = props.flush ? false : horizontal;
|
|
var componentData = {
|
|
staticClass: 'list-group',
|
|
class: _defineProperty({
|
|
'list-group-flush': props.flush,
|
|
'list-group-horizontal': horizontal === true
|
|
}, "list-group-horizontal-".concat(horizontal), isString(horizontal))
|
|
};
|
|
return h(props.tag, a(data, componentData), children);
|
|
}
|
|
});
|
|
|
|
var actionTags = ['a', 'router-link', 'button', 'b-link']; // --- Props ---
|
|
|
|
var linkProps$3 = omit(props$1, ['event', 'routerTag']);
|
|
delete linkProps$3.href.default;
|
|
delete linkProps$3.to.default;
|
|
var props$S = makePropsConfigurable(_objectSpread2(_objectSpread2({}, linkProps$3), {}, {
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
action: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
button: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
}
|
|
}), NAME_LIST_GROUP_ITEM); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BListGroupItem = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_LIST_GROUP_ITEM,
|
|
functional: true,
|
|
props: props$S,
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var button = props.button,
|
|
variant = props.variant,
|
|
active = props.active,
|
|
disabled = props.disabled;
|
|
var link = isLink(props);
|
|
var tag = button ? 'button' : !link ? props.tag : BLink;
|
|
var action = !!(props.action || link || button || arrayIncludes(actionTags, props.tag));
|
|
var attrs = {};
|
|
var itemProps = {};
|
|
|
|
if (isTag(tag, 'button')) {
|
|
if (!data.attrs || !data.attrs.type) {
|
|
// Add a type for button is one not provided in passed attributes
|
|
attrs.type = 'button';
|
|
}
|
|
|
|
if (props.disabled) {
|
|
// Set disabled attribute if button and disabled
|
|
attrs.disabled = true;
|
|
}
|
|
} else {
|
|
itemProps = pluckProps(linkProps$3, props);
|
|
}
|
|
|
|
return h(tag, a(data, {
|
|
attrs: attrs,
|
|
props: itemProps,
|
|
staticClass: 'list-group-item',
|
|
class: (_class = {}, _defineProperty(_class, "list-group-item-".concat(variant), variant), _defineProperty(_class, 'list-group-item-action', action), _defineProperty(_class, "active", active), _defineProperty(_class, "disabled", disabled), _class)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var ListGroupPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BListGroup: BListGroup,
|
|
BListGroupItem: BListGroupItem
|
|
}
|
|
});
|
|
|
|
var props$T = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
right: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
verticalAlign: {
|
|
type: String,
|
|
default: 'top'
|
|
}
|
|
}, NAME_MEDIA_ASIDE); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BMediaAside = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_MEDIA_ASIDE,
|
|
functional: true,
|
|
props: props$T,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var verticalAlign = props.verticalAlign;
|
|
var align = verticalAlign === 'top' ? 'start' : verticalAlign === 'bottom' ? 'end' :
|
|
/* istanbul ignore next */
|
|
verticalAlign;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'media-aside',
|
|
class: _defineProperty({
|
|
'media-aside-right': props.right
|
|
}, "align-self-".concat(align), align)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$U = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, NAME_MEDIA_BODY); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BMediaBody = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_MEDIA_BODY,
|
|
functional: true,
|
|
props: props$U,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'media-body'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$V = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
noBody: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
rightAlign: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
verticalAlign: {
|
|
type: String,
|
|
default: 'top'
|
|
}
|
|
}, NAME_MEDIA); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BMedia = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_MEDIA,
|
|
functional: true,
|
|
props: props$V,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots,
|
|
children = _ref.children;
|
|
var noBody = props.noBody,
|
|
rightAlign = props.rightAlign,
|
|
verticalAlign = props.verticalAlign;
|
|
var $children = noBody ? children : [];
|
|
|
|
if (!noBody) {
|
|
var slotScope = {};
|
|
var $slots = slots();
|
|
var $scopedSlots = scopedSlots || {};
|
|
$children.push(h(BMediaBody, normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots)));
|
|
var $aside = normalizeSlot('aside', slotScope, $scopedSlots, $slots);
|
|
|
|
if ($aside) {
|
|
$children[rightAlign ? 'push' : 'unshift'](h(BMediaAside, {
|
|
props: {
|
|
right: rightAlign,
|
|
verticalAlign: verticalAlign
|
|
}
|
|
}, $aside));
|
|
}
|
|
}
|
|
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'media'
|
|
}), $children);
|
|
}
|
|
});
|
|
|
|
var MediaPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BMedia: BMedia,
|
|
BMediaAside: BMediaAside,
|
|
BMediaBody: BMediaBody
|
|
}
|
|
});
|
|
|
|
//
|
|
// Single root node portaling of content, which retains parent/child hierarchy
|
|
// Unlike Portal-Vue where portaled content is no longer a descendent of its
|
|
// intended parent components
|
|
//
|
|
// Private components for use by Tooltips, Popovers and Modals
|
|
//
|
|
// Based on vue-simple-portal
|
|
// https://github.com/LinusBorg/vue-simple-portal
|
|
// Transporter target used by BTransporterSingle
|
|
// Supports only a single root element
|
|
// @vue/component
|
|
|
|
var BTransporterTargetSingle = /*#__PURE__*/Vue__default['default'].extend({
|
|
// As an abstract component, it doesn't appear in the $parent chain of
|
|
// components, which means the next parent of any component rendered inside
|
|
// of this one will be the parent from which is was portal'd
|
|
abstract: true,
|
|
name: NAME_TRANSPORTER_TARGET_SINGLE,
|
|
props: {
|
|
nodes: {
|
|
// Even though we only support a single root element,
|
|
// VNodes are always passed as an array
|
|
type: [Array, Function] // default: undefined
|
|
|
|
}
|
|
},
|
|
data: function data(vm) {
|
|
return {
|
|
updatedNodes: vm.nodes
|
|
};
|
|
},
|
|
destroyed: function destroyed() {
|
|
removeNode(this.$el);
|
|
},
|
|
render: function render(h) {
|
|
var nodes = isFunction(this.updatedNodes) ? this.updatedNodes({}) : this.updatedNodes;
|
|
nodes = concat(nodes).filter(Boolean);
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes && nodes.length > 0 && !nodes[0].text) {
|
|
return nodes[0];
|
|
} else {
|
|
/* istanbul ignore next */
|
|
return h();
|
|
}
|
|
}
|
|
}); // This component has no root element, so only a single VNode is allowed
|
|
// @vue/component
|
|
|
|
var BTransporterSingle = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TRANSPORTER_SINGLE,
|
|
mixins: [normalizeSlotMixin],
|
|
props: {
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
container: {
|
|
// String: CSS selector,
|
|
// HTMLElement: Element reference
|
|
// Mainly needed for tooltips/popovers inside modals
|
|
type: [String, HTMLElement],
|
|
default: 'body'
|
|
},
|
|
tag: {
|
|
// This should be set to match the root element type
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
},
|
|
watch: {
|
|
disabled: {
|
|
immediate: true,
|
|
handler: function handler(disabled) {
|
|
disabled ? this.unmountTarget() : this.$nextTick(this.mountTarget);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_defaultFn = null;
|
|
this.$_target = null;
|
|
},
|
|
beforeMount: function beforeMount() {
|
|
this.mountTarget();
|
|
},
|
|
updated: function updated() {
|
|
// We need to make sure that all children have completed updating
|
|
// before rendering in the target
|
|
// `vue-simple-portal` has the this in a `$nextTick()`,
|
|
// while `portal-vue` doesn't
|
|
// Just trying to see if the `$nextTick()` delay is required or not
|
|
// Since all slots in Vue 2.6.x are always functions
|
|
this.updateTarget();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.unmountTarget();
|
|
this.$_defaultFn = null;
|
|
},
|
|
methods: {
|
|
// Get the element which the target should be appended to
|
|
getContainer: function getContainer() {
|
|
/* istanbul ignore else */
|
|
if (isBrowser) {
|
|
var container = this.container;
|
|
return isString(container) ? select(container) : container;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
// Mount the target
|
|
mountTarget: function mountTarget() {
|
|
if (!this.$_target) {
|
|
var container = this.getContainer();
|
|
|
|
if (container) {
|
|
var el = document.createElement('div');
|
|
container.appendChild(el);
|
|
this.$_target = new BTransporterTargetSingle({
|
|
el: el,
|
|
parent: this,
|
|
propsData: {
|
|
// Initial nodes to be rendered
|
|
nodes: concat(this.normalizeSlot())
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
// Update the content of the target
|
|
updateTarget: function updateTarget() {
|
|
if (isBrowser && this.$_target) {
|
|
var defaultFn = this.$scopedSlots.default;
|
|
|
|
if (!this.disabled) {
|
|
/* istanbul ignore else: only applicable in Vue 2.5.x */
|
|
if (defaultFn && this.$_defaultFn !== defaultFn) {
|
|
// We only update the target component if the scoped slot
|
|
// function is a fresh one. The new slot syntax (since Vue 2.6)
|
|
// can cache unchanged slot functions and we want to respect that here
|
|
this.$_target.updatedNodes = defaultFn;
|
|
} else if (!defaultFn) {
|
|
// We also need to be back compatible with non-scoped default slot (i.e. 2.5.x)
|
|
this.$_target.updatedNodes = this.$slots.default;
|
|
}
|
|
} // Update the scoped slot function cache
|
|
|
|
|
|
this.$_defaultFn = defaultFn;
|
|
}
|
|
},
|
|
// Unmount the target
|
|
unmountTarget: function unmountTarget() {
|
|
this.$_target && this.$_target.$destroy();
|
|
this.$_target = null;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
if (this.disabled) {
|
|
var nodes = concat(this.normalizeSlot()).filter(identity);
|
|
|
|
if (nodes.length > 0 && !nodes[0].text) {
|
|
return nodes[0];
|
|
}
|
|
}
|
|
|
|
return h();
|
|
}
|
|
});
|
|
|
|
var PROP$1 = '$_bv_documentHandlers_'; // @vue/component
|
|
|
|
var listenOnDocumentMixin = {
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
/* istanbul ignore next */
|
|
if (!isBrowser) {
|
|
return;
|
|
} // Declare non-reactive property
|
|
// Object of arrays, keyed by event name,
|
|
// where value is an array of handlers
|
|
// Prop will be defined on client only
|
|
|
|
|
|
this[PROP$1] = {}; // Set up our beforeDestroy handler (client only)
|
|
|
|
this.$once('hook:beforeDestroy', function () {
|
|
var items = _this[PROP$1] || {}; // Immediately delete this[PROP] to prevent the
|
|
// listenOn/Off methods from running (which may occur
|
|
// due to requestAnimationFrame/transition delays)
|
|
|
|
delete _this[PROP$1]; // Remove all registered event handlers
|
|
|
|
keys(items).forEach(function (evtName) {
|
|
var handlers = items[evtName] || [];
|
|
handlers.forEach(function (handler) {
|
|
return eventOff(document, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
});
|
|
});
|
|
},
|
|
methods: {
|
|
listenDocument: function listenDocument(on, evtName, handler) {
|
|
on ? this.listenOnDocument(evtName, handler) : this.listenOffDocument(evtName, handler);
|
|
},
|
|
listenOnDocument: function listenOnDocument(evtName, handler) {
|
|
if (this[PROP$1] && isString(evtName) && isFunction(handler)) {
|
|
this[PROP$1][evtName] = this[PROP$1][evtName] || [];
|
|
|
|
if (!arrayIncludes(this[PROP$1][evtName], handler)) {
|
|
this[PROP$1][evtName].push(handler);
|
|
eventOn(document, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
}
|
|
},
|
|
listenOffDocument: function listenOffDocument(evtName, handler) {
|
|
if (this[PROP$1] && isString(evtName) && isFunction(handler)) {
|
|
eventOff(document, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
this[PROP$1][evtName] = (this[PROP$1][evtName] || []).filter(function (h) {
|
|
return h !== handler;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var PROP$2 = '$_bv_windowHandlers_'; // @vue/component
|
|
|
|
var listenOnWindowMixin = {
|
|
beforeCreate: function beforeCreate() {
|
|
// Declare non-reactive property
|
|
// Object of arrays, keyed by event name,
|
|
// where value is an array of handlers
|
|
this[PROP$2] = {};
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
if (isBrowser) {
|
|
var items = this[PROP$2]; // Immediately delete this[PROP] to prevent the
|
|
// listenOn/Off methods from running (which may occur
|
|
// due to requestAnimationFrame delays)
|
|
|
|
delete this[PROP$2]; // Remove all registered event handlers
|
|
|
|
keys(items).forEach(function (evtName) {
|
|
var handlers = items[evtName] || [];
|
|
handlers.forEach(function (handler) {
|
|
return eventOff(window, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
listenWindow: function listenWindow(on, evtName, handler) {
|
|
on ? this.listenOnWindow(evtName, handler) : this.listenOffWindow(evtName, handler);
|
|
},
|
|
listenOnWindow: function listenOnWindow(evtName, handler) {
|
|
if (isBrowser && this[PROP$2] && isString(evtName) && isFunction(handler)) {
|
|
this[PROP$2][evtName] = this[PROP$2][evtName] || [];
|
|
|
|
if (!arrayIncludes(this[PROP$2][evtName], handler)) {
|
|
this[PROP$2][evtName].push(handler);
|
|
eventOn(window, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
}
|
|
},
|
|
listenOffWindow: function listenOffWindow(evtName, handler) {
|
|
if (isBrowser && this[PROP$2] && isString(evtName) && isFunction(handler)) {
|
|
eventOff(window, evtName, handler, EVENT_OPTIONS_NO_CAPTURE);
|
|
this[PROP$2][evtName] = (this[PROP$2][evtName] || []).filter(function (h) {
|
|
return h !== handler;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// This method returns a component's scoped style attribute name: `data-v-xxxxxxx`
|
|
// The `_scopeId` options property is added by vue-loader when using scoped styles
|
|
// and will be `undefined` if no scoped styles are in use
|
|
var getScopeId = function getScopeId(vm) {
|
|
var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
return vm ? vm.$options._scopeId || defaultValue : defaultValue;
|
|
};
|
|
|
|
var scopedStyleAttrsMixin = {
|
|
computed: {
|
|
scopedStyleAttrs: function scopedStyleAttrs() {
|
|
var scopeId = getScopeId(this.$parent);
|
|
return scopeId ? _defineProperty({}, scopeId, '') : {};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Private ModalManager helper
|
|
* Handles controlling modal stacking zIndexes and body adjustments/classes
|
|
*/
|
|
// Default modal backdrop z-index
|
|
|
|
var DEFAULT_ZINDEX = 1040; // Selectors for padding/margin adjustments
|
|
|
|
var Selector = {
|
|
FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
|
|
STICKY_CONTENT: '.sticky-top',
|
|
NAVBAR_TOGGLER: '.navbar-toggler'
|
|
}; // @vue/component
|
|
|
|
var ModalManager = /*#__PURE__*/Vue__default['default'].extend({
|
|
data: function data() {
|
|
return {
|
|
modals: [],
|
|
baseZIndex: null,
|
|
scrollbarWidth: null,
|
|
isBodyOverflowing: false
|
|
};
|
|
},
|
|
computed: {
|
|
modalCount: function modalCount() {
|
|
return this.modals.length;
|
|
},
|
|
modalsAreOpen: function modalsAreOpen() {
|
|
return this.modalCount > 0;
|
|
}
|
|
},
|
|
watch: {
|
|
modalCount: function modalCount(newCount, oldCount) {
|
|
if (isBrowser) {
|
|
this.getScrollbarWidth();
|
|
|
|
if (newCount > 0 && oldCount === 0) {
|
|
// Transitioning to modal(s) open
|
|
this.checkScrollbar();
|
|
this.setScrollbar();
|
|
addClass(document.body, 'modal-open');
|
|
} else if (newCount === 0 && oldCount > 0) {
|
|
// Transitioning to modal(s) closed
|
|
this.resetScrollbar();
|
|
removeClass(document.body, 'modal-open');
|
|
}
|
|
|
|
setAttr(document.body, 'data-modal-open-count', String(newCount));
|
|
}
|
|
},
|
|
modals: function modals(newVal) {
|
|
var _this = this;
|
|
|
|
this.checkScrollbar();
|
|
requestAF(function () {
|
|
_this.updateModals(newVal || []);
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
// Public methods
|
|
registerModal: function registerModal(modal) {
|
|
var _this2 = this;
|
|
|
|
// Register the modal if not already registered
|
|
if (modal && this.modals.indexOf(modal) === -1) {
|
|
// Add modal to modals array
|
|
this.modals.push(modal);
|
|
modal.$once('hook:beforeDestroy', function () {
|
|
_this2.unregisterModal(modal);
|
|
});
|
|
}
|
|
},
|
|
unregisterModal: function unregisterModal(modal) {
|
|
var index = this.modals.indexOf(modal);
|
|
|
|
if (index > -1) {
|
|
// Remove modal from modals array
|
|
this.modals.splice(index, 1); // Reset the modal's data
|
|
|
|
if (!(modal._isBeingDestroyed || modal._isDestroyed)) {
|
|
this.resetModal(modal);
|
|
}
|
|
}
|
|
},
|
|
getBaseZIndex: function getBaseZIndex() {
|
|
if (isNull(this.baseZIndex) && isBrowser) {
|
|
// Create a temporary `div.modal-backdrop` to get computed z-index
|
|
var div = document.createElement('div');
|
|
addClass(div, 'modal-backdrop');
|
|
addClass(div, 'd-none');
|
|
setStyle(div, 'display', 'none');
|
|
document.body.appendChild(div);
|
|
this.baseZIndex = toInteger(getCS(div).zIndex, DEFAULT_ZINDEX);
|
|
document.body.removeChild(div);
|
|
}
|
|
|
|
return this.baseZIndex || DEFAULT_ZINDEX;
|
|
},
|
|
getScrollbarWidth: function getScrollbarWidth() {
|
|
if (isNull(this.scrollbarWidth) && isBrowser) {
|
|
// Create a temporary `div.measure-scrollbar` to get computed z-index
|
|
var div = document.createElement('div');
|
|
addClass(div, 'modal-scrollbar-measure');
|
|
document.body.appendChild(div);
|
|
this.scrollbarWidth = getBCR(div).width - div.clientWidth;
|
|
document.body.removeChild(div);
|
|
}
|
|
|
|
return this.scrollbarWidth || 0;
|
|
},
|
|
// Private methods
|
|
updateModals: function updateModals(modals) {
|
|
var _this3 = this;
|
|
|
|
var baseZIndex = this.getBaseZIndex();
|
|
var scrollbarWidth = this.getScrollbarWidth();
|
|
modals.forEach(function (modal, index) {
|
|
// We update data values on each modal
|
|
modal.zIndex = baseZIndex + index;
|
|
modal.scrollbarWidth = scrollbarWidth;
|
|
modal.isTop = index === _this3.modals.length - 1;
|
|
modal.isBodyOverflowing = _this3.isBodyOverflowing;
|
|
});
|
|
},
|
|
resetModal: function resetModal(modal) {
|
|
if (modal) {
|
|
modal.zIndex = this.getBaseZIndex();
|
|
modal.isTop = true;
|
|
modal.isBodyOverflowing = false;
|
|
}
|
|
},
|
|
checkScrollbar: function checkScrollbar() {
|
|
// Determine if the body element is overflowing
|
|
var _getBCR = getBCR(document.body),
|
|
left = _getBCR.left,
|
|
right = _getBCR.right;
|
|
|
|
this.isBodyOverflowing = left + right < window.innerWidth;
|
|
},
|
|
setScrollbar: function setScrollbar() {
|
|
var body = document.body; // Storage place to cache changes to margins and padding
|
|
// Note: This assumes the following element types are not added to the
|
|
// document after the modal has opened.
|
|
|
|
body._paddingChangedForModal = body._paddingChangedForModal || [];
|
|
body._marginChangedForModal = body._marginChangedForModal || [];
|
|
|
|
if (this.isBodyOverflowing) {
|
|
var scrollbarWidth = this.scrollbarWidth; // Adjust fixed content padding
|
|
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
|
|
selectAll(Selector.FIXED_CONTENT).forEach(function (el) {
|
|
var actualPadding = getStyle(el, 'paddingRight') || '';
|
|
setAttr(el, 'data-padding-right', actualPadding);
|
|
setStyle(el, 'paddingRight', "".concat(toFloat(getCS(el).paddingRight, 0) + scrollbarWidth, "px"));
|
|
|
|
body._paddingChangedForModal.push(el);
|
|
}); // Adjust sticky content margin
|
|
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
|
|
selectAll(Selector.STICKY_CONTENT).forEach(function (el)
|
|
/* istanbul ignore next */
|
|
{
|
|
var actualMargin = getStyle(el, 'marginRight') || '';
|
|
setAttr(el, 'data-margin-right', actualMargin);
|
|
setStyle(el, 'marginRight', "".concat(toFloat(getCS(el).marginRight, 0) - scrollbarWidth, "px"));
|
|
|
|
body._marginChangedForModal.push(el);
|
|
}); // Adjust <b-navbar-toggler> margin
|
|
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
|
|
selectAll(Selector.NAVBAR_TOGGLER).forEach(function (el)
|
|
/* istanbul ignore next */
|
|
{
|
|
var actualMargin = getStyle(el, 'marginRight') || '';
|
|
setAttr(el, 'data-margin-right', actualMargin);
|
|
setStyle(el, 'marginRight', "".concat(toFloat(getCS(el).marginRight, 0) + scrollbarWidth, "px"));
|
|
|
|
body._marginChangedForModal.push(el);
|
|
}); // Adjust body padding
|
|
|
|
var actualPadding = getStyle(body, 'paddingRight') || '';
|
|
setAttr(body, 'data-padding-right', actualPadding);
|
|
setStyle(body, 'paddingRight', "".concat(toFloat(getCS(body).paddingRight, 0) + scrollbarWidth, "px"));
|
|
}
|
|
},
|
|
resetScrollbar: function resetScrollbar() {
|
|
var body = document.body;
|
|
|
|
if (body._paddingChangedForModal) {
|
|
// Restore fixed content padding
|
|
body._paddingChangedForModal.forEach(function (el) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
if (hasAttr(el, 'data-padding-right')) {
|
|
setStyle(el, 'paddingRight', getAttr(el, 'data-padding-right') || '');
|
|
removeAttr(el, 'data-padding-right');
|
|
}
|
|
});
|
|
}
|
|
|
|
if (body._marginChangedForModal) {
|
|
// Restore sticky content and navbar-toggler margin
|
|
body._marginChangedForModal.forEach(function (el) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
if (hasAttr(el, 'data-margin-right')) {
|
|
setStyle(el, 'marginRight', getAttr(el, 'data-margin-right') || '');
|
|
removeAttr(el, 'data-margin-right');
|
|
}
|
|
});
|
|
}
|
|
|
|
body._paddingChangedForModal = null;
|
|
body._marginChangedForModal = null; // Restore body padding
|
|
|
|
if (hasAttr(body, 'data-padding-right')) {
|
|
setStyle(body, 'paddingRight', getAttr(body, 'data-padding-right') || '');
|
|
removeAttr(body, 'data-padding-right');
|
|
}
|
|
}
|
|
}
|
|
}); // Create and export our modal manager instance
|
|
|
|
var modalManager = new ModalManager();
|
|
|
|
var BvModalEvent = /*#__PURE__*/function (_BvEvent) {
|
|
_inherits(BvModalEvent, _BvEvent);
|
|
|
|
var _super = _createSuper(BvModalEvent);
|
|
|
|
function BvModalEvent(type) {
|
|
var _this;
|
|
|
|
var eventInit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
_classCallCheck(this, BvModalEvent);
|
|
|
|
_this = _super.call(this, type, eventInit); // Freeze our new props as readonly, but leave them enumerable
|
|
|
|
defineProperties(_assertThisInitialized(_this), {
|
|
trigger: readonlyDescriptor()
|
|
});
|
|
return _this;
|
|
}
|
|
|
|
_createClass(BvModalEvent, null, [{
|
|
key: "Defaults",
|
|
get: function get() {
|
|
return _objectSpread2(_objectSpread2({}, _get(_getPrototypeOf(BvModalEvent), "Defaults", this)), {}, {
|
|
trigger: null
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return BvModalEvent;
|
|
}(BvEvent); // Named exports
|
|
|
|
// ObserveDom config to detect changes in modal content
|
|
// so that we can adjust the modal padding if needed
|
|
|
|
var OBSERVER_CONFIG = {
|
|
subtree: true,
|
|
childList: true,
|
|
characterData: true,
|
|
attributes: true,
|
|
attributeFilter: ['style', 'class']
|
|
}; // --- Props ---
|
|
|
|
var props$W = makePropsConfigurable({
|
|
size: {
|
|
type: String,
|
|
default: 'md'
|
|
},
|
|
centered: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
scrollable: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
buttonSize: {
|
|
type: String // default: ''
|
|
|
|
},
|
|
noStacking: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noFade: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noCloseOnBackdrop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noCloseOnEsc: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noEnforceFocus: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ignoreEnforceFocusSelector: {
|
|
type: [Array, String],
|
|
default: ''
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
titleHtml: {
|
|
type: String
|
|
},
|
|
titleTag: {
|
|
type: String,
|
|
default: 'h5'
|
|
},
|
|
titleClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
titleSrOnly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaLabel: {
|
|
type: String // default: null
|
|
|
|
},
|
|
headerBgVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
headerBorderVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
headerTextVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
headerCloseVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
headerClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
bodyBgVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
bodyTextVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
modalClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
dialogClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
contentClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
bodyClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
footerBgVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
footerBorderVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
footerTextVariant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
footerClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
// TODO: Rename to `noHeader` and deprecate `hideHeader`
|
|
hideHeader: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// TODO: Rename to `noFooter` and deprecate `hideFooter`
|
|
hideFooter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// TODO: Rename to `noHeaderClose` and deprecate `hideHeaderClose`
|
|
hideHeaderClose: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// TODO: Rename to `noBackdrop` and deprecate `hideBackdrop`
|
|
hideBackdrop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
okOnly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
okDisabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
cancelDisabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
returnFocus: {
|
|
// HTML Element, CSS selector string or Vue component instance
|
|
type: [HTMLElement, String, Object],
|
|
default: null
|
|
},
|
|
headerCloseContent: {
|
|
type: String,
|
|
default: '×'
|
|
},
|
|
headerCloseLabel: {
|
|
type: String,
|
|
default: 'Close'
|
|
},
|
|
cancelTitle: {
|
|
type: String,
|
|
default: 'Cancel'
|
|
},
|
|
cancelTitleHtml: {
|
|
type: String
|
|
},
|
|
okTitle: {
|
|
type: String,
|
|
default: 'OK'
|
|
},
|
|
okTitleHtml: {
|
|
type: String
|
|
},
|
|
cancelVariant: {
|
|
type: String,
|
|
default: 'secondary'
|
|
},
|
|
okVariant: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
lazy: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
busy: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
static: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
autoFocusButton: {
|
|
type: String,
|
|
default: null,
|
|
|
|
/* istanbul ignore next */
|
|
validator: function validator(value) {
|
|
return isUndefinedOrNull(value) || arrayIncludes(['ok', 'cancel', 'close'], value);
|
|
}
|
|
}
|
|
}, NAME_MODAL); // @vue/component
|
|
|
|
var BModal = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_MODAL,
|
|
mixins: [attrsMixin, idMixin, listenOnDocumentMixin, listenOnRootMixin, listenOnWindowMixin, normalizeSlotMixin, scopedStyleAttrsMixin],
|
|
inheritAttrs: false,
|
|
model: {
|
|
prop: 'visible',
|
|
event: 'change'
|
|
},
|
|
props: props$W,
|
|
data: function data() {
|
|
return {
|
|
isHidden: true,
|
|
// If modal should not be in document
|
|
isVisible: false,
|
|
// Controls modal visible state
|
|
isTransitioning: false,
|
|
// Used for style control
|
|
isShow: false,
|
|
// Used for style control
|
|
isBlock: false,
|
|
// Used for style control
|
|
isOpening: false,
|
|
// To signal that the modal is in the process of opening
|
|
isClosing: false,
|
|
// To signal that the modal is in the process of closing
|
|
ignoreBackdropClick: false,
|
|
// Used to signify if click out listener should ignore the click
|
|
isModalOverflowing: false,
|
|
return_focus: this.returnFocus || null,
|
|
// The following items are controlled by the modalManager instance
|
|
scrollbarWidth: 0,
|
|
zIndex: modalManager.getBaseZIndex(),
|
|
isTop: true,
|
|
isBodyOverflowing: false
|
|
};
|
|
},
|
|
computed: {
|
|
modalId: function modalId() {
|
|
return this.safeId();
|
|
},
|
|
modalOuterId: function modalOuterId() {
|
|
return this.safeId('__BV_modal_outer_');
|
|
},
|
|
modalHeaderId: function modalHeaderId() {
|
|
return this.safeId('__BV_modal_header_');
|
|
},
|
|
modalBodyId: function modalBodyId() {
|
|
return this.safeId('__BV_modal_body_');
|
|
},
|
|
modalTitleId: function modalTitleId() {
|
|
return this.safeId('__BV_modal_title_');
|
|
},
|
|
modalContentId: function modalContentId() {
|
|
return this.safeId('__BV_modal_content_');
|
|
},
|
|
modalFooterId: function modalFooterId() {
|
|
return this.safeId('__BV_modal_footer_');
|
|
},
|
|
modalBackdropId: function modalBackdropId() {
|
|
return this.safeId('__BV_modal_backdrop_');
|
|
},
|
|
modalClasses: function modalClasses() {
|
|
return [{
|
|
fade: !this.noFade,
|
|
show: this.isShow
|
|
}, this.modalClass];
|
|
},
|
|
modalStyles: function modalStyles() {
|
|
var sbWidth = "".concat(this.scrollbarWidth, "px");
|
|
return {
|
|
paddingLeft: !this.isBodyOverflowing && this.isModalOverflowing ? sbWidth : '',
|
|
paddingRight: this.isBodyOverflowing && !this.isModalOverflowing ? sbWidth : '',
|
|
// Needed to fix issue https://github.com/bootstrap-vue/bootstrap-vue/issues/3457
|
|
// Even though we are using v-show, we must ensure 'none' is restored in the styles
|
|
display: this.isBlock ? 'block' : 'none'
|
|
};
|
|
},
|
|
dialogClasses: function dialogClasses() {
|
|
var _ref;
|
|
|
|
return [(_ref = {}, _defineProperty(_ref, "modal-".concat(this.size), this.size), _defineProperty(_ref, 'modal-dialog-centered', this.centered), _defineProperty(_ref, 'modal-dialog-scrollable', this.scrollable), _ref), this.dialogClass];
|
|
},
|
|
headerClasses: function headerClasses() {
|
|
var _ref2;
|
|
|
|
return [(_ref2 = {}, _defineProperty(_ref2, "bg-".concat(this.headerBgVariant), this.headerBgVariant), _defineProperty(_ref2, "text-".concat(this.headerTextVariant), this.headerTextVariant), _defineProperty(_ref2, "border-".concat(this.headerBorderVariant), this.headerBorderVariant), _ref2), this.headerClass];
|
|
},
|
|
titleClasses: function titleClasses() {
|
|
return [{
|
|
'sr-only': this.titleSrOnly
|
|
}, this.titleClass];
|
|
},
|
|
bodyClasses: function bodyClasses() {
|
|
var _ref3;
|
|
|
|
return [(_ref3 = {}, _defineProperty(_ref3, "bg-".concat(this.bodyBgVariant), this.bodyBgVariant), _defineProperty(_ref3, "text-".concat(this.bodyTextVariant), this.bodyTextVariant), _ref3), this.bodyClass];
|
|
},
|
|
footerClasses: function footerClasses() {
|
|
var _ref4;
|
|
|
|
return [(_ref4 = {}, _defineProperty(_ref4, "bg-".concat(this.footerBgVariant), this.footerBgVariant), _defineProperty(_ref4, "text-".concat(this.footerTextVariant), this.footerTextVariant), _defineProperty(_ref4, "border-".concat(this.footerBorderVariant), this.footerBorderVariant), _ref4), this.footerClass];
|
|
},
|
|
modalOuterStyle: function modalOuterStyle() {
|
|
// Styles needed for proper stacking of modals
|
|
return {
|
|
position: 'absolute',
|
|
zIndex: this.zIndex
|
|
};
|
|
},
|
|
slotScope: function slotScope() {
|
|
return {
|
|
ok: this.onOk,
|
|
cancel: this.onCancel,
|
|
close: this.onClose,
|
|
hide: this.hide,
|
|
visible: this.isVisible
|
|
};
|
|
},
|
|
computeIgnoreEnforceFocusSelector: function computeIgnoreEnforceFocusSelector() {
|
|
// Normalize to an single selector with selectors separated by `,`
|
|
return concat(this.ignoreEnforceFocusSelector).filter(identity).join(',').trim();
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
// If the parent has a scoped style attribute, and the modal
|
|
// is portalled, add the scoped attribute to the modal wrapper
|
|
var scopedStyleAttrs = !this.static ? this.scopedStyleAttrs : {};
|
|
return _objectSpread2(_objectSpread2(_objectSpread2({}, scopedStyleAttrs), this.bvAttrs), {}, {
|
|
id: this.modalOuterId
|
|
});
|
|
},
|
|
computedModalAttrs: function computedModalAttrs() {
|
|
var isVisible = this.isVisible,
|
|
ariaLabel = this.ariaLabel;
|
|
return {
|
|
id: this.modalId,
|
|
role: 'dialog',
|
|
'aria-hidden': isVisible ? null : 'true',
|
|
'aria-modal': isVisible ? 'true' : null,
|
|
'aria-label': ariaLabel,
|
|
'aria-labelledby': this.hideHeader || ariaLabel || // TODO: Rename slot to `title` and deprecate `modal-title`
|
|
!(this.hasNormalizedSlot('modal-title') || this.titleHtml || this.title) ? null : this.modalTitleId,
|
|
'aria-describedby': this.modalBodyId
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
visible: function visible(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this[newVal ? 'show' : 'hide']();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Define non-reactive properties
|
|
this.$_observer = null;
|
|
},
|
|
mounted: function mounted() {
|
|
// Set initial z-index as queried from the DOM
|
|
this.zIndex = modalManager.getBaseZIndex(); // Listen for events from others to either open or close ourselves
|
|
// and listen to all modals to enable/disable enforce focus
|
|
|
|
this.listenOnRoot('bv::show::modal', this.showHandler);
|
|
this.listenOnRoot('bv::hide::modal', this.hideHandler);
|
|
this.listenOnRoot('bv::toggle::modal', this.toggleHandler); // Listen for `bv:modal::show events`, and close ourselves if the
|
|
// opening modal not us
|
|
|
|
this.listenOnRoot('bv::modal::show', this.modalListener); // Initially show modal?
|
|
|
|
if (this.visible === true) {
|
|
this.$nextTick(this.show);
|
|
}
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
// Ensure everything is back to normal
|
|
this.setObserver(false);
|
|
|
|
if (this.isVisible) {
|
|
this.isVisible = false;
|
|
this.isShow = false;
|
|
this.isTransitioning = false;
|
|
}
|
|
},
|
|
methods: {
|
|
setObserver: function setObserver() {
|
|
var on = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
this.$_observer && this.$_observer.disconnect();
|
|
this.$_observer = null;
|
|
|
|
if (on) {
|
|
this.$_observer = observeDom(this.$refs.content, this.checkModalOverflow.bind(this), OBSERVER_CONFIG);
|
|
}
|
|
},
|
|
// Private method to update the v-model
|
|
updateModel: function updateModel(val) {
|
|
if (val !== this.visible) {
|
|
this.$emit('change', val);
|
|
}
|
|
},
|
|
// Private method to create a BvModalEvent object
|
|
buildEvent: function buildEvent(type) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
return new BvModalEvent(type, _objectSpread2(_objectSpread2({
|
|
// Default options
|
|
cancelable: false,
|
|
target: this.$refs.modal || this.$el || null,
|
|
relatedTarget: null,
|
|
trigger: null
|
|
}, options), {}, {
|
|
// Options that can't be overridden
|
|
vueTarget: this,
|
|
componentId: this.modalId
|
|
}));
|
|
},
|
|
// Public method to show modal
|
|
show: function show() {
|
|
if (this.isVisible || this.isOpening) {
|
|
// If already open, or in the process of opening, do nothing
|
|
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
if (this.isClosing) {
|
|
// If we are in the process of closing, wait until hidden before re-opening
|
|
|
|
/* istanbul ignore next */
|
|
this.$once('hidden', this.show);
|
|
/* istanbul ignore next */
|
|
|
|
return;
|
|
}
|
|
|
|
this.isOpening = true; // Set the element to return focus to when closed
|
|
|
|
this.return_focus = this.return_focus || this.getActiveElement();
|
|
var showEvt = this.buildEvent('show', {
|
|
cancelable: true
|
|
});
|
|
this.emitEvent(showEvt); // Don't show if canceled
|
|
|
|
if (showEvt.defaultPrevented || this.isVisible) {
|
|
this.isOpening = false; // Ensure the v-model reflects the current state
|
|
|
|
this.updateModel(false);
|
|
return;
|
|
} // Show the modal
|
|
|
|
|
|
this.doShow();
|
|
},
|
|
// Public method to hide modal
|
|
hide: function hide() {
|
|
var trigger = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
|
|
if (!this.isVisible || this.isClosing) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
this.isClosing = true;
|
|
var hideEvt = this.buildEvent('hide', {
|
|
cancelable: trigger !== 'FORCE',
|
|
trigger: trigger || null
|
|
}); // We emit specific event for one of the three built-in buttons
|
|
|
|
if (trigger === 'ok') {
|
|
this.$emit('ok', hideEvt);
|
|
} else if (trigger === 'cancel') {
|
|
this.$emit('cancel', hideEvt);
|
|
} else if (trigger === 'headerclose') {
|
|
this.$emit('close', hideEvt);
|
|
}
|
|
|
|
this.emitEvent(hideEvt); // Hide if not canceled
|
|
|
|
if (hideEvt.defaultPrevented || !this.isVisible) {
|
|
this.isClosing = false; // Ensure v-model reflects current state
|
|
|
|
this.updateModel(true);
|
|
return;
|
|
} // Stop observing for content changes
|
|
|
|
|
|
this.setObserver(false); // Trigger the hide transition
|
|
|
|
this.isVisible = false; // Update the v-model
|
|
|
|
this.updateModel(false);
|
|
},
|
|
// Public method to toggle modal visibility
|
|
toggle: function toggle(triggerEl) {
|
|
if (triggerEl) {
|
|
this.return_focus = triggerEl;
|
|
}
|
|
|
|
if (this.isVisible) {
|
|
this.hide('toggle');
|
|
} else {
|
|
this.show();
|
|
}
|
|
},
|
|
// Private method to get the current document active element
|
|
getActiveElement: function getActiveElement$1() {
|
|
// Returning focus to `document.body` may cause unwanted scrolls,
|
|
// so we exclude setting focus on body
|
|
var activeElement = getActiveElement(isBrowser ? [document.body] : []); // Preset the fallback return focus value if it is not set
|
|
// `document.activeElement` should be the trigger element that was clicked or
|
|
// in the case of using the v-model, which ever element has current focus
|
|
// Will be overridden by some commands such as toggle, etc.
|
|
// Note: On IE 11, `document.activeElement` may be `null`
|
|
// So we test it for truthiness first
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3206
|
|
|
|
|
|
return activeElement && activeElement.focus ? activeElement : null;
|
|
},
|
|
// Private method to finish showing modal
|
|
doShow: function doShow() {
|
|
var _this = this;
|
|
|
|
/* istanbul ignore next: commenting out for now until we can test stacking */
|
|
if (modalManager.modalsAreOpen && this.noStacking) {
|
|
// If another modal(s) is already open, wait for it(them) to close
|
|
this.listenOnRootOnce('bv::modal::hidden', this.doShow);
|
|
return;
|
|
}
|
|
|
|
modalManager.registerModal(this); // Place modal in DOM
|
|
|
|
this.isHidden = false;
|
|
this.$nextTick(function () {
|
|
// We do this in `$nextTick()` to ensure the modal is in DOM first
|
|
// before we show it
|
|
_this.isVisible = true;
|
|
_this.isOpening = false; // Update the v-model
|
|
|
|
_this.updateModel(true);
|
|
|
|
_this.$nextTick(function () {
|
|
// Observe changes in modal content and adjust if necessary
|
|
// In a `$nextTick()` in case modal content is lazy
|
|
_this.setObserver(true);
|
|
});
|
|
});
|
|
},
|
|
// Transition handlers
|
|
onBeforeEnter: function onBeforeEnter() {
|
|
this.isTransitioning = true;
|
|
this.setResizeEvent(true);
|
|
},
|
|
onEnter: function onEnter() {
|
|
var _this2 = this;
|
|
|
|
this.isBlock = true; // We add the `show` class 1 frame later
|
|
// `requestAF()` runs the callback before the next repaint, so we need
|
|
// two calls to guarantee the next frame has been rendered
|
|
|
|
requestAF(function () {
|
|
requestAF(function () {
|
|
_this2.isShow = true;
|
|
});
|
|
});
|
|
},
|
|
onAfterEnter: function onAfterEnter() {
|
|
var _this3 = this;
|
|
|
|
this.checkModalOverflow();
|
|
this.isTransitioning = false; // We use `requestAF()` to allow transition hooks to complete
|
|
// before passing control over to the other handlers
|
|
// This will allow users to not have to use `$nextTick()` or `requestAF()`
|
|
// when trying to pre-focus an element
|
|
|
|
requestAF(function () {
|
|
_this3.emitEvent(_this3.buildEvent('shown'));
|
|
|
|
_this3.setEnforceFocus(true);
|
|
|
|
_this3.$nextTick(function () {
|
|
// Delayed in a `$nextTick()` to allow users time to pre-focus
|
|
// an element if the wish
|
|
_this3.focusFirst();
|
|
});
|
|
});
|
|
},
|
|
onBeforeLeave: function onBeforeLeave() {
|
|
this.isTransitioning = true;
|
|
this.setResizeEvent(false);
|
|
this.setEnforceFocus(false);
|
|
},
|
|
onLeave: function onLeave() {
|
|
// Remove the 'show' class
|
|
this.isShow = false;
|
|
},
|
|
onAfterLeave: function onAfterLeave() {
|
|
var _this4 = this;
|
|
|
|
this.isBlock = false;
|
|
this.isTransitioning = false;
|
|
this.isModalOverflowing = false;
|
|
this.isHidden = true;
|
|
this.$nextTick(function () {
|
|
_this4.isClosing = false;
|
|
modalManager.unregisterModal(_this4);
|
|
|
|
_this4.returnFocusTo(); // TODO: Need to find a way to pass the `trigger` property
|
|
// to the `hidden` event, not just only the `hide` event
|
|
|
|
|
|
_this4.emitEvent(_this4.buildEvent('hidden'));
|
|
});
|
|
},
|
|
// Event emitter
|
|
emitEvent: function emitEvent(bvModalEvt) {
|
|
var type = bvModalEvt.type; // We emit on root first incase a global listener wants to cancel
|
|
// the event first before the instance emits its event
|
|
|
|
this.emitOnRoot("bv::modal::".concat(type), bvModalEvt, bvModalEvt.componentId);
|
|
this.$emit(type, bvModalEvt);
|
|
},
|
|
// UI event handlers
|
|
onDialogMousedown: function onDialogMousedown() {
|
|
var _this5 = this;
|
|
|
|
// Watch to see if the matching mouseup event occurs outside the dialog
|
|
// And if it does, cancel the clickOut handler
|
|
var modal = this.$refs.modal;
|
|
|
|
var onceModalMouseup = function onceModalMouseup(evt) {
|
|
eventOff(modal, 'mouseup', onceModalMouseup, EVENT_OPTIONS_NO_CAPTURE);
|
|
|
|
if (evt.target === modal) {
|
|
_this5.ignoreBackdropClick = true;
|
|
}
|
|
};
|
|
|
|
eventOn(modal, 'mouseup', onceModalMouseup, EVENT_OPTIONS_NO_CAPTURE);
|
|
},
|
|
onClickOut: function onClickOut(evt) {
|
|
if (this.ignoreBackdropClick) {
|
|
// Click was initiated inside the modal content, but finished outside.
|
|
// Set by the above onDialogMousedown handler
|
|
this.ignoreBackdropClick = false;
|
|
return;
|
|
} // Do nothing if not visible, backdrop click disabled, or element
|
|
// that generated click event is no longer in document body
|
|
|
|
|
|
if (!this.isVisible || this.noCloseOnBackdrop || !contains(document.body, evt.target)) {
|
|
return;
|
|
} // If backdrop clicked, hide modal
|
|
|
|
|
|
if (!contains(this.$refs.content, evt.target)) {
|
|
this.hide('backdrop');
|
|
}
|
|
},
|
|
onOk: function onOk() {
|
|
this.hide('ok');
|
|
},
|
|
onCancel: function onCancel() {
|
|
this.hide('cancel');
|
|
},
|
|
onClose: function onClose() {
|
|
this.hide('headerclose');
|
|
},
|
|
onEsc: function onEsc(evt) {
|
|
// If ESC pressed, hide modal
|
|
if (evt.keyCode === CODE_ESC && this.isVisible && !this.noCloseOnEsc) {
|
|
this.hide('esc');
|
|
}
|
|
},
|
|
// Document focusin listener
|
|
focusHandler: function focusHandler(evt) {
|
|
// If focus leaves modal content, bring it back
|
|
var content = this.$refs.content;
|
|
var target = evt.target;
|
|
|
|
if (this.noEnforceFocus || !this.isTop || !this.isVisible || !content || document === target || contains(content, target) || this.computeIgnoreEnforceFocusSelector && closest(this.computeIgnoreEnforceFocusSelector, target, true)) {
|
|
return;
|
|
}
|
|
|
|
var tabables = getTabables(this.$refs.content);
|
|
var _this$$refs = this.$refs,
|
|
bottomTrap = _this$$refs.bottomTrap,
|
|
topTrap = _this$$refs.topTrap;
|
|
|
|
if (bottomTrap && target === bottomTrap) {
|
|
// If user pressed TAB out of modal into our bottom trab trap element
|
|
// Find the first tabable element in the modal content and focus it
|
|
if (attemptFocus(tabables[0])) {
|
|
// Focus was successful
|
|
return;
|
|
}
|
|
} else if (topTrap && target === topTrap) {
|
|
// If user pressed CTRL-TAB out of modal and into our top tab trap element
|
|
// Find the last tabable element in the modal content and focus it
|
|
if (attemptFocus(tabables[tabables.length - 1])) {
|
|
// Focus was successful
|
|
return;
|
|
}
|
|
} // Otherwise focus the modal content container
|
|
|
|
|
|
attemptFocus(content, {
|
|
preventScroll: true
|
|
});
|
|
},
|
|
// Turn on/off focusin listener
|
|
setEnforceFocus: function setEnforceFocus(on) {
|
|
this.listenDocument(on, 'focusin', this.focusHandler);
|
|
},
|
|
// Resize listener
|
|
setResizeEvent: function setResizeEvent(on) {
|
|
this.listenWindow(on, 'resize', this.checkModalOverflow);
|
|
this.listenWindow(on, 'orientationchange', this.checkModalOverflow);
|
|
},
|
|
// Root listener handlers
|
|
showHandler: function showHandler(id, triggerEl) {
|
|
if (id === this.modalId) {
|
|
this.return_focus = triggerEl || this.getActiveElement();
|
|
this.show();
|
|
}
|
|
},
|
|
hideHandler: function hideHandler(id) {
|
|
if (id === this.modalId) {
|
|
this.hide('event');
|
|
}
|
|
},
|
|
toggleHandler: function toggleHandler(id, triggerEl) {
|
|
if (id === this.modalId) {
|
|
this.toggle(triggerEl);
|
|
}
|
|
},
|
|
modalListener: function modalListener(bvEvt) {
|
|
// If another modal opens, close this one if stacking not permitted
|
|
if (this.noStacking && bvEvt.vueTarget !== this) {
|
|
this.hide();
|
|
}
|
|
},
|
|
// Focus control handlers
|
|
focusFirst: function focusFirst() {
|
|
var _this6 = this;
|
|
|
|
// Don't try and focus if we are SSR
|
|
if (isBrowser) {
|
|
requestAF(function () {
|
|
var modal = _this6.$refs.modal;
|
|
var content = _this6.$refs.content;
|
|
|
|
var activeElement = _this6.getActiveElement(); // If the modal contains the activeElement, we don't do anything
|
|
|
|
|
|
if (modal && content && !(activeElement && contains(content, activeElement))) {
|
|
var ok = _this6.$refs['ok-button'];
|
|
var cancel = _this6.$refs['cancel-button'];
|
|
var close = _this6.$refs['close-button']; // Focus the appropriate button or modal content wrapper
|
|
|
|
var autoFocus = _this6.autoFocusButton;
|
|
/* istanbul ignore next */
|
|
|
|
var el = autoFocus === 'ok' && ok ? ok.$el || ok : autoFocus === 'cancel' && cancel ? cancel.$el || cancel : autoFocus === 'close' && close ? close.$el || close : content; // Focus the element
|
|
|
|
attemptFocus(el);
|
|
|
|
if (el === content) {
|
|
// Make sure top of modal is showing (if longer than the viewport)
|
|
_this6.$nextTick(function () {
|
|
modal.scrollTop = 0;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
returnFocusTo: function returnFocusTo() {
|
|
// Prefer `returnFocus` prop over event specified
|
|
// `return_focus` value
|
|
var el = this.returnFocus || this.return_focus || null;
|
|
this.return_focus = null;
|
|
this.$nextTick(function () {
|
|
// Is el a string CSS selector?
|
|
el = isString(el) ? select(el) : el;
|
|
|
|
if (el) {
|
|
// Possibly could be a component reference
|
|
el = el.$el || el;
|
|
attemptFocus(el);
|
|
}
|
|
});
|
|
},
|
|
checkModalOverflow: function checkModalOverflow() {
|
|
if (this.isVisible) {
|
|
var modal = this.$refs.modal;
|
|
this.isModalOverflowing = modal.scrollHeight > document.documentElement.clientHeight;
|
|
}
|
|
},
|
|
makeModal: function makeModal(h) {
|
|
// Modal header
|
|
var $header = h();
|
|
|
|
if (!this.hideHeader) {
|
|
// TODO: Rename slot to `header` and deprecate `modal-header`
|
|
var $modalHeader = this.normalizeSlot('modal-header', this.slotScope);
|
|
|
|
if (!$modalHeader) {
|
|
var $closeButton = h();
|
|
|
|
if (!this.hideHeaderClose) {
|
|
$closeButton = h(BButtonClose, {
|
|
props: {
|
|
content: this.headerCloseContent,
|
|
disabled: this.isTransitioning,
|
|
ariaLabel: this.headerCloseLabel,
|
|
textVariant: this.headerCloseVariant || this.headerTextVariant
|
|
},
|
|
on: {
|
|
click: this.onClose
|
|
},
|
|
ref: 'close-button'
|
|
}, // TODO: Rename slot to `header-close` and deprecate `modal-header-close`
|
|
[this.normalizeSlot('modal-header-close')]);
|
|
}
|
|
|
|
$modalHeader = [h(this.titleTag, {
|
|
staticClass: 'modal-title',
|
|
class: this.titleClasses,
|
|
attrs: {
|
|
id: this.modalTitleId
|
|
},
|
|
// TODO: Rename slot to `title` and deprecate `modal-title`
|
|
domProps: this.hasNormalizedSlot('modal-title') ? {} : htmlOrText(this.titleHtml, this.title)
|
|
}, // TODO: Rename slot to `title` and deprecate `modal-title`
|
|
this.normalizeSlot('modal-title', this.slotScope)), $closeButton];
|
|
}
|
|
|
|
$header = h('header', {
|
|
staticClass: 'modal-header',
|
|
class: this.headerClasses,
|
|
attrs: {
|
|
id: this.modalHeaderId
|
|
},
|
|
ref: 'header'
|
|
}, [$modalHeader]);
|
|
} // Modal body
|
|
|
|
|
|
var $body = h('div', {
|
|
staticClass: 'modal-body',
|
|
class: this.bodyClasses,
|
|
attrs: {
|
|
id: this.modalBodyId
|
|
},
|
|
ref: 'body'
|
|
}, this.normalizeSlot(SLOT_NAME_DEFAULT, this.slotScope)); // Modal footer
|
|
|
|
var $footer = h();
|
|
|
|
if (!this.hideFooter) {
|
|
// TODO: Rename slot to `footer` and deprecate `modal-footer`
|
|
var $modalFooter = this.normalizeSlot('modal-footer', this.slotScope);
|
|
|
|
if (!$modalFooter) {
|
|
var $cancelButton = h();
|
|
|
|
if (!this.okOnly) {
|
|
$cancelButton = h(BButton, {
|
|
props: {
|
|
variant: this.cancelVariant,
|
|
size: this.buttonSize,
|
|
disabled: this.cancelDisabled || this.busy || this.isTransitioning
|
|
},
|
|
// TODO: Rename slot to `cancel-button` and deprecate `modal-cancel`
|
|
domProps: this.hasNormalizedSlot('modal-cancel') ? {} : htmlOrText(this.cancelTitleHtml, this.cancelTitle),
|
|
on: {
|
|
click: this.onCancel
|
|
},
|
|
ref: 'cancel-button'
|
|
}, // TODO: Rename slot to `cancel-button` and deprecate `modal-cancel`
|
|
this.normalizeSlot('modal-cancel'));
|
|
}
|
|
|
|
var $okButton = h(BButton, {
|
|
props: {
|
|
variant: this.okVariant,
|
|
size: this.buttonSize,
|
|
disabled: this.okDisabled || this.busy || this.isTransitioning
|
|
},
|
|
// TODO: Rename slot to `ok-button` and deprecate `modal-ok`
|
|
domProps: this.hasNormalizedSlot('modal-ok') ? {} : htmlOrText(this.okTitleHtml, this.okTitle),
|
|
on: {
|
|
click: this.onOk
|
|
},
|
|
ref: 'ok-button'
|
|
}, // TODO: Rename slot to `ok-button` and deprecate `modal-ok`
|
|
this.normalizeSlot('modal-ok'));
|
|
$modalFooter = [$cancelButton, $okButton];
|
|
}
|
|
|
|
$footer = h('footer', {
|
|
staticClass: 'modal-footer',
|
|
class: this.footerClasses,
|
|
attrs: {
|
|
id: this.modalFooterId
|
|
},
|
|
ref: 'footer'
|
|
}, [$modalFooter]);
|
|
} // Assemble modal content
|
|
|
|
|
|
var $modalContent = h('div', {
|
|
staticClass: 'modal-content',
|
|
class: this.contentClass,
|
|
attrs: {
|
|
id: this.modalContentId,
|
|
tabindex: '-1'
|
|
},
|
|
ref: 'content'
|
|
}, [$header, $body, $footer]); // Tab traps to prevent page from scrolling to next element in
|
|
// tab index during enforce-focus tab cycle
|
|
|
|
var $tabTrapTop = h();
|
|
var $tabTrapBottom = h();
|
|
|
|
if (this.isVisible && !this.noEnforceFocus) {
|
|
$tabTrapTop = h('span', {
|
|
ref: 'topTrap',
|
|
attrs: {
|
|
tabindex: '0'
|
|
}
|
|
});
|
|
$tabTrapBottom = h('span', {
|
|
ref: 'bottomTrap',
|
|
attrs: {
|
|
tabindex: '0'
|
|
}
|
|
});
|
|
} // Modal dialog wrapper
|
|
|
|
|
|
var $modalDialog = h('div', {
|
|
staticClass: 'modal-dialog',
|
|
class: this.dialogClasses,
|
|
on: {
|
|
mousedown: this.onDialogMousedown
|
|
},
|
|
ref: 'dialog'
|
|
}, [$tabTrapTop, $modalContent, $tabTrapBottom]); // Modal
|
|
|
|
var $modal = h('div', {
|
|
staticClass: 'modal',
|
|
class: this.modalClasses,
|
|
style: this.modalStyles,
|
|
attrs: this.computedModalAttrs,
|
|
on: {
|
|
keydown: this.onEsc,
|
|
click: this.onClickOut
|
|
},
|
|
directives: [{
|
|
name: 'show',
|
|
value: this.isVisible
|
|
}],
|
|
ref: 'modal'
|
|
}, [$modalDialog]); // Wrap modal in transition
|
|
// Sadly, we can't use `BVTransition` here due to the differences in
|
|
// transition durations for `.modal` and `.modal-dialog`
|
|
// At least until https://github.com/vuejs/vue/issues/9986 is resolved
|
|
|
|
$modal = h('transition', {
|
|
props: {
|
|
enterClass: '',
|
|
enterToClass: '',
|
|
enterActiveClass: '',
|
|
leaveClass: '',
|
|
leaveActiveClass: '',
|
|
leaveToClass: ''
|
|
},
|
|
on: {
|
|
beforeEnter: this.onBeforeEnter,
|
|
enter: this.onEnter,
|
|
afterEnter: this.onAfterEnter,
|
|
beforeLeave: this.onBeforeLeave,
|
|
leave: this.onLeave,
|
|
afterLeave: this.onAfterLeave
|
|
}
|
|
}, [$modal]); // Modal backdrop
|
|
|
|
var $backdrop = h();
|
|
|
|
if (!this.hideBackdrop && this.isVisible) {
|
|
$backdrop = h('div', {
|
|
staticClass: 'modal-backdrop',
|
|
attrs: {
|
|
id: this.modalBackdropId
|
|
}
|
|
}, // TODO: Rename slot to `backdrop` and deprecate `modal-backdrop`
|
|
this.normalizeSlot('modal-backdrop'));
|
|
}
|
|
|
|
$backdrop = h(BVTransition, {
|
|
props: {
|
|
noFade: this.noFade
|
|
}
|
|
}, [$backdrop]); // Assemble modal and backdrop in an outer <div>
|
|
|
|
return h('div', {
|
|
style: this.modalOuterStyle,
|
|
attrs: this.computedAttrs,
|
|
key: "modal-outer-".concat(this._uid)
|
|
}, [$modal, $backdrop]);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
if (this.static) {
|
|
return this.lazy && this.isHidden ? h() : this.makeModal(h);
|
|
} else {
|
|
return this.isHidden ? h() : h(BTransporterSingle, [this.makeModal(h)]);
|
|
}
|
|
}
|
|
});
|
|
|
|
var EVENT_SHOW = 'bv::show::modal'; // Prop name we use to store info on root element
|
|
|
|
var PROPERTY = '__bv_modal_directive__';
|
|
|
|
var getTarget = function getTarget(_ref) {
|
|
var _ref$modifiers = _ref.modifiers,
|
|
modifiers = _ref$modifiers === void 0 ? {} : _ref$modifiers,
|
|
arg = _ref.arg,
|
|
value = _ref.value;
|
|
// Try value, then arg, otherwise pick last modifier
|
|
return isString(value) ? value : isString(arg) ? arg : keys(modifiers).reverse()[0];
|
|
};
|
|
|
|
var getTriggerElement = function getTriggerElement(el) {
|
|
// If root element is a dropdown-item or nav-item, we
|
|
// need to target the inner link or button instead
|
|
return el && matches(el, '.dropdown-menu > li, li.nav-item') ? select('a, button', el) || el : el;
|
|
};
|
|
|
|
var setRole = function setRole(trigger) {
|
|
// Ensure accessibility on non button elements
|
|
if (trigger && trigger.tagName !== 'BUTTON') {
|
|
// Only set a role if the trigger element doesn't have one
|
|
if (!hasAttr(trigger, 'role')) {
|
|
setAttr(trigger, 'role', 'button');
|
|
} // Add a tabindex is not a button or link, and tabindex is not provided
|
|
|
|
|
|
if (trigger.tagName !== 'A' && !hasAttr(trigger, 'tabindex')) {
|
|
setAttr(trigger, 'tabindex', '0');
|
|
}
|
|
}
|
|
};
|
|
|
|
var bind$1 = function bind(el, binding, vnode) {
|
|
var target = getTarget(binding);
|
|
var trigger = getTriggerElement(el);
|
|
|
|
if (target && trigger) {
|
|
var handler = function handler(evt) {
|
|
// `currentTarget` is the element with the listener on it
|
|
var currentTarget = evt.currentTarget;
|
|
|
|
if (!isDisabled(currentTarget)) {
|
|
var type = evt.type;
|
|
var key = evt.keyCode; // Open modal only if trigger is not disabled
|
|
|
|
if (type === 'click' || type === 'keydown' && (key === CODE_ENTER || key === CODE_SPACE)) {
|
|
vnode.context.$root.$emit(EVENT_SHOW, target, currentTarget);
|
|
}
|
|
}
|
|
};
|
|
|
|
el[PROPERTY] = {
|
|
handler: handler,
|
|
target: target,
|
|
trigger: trigger
|
|
}; // If element is not a button, we add `role="button"` for accessibility
|
|
|
|
setRole(trigger); // Listen for click events
|
|
|
|
eventOn(trigger, 'click', handler, EVENT_OPTIONS_PASSIVE);
|
|
|
|
if (trigger.tagName !== 'BUTTON' && getAttr(trigger, 'role') === 'button') {
|
|
// If trigger isn't a button but has role button,
|
|
// we also listen for `keydown.space` && `keydown.enter`
|
|
eventOn(trigger, 'keydown', handler, EVENT_OPTIONS_PASSIVE);
|
|
}
|
|
}
|
|
};
|
|
|
|
var unbind$1 = function unbind(el) {
|
|
var oldProp = el[PROPERTY] || {};
|
|
var trigger = oldProp.trigger;
|
|
var handler = oldProp.handler;
|
|
|
|
if (trigger && handler) {
|
|
eventOff(trigger, 'click', handler, EVENT_OPTIONS_PASSIVE);
|
|
eventOff(trigger, 'keydown', handler, EVENT_OPTIONS_PASSIVE);
|
|
eventOff(el, 'click', handler, EVENT_OPTIONS_PASSIVE);
|
|
eventOff(el, 'keydown', handler, EVENT_OPTIONS_PASSIVE);
|
|
}
|
|
|
|
delete el[PROPERTY];
|
|
};
|
|
|
|
var componentUpdated$1 = function componentUpdated(el, binding, vnode) {
|
|
var oldProp = el[PROPERTY] || {};
|
|
var target = getTarget(binding);
|
|
var trigger = getTriggerElement(el);
|
|
|
|
if (target !== oldProp.target || trigger !== oldProp.trigger) {
|
|
// We bind and rebind if the target or trigger changes
|
|
unbind$1(el);
|
|
bind$1(el, binding, vnode);
|
|
} // If trigger element is not a button, ensure `role="button"`
|
|
// is still set for accessibility
|
|
|
|
|
|
setRole(trigger);
|
|
};
|
|
|
|
var updated = function updated() {};
|
|
/*
|
|
* Export our directive
|
|
*/
|
|
|
|
|
|
var VBModal = {
|
|
inserted: componentUpdated$1,
|
|
updated: updated,
|
|
componentUpdated: componentUpdated$1,
|
|
unbind: unbind$1
|
|
};
|
|
|
|
var PROP_NAME$1 = '$bvModal';
|
|
var PROP_NAME_PRIV = '_bv__modal'; // Base modal props that are allowed
|
|
// Some may be ignored or overridden on some message boxes
|
|
// Prop ID is allowed, but really only should be used for testing
|
|
// We need to add it in explicitly as it comes from the `idMixin`
|
|
|
|
var BASE_PROPS = ['id'].concat(_toConsumableArray(keys(omit(props$W, ['busy', 'lazy', 'noStacking', "static", 'visible'])))); // Fallback event resolver (returns undefined)
|
|
|
|
var defaultResolver = function defaultResolver() {}; // Map prop names to modal slot names
|
|
|
|
|
|
var propsToSlots = {
|
|
msgBoxContent: 'default',
|
|
title: 'modal-title',
|
|
okTitle: 'modal-ok',
|
|
cancelTitle: 'modal-cancel'
|
|
}; // --- Utility methods ---
|
|
// Method to filter only recognized props that are not undefined
|
|
|
|
var filterOptions = function filterOptions(options) {
|
|
return BASE_PROPS.reduce(function (memo, key) {
|
|
if (!isUndefined(options[key])) {
|
|
memo[key] = options[key];
|
|
}
|
|
|
|
return memo;
|
|
}, {});
|
|
}; // Method to install `$bvModal` VM injection
|
|
|
|
|
|
var plugin = function plugin(Vue) {
|
|
// Create a private sub-component that extends BModal
|
|
// which self-destructs after hidden
|
|
// @vue/component
|
|
var BMsgBox = Vue.extend({
|
|
name: NAME_MSG_BOX,
|
|
extends: BModal,
|
|
destroyed: function destroyed() {
|
|
// Make sure we not in document any more
|
|
if (this.$el && this.$el.parentNode) {
|
|
this.$el.parentNode.removeChild(this.$el);
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// Self destruct handler
|
|
var handleDestroy = function handleDestroy() {
|
|
_this.$nextTick(function () {
|
|
// In a `requestAF()` to release control back to application
|
|
requestAF(function () {
|
|
_this.$destroy();
|
|
});
|
|
});
|
|
}; // Self destruct if parent destroyed
|
|
|
|
|
|
this.$parent.$once('hook:destroyed', handleDestroy); // Self destruct after hidden
|
|
|
|
this.$once('hidden', handleDestroy); // Self destruct on route change
|
|
|
|
/* istanbul ignore if */
|
|
|
|
if (this.$router && this.$route) {
|
|
// Destroy ourselves if route changes
|
|
|
|
/* istanbul ignore next */
|
|
this.$once('hook:beforeDestroy', this.$watch('$router', handleDestroy));
|
|
} // Show the `BMsgBox`
|
|
|
|
|
|
this.show();
|
|
}
|
|
}); // Method to generate the on-demand modal message box
|
|
// Returns a promise that resolves to a value returned by the resolve
|
|
|
|
var asyncMsgBox = function asyncMsgBox($parent, props) {
|
|
var resolver = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultResolver;
|
|
|
|
if (warnNotClient(PROP_NAME$1) || warnNoPromiseSupport(PROP_NAME$1)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Create an instance of `BMsgBox` component
|
|
|
|
|
|
var msgBox = new BMsgBox({
|
|
// We set parent as the local VM so these modals can emit events on
|
|
// the app `$root`, as needed by things like tooltips and popovers
|
|
// And it helps to ensure `BMsgBox` is destroyed when parent is destroyed
|
|
parent: $parent,
|
|
// Preset the prop values
|
|
propsData: _objectSpread2(_objectSpread2(_objectSpread2({}, filterOptions(getComponentConfig(NAME_MODAL))), {}, {
|
|
// Defaults that user can override
|
|
hideHeaderClose: true,
|
|
hideHeader: !(props.title || props.titleHtml)
|
|
}, omit(props, keys(propsToSlots))), {}, {
|
|
// Props that can't be overridden
|
|
lazy: false,
|
|
busy: false,
|
|
visible: false,
|
|
noStacking: false,
|
|
noEnforceFocus: false
|
|
})
|
|
}); // Convert certain props to scoped slots
|
|
|
|
keys(propsToSlots).forEach(function (prop) {
|
|
if (!isUndefined(props[prop])) {
|
|
// Can be a string, or array of VNodes.
|
|
// Alternatively, user can use HTML version of prop to pass an HTML string.
|
|
msgBox.$slots[propsToSlots[prop]] = concat(props[prop]);
|
|
}
|
|
}); // Return a promise that resolves when hidden, or rejects on destroyed
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
var resolved = false;
|
|
msgBox.$once('hook:destroyed', function () {
|
|
if (!resolved) {
|
|
/* istanbul ignore next */
|
|
reject(new Error('BootstrapVue MsgBox destroyed before resolve'));
|
|
}
|
|
});
|
|
msgBox.$on('hide', function (bvModalEvt) {
|
|
if (!bvModalEvt.defaultPrevented) {
|
|
var result = resolver(bvModalEvt); // If resolver didn't cancel hide, we resolve
|
|
|
|
if (!bvModalEvt.defaultPrevented) {
|
|
resolved = true;
|
|
resolve(result);
|
|
}
|
|
}
|
|
}); // Create a mount point (a DIV) and mount the msgBo which will trigger it to show
|
|
|
|
var div = document.createElement('div');
|
|
document.body.appendChild(div);
|
|
msgBox.$mount(div);
|
|
});
|
|
}; // Private utility method to open a user defined message box and returns a promise.
|
|
// Not to be used directly by consumers, as this method may change calling syntax
|
|
|
|
|
|
var makeMsgBox = function makeMsgBox($parent, content) {
|
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
var resolver = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
|
|
|
|
if (!content || warnNoPromiseSupport(PROP_NAME$1) || warnNotClient(PROP_NAME$1) || !isFunction(resolver)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
return asyncMsgBox($parent, _objectSpread2(_objectSpread2({}, filterOptions(options)), {}, {
|
|
msgBoxContent: content
|
|
}), resolver);
|
|
}; // BvModal instance class
|
|
|
|
|
|
var BvModal = /*#__PURE__*/function () {
|
|
function BvModal(vm) {
|
|
_classCallCheck(this, BvModal);
|
|
|
|
// Assign the new properties to this instance
|
|
assign(this, {
|
|
_vm: vm,
|
|
_root: vm.$root
|
|
}); // Set these properties as read-only and non-enumerable
|
|
|
|
defineProperties(this, {
|
|
_vm: readonlyDescriptor(),
|
|
_root: readonlyDescriptor()
|
|
});
|
|
} // --- Instance methods ---
|
|
// Show modal with the specified ID args are for future use
|
|
|
|
|
|
_createClass(BvModal, [{
|
|
key: "show",
|
|
value: function show(id) {
|
|
if (id && this._root) {
|
|
var _this$_root;
|
|
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
args[_key - 1] = arguments[_key];
|
|
}
|
|
|
|
(_this$_root = this._root).$emit.apply(_this$_root, ['bv::show::modal', id].concat(args));
|
|
}
|
|
} // Hide modal with the specified ID args are for future use
|
|
|
|
}, {
|
|
key: "hide",
|
|
value: function hide(id) {
|
|
if (id && this._root) {
|
|
var _this$_root2;
|
|
|
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
args[_key2 - 1] = arguments[_key2];
|
|
}
|
|
|
|
(_this$_root2 = this._root).$emit.apply(_this$_root2, ['bv::hide::modal', id].concat(args));
|
|
}
|
|
} // The following methods require Promise support!
|
|
// IE 11 and others do not support Promise natively, so users
|
|
// should have a Polyfill loaded (which they need anyways for IE 11 support)
|
|
// Open a message box with OK button only and returns a promise
|
|
|
|
}, {
|
|
key: "msgBoxOk",
|
|
value: function msgBoxOk(message) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
// Pick the modal props we support from options
|
|
var props = _objectSpread2(_objectSpread2({}, options), {}, {
|
|
// Add in overrides and our content prop
|
|
okOnly: true,
|
|
okDisabled: false,
|
|
hideFooter: false,
|
|
msgBoxContent: message
|
|
});
|
|
|
|
return makeMsgBox(this._vm, message, props, function () {
|
|
// Always resolve to true for OK
|
|
return true;
|
|
});
|
|
} // Open a message box modal with OK and CANCEL buttons
|
|
// and returns a promise
|
|
|
|
}, {
|
|
key: "msgBoxConfirm",
|
|
value: function msgBoxConfirm(message) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
// Set the modal props we support from options
|
|
var props = _objectSpread2(_objectSpread2({}, options), {}, {
|
|
// Add in overrides and our content prop
|
|
okOnly: false,
|
|
okDisabled: false,
|
|
cancelDisabled: false,
|
|
hideFooter: false
|
|
});
|
|
|
|
return makeMsgBox(this._vm, message, props, function (bvModalEvt) {
|
|
var trigger = bvModalEvt.trigger;
|
|
return trigger === 'ok' ? true : trigger === 'cancel' ? false : null;
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return BvModal;
|
|
}(); // Add our instance mixin
|
|
|
|
|
|
Vue.mixin({
|
|
beforeCreate: function beforeCreate() {
|
|
// Because we need access to `$root` for `$emits`, and VM for parenting,
|
|
// we have to create a fresh instance of `BvModal` for each VM
|
|
this[PROP_NAME_PRIV] = new BvModal(this);
|
|
}
|
|
}); // Define our read-only `$bvModal` instance property
|
|
// Placed in an if just in case in HMR mode
|
|
|
|
if (!hasOwnProperty(Vue.prototype, PROP_NAME$1)) {
|
|
defineProperty(Vue.prototype, PROP_NAME$1, {
|
|
get: function get() {
|
|
/* istanbul ignore next */
|
|
if (!this || !this[PROP_NAME_PRIV]) {
|
|
warn("\"".concat(PROP_NAME$1, "\" must be accessed from a Vue instance \"this\" context."), NAME_MODAL);
|
|
}
|
|
|
|
return this[PROP_NAME_PRIV];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
var BVModalPlugin = /*#__PURE__*/pluginFactory({
|
|
plugins: {
|
|
plugin: plugin
|
|
}
|
|
});
|
|
|
|
var ModalPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BModal: BModal
|
|
},
|
|
directives: {
|
|
VBModal: VBModal
|
|
},
|
|
// $bvModal injection
|
|
plugins: {
|
|
BVModalPlugin: BVModalPlugin
|
|
}
|
|
});
|
|
|
|
var props$X = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'ul'
|
|
},
|
|
fill: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
justified: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
align: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tabs: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
pills: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
vertical: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
small: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
cardHeader: {
|
|
// Set to true if placing in a card header
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_NAV); // -- Utils --
|
|
|
|
var computeJustifyContent = function computeJustifyContent(value) {
|
|
// Normalize value
|
|
value = value === 'left' ? 'start' : value === 'right' ? 'end' : value;
|
|
return "justify-content-".concat(value);
|
|
}; // @vue/component
|
|
|
|
|
|
var BNav = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAV,
|
|
functional: true,
|
|
props: props$X,
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'nav',
|
|
class: (_class = {
|
|
'nav-tabs': props.tabs,
|
|
'nav-pills': props.pills && !props.tabs,
|
|
'card-header-tabs': !props.vertical && props.cardHeader && props.tabs,
|
|
'card-header-pills': !props.vertical && props.cardHeader && props.pills && !props.tabs,
|
|
'flex-column': props.vertical,
|
|
'nav-fill': !props.vertical && props.fill,
|
|
'nav-justified': !props.vertical && props.justified
|
|
}, _defineProperty(_class, computeJustifyContent(props.align), !props.vertical && props.align), _defineProperty(_class, "small", props.small), _class)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$Y = makePropsConfigurable(_objectSpread2(_objectSpread2({}, omit(props$1, ['event', 'routerTag'])), {}, {
|
|
linkAttrs: {
|
|
type: Object,
|
|
default: function _default() {}
|
|
},
|
|
linkClasses: {
|
|
type: [String, Object, Array],
|
|
default: null
|
|
}
|
|
}), NAME_NAV_ITEM); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BNavItem = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAV_ITEM,
|
|
functional: true,
|
|
props: props$Y,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
listeners = _ref.listeners,
|
|
children = _ref.children;
|
|
// We transfer the listeners to the link
|
|
delete data.on;
|
|
return h('li', a(data, {
|
|
staticClass: 'nav-item'
|
|
}), [h(BLink, {
|
|
staticClass: 'nav-link',
|
|
class: props.linkClasses,
|
|
attrs: props.linkAttrs,
|
|
props: props,
|
|
on: listeners
|
|
}, children)]);
|
|
}
|
|
});
|
|
|
|
var props$Z = {}; // @vue/component
|
|
|
|
var BNavText = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAV_TEXT,
|
|
functional: true,
|
|
props: props$Z,
|
|
render: function render(h, _ref) {
|
|
var data = _ref.data,
|
|
children = _ref.children;
|
|
return h('li', a(data, {
|
|
staticClass: 'navbar-text'
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var props$_ = makePropsConfigurable(_objectSpread2(_objectSpread2({}, omit(props$s, ['inline'])), {}, {
|
|
formClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}), NAME_NAV_FORM); // @vue/component
|
|
|
|
var BNavForm = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAV_FORM,
|
|
functional: true,
|
|
props: props$_,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children,
|
|
_ref$listeners = _ref.listeners,
|
|
listeners = _ref$listeners === void 0 ? {} : _ref$listeners;
|
|
var attrs = data.attrs; // The following data properties are cleared out
|
|
// as they will be passed to BForm directly
|
|
|
|
data.attrs = {};
|
|
data.on = {};
|
|
var $form = h(BForm, {
|
|
class: props.formClass,
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
inline: true
|
|
}),
|
|
attrs: attrs,
|
|
on: listeners
|
|
}, children);
|
|
return h('li', a(data, {
|
|
staticClass: 'form-inline'
|
|
}), [$form]);
|
|
}
|
|
});
|
|
|
|
var props$$ = makePropsConfigurable(pluckProps(['text', 'html', 'menuClass', 'toggleClass', 'noCaret', 'role', 'lazy'], props$n), NAME_NAV_ITEM_DROPDOWN); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BNavItemDropdown = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAV_ITEM_DROPDOWN,
|
|
mixins: [idMixin, dropdownMixin, normalizeSlotMixin],
|
|
props: props$$,
|
|
computed: {
|
|
toggleId: function toggleId() {
|
|
return this.safeId('_BV_toggle_');
|
|
},
|
|
dropdownClasses: function dropdownClasses() {
|
|
return [this.directionClass, this.boundaryClass, {
|
|
show: this.visible
|
|
}];
|
|
},
|
|
menuClasses: function menuClasses() {
|
|
return [this.menuClass, {
|
|
'dropdown-menu-right': this.right,
|
|
show: this.visible
|
|
}];
|
|
},
|
|
toggleClasses: function toggleClasses() {
|
|
return [this.toggleClass, {
|
|
'dropdown-toggle-no-caret': this.noCaret
|
|
}];
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var toggleId = this.toggleId,
|
|
visible = this.visible;
|
|
var $toggle = h(BLink, {
|
|
staticClass: 'nav-link dropdown-toggle',
|
|
class: this.toggleClasses,
|
|
props: {
|
|
href: "#".concat(this.id || ''),
|
|
disabled: this.disabled
|
|
},
|
|
attrs: {
|
|
id: toggleId,
|
|
role: 'button',
|
|
'aria-haspopup': 'true',
|
|
'aria-expanded': visible ? 'true' : 'false'
|
|
},
|
|
on: {
|
|
mousedown: this.onMousedown,
|
|
click: this.toggle,
|
|
keydown: this.toggle // Handle ENTER, SPACE and DOWN
|
|
|
|
},
|
|
ref: 'toggle'
|
|
}, [// TODO: The `text` slot is deprecated in favor of the `button-content` slot
|
|
this.normalizeSlot([SLOT_NAME_BUTTON_CONTENT, SLOT_NAME_TEXT]) || h('span', {
|
|
domProps: htmlOrText(this.html, this.text)
|
|
})]);
|
|
var $menu = h('ul', {
|
|
staticClass: 'dropdown-menu',
|
|
class: this.menuClasses,
|
|
attrs: {
|
|
tabindex: '-1',
|
|
'aria-labelledby': toggleId
|
|
},
|
|
on: {
|
|
keydown: this.onKeydown // Handle UP, DOWN and ESC
|
|
|
|
},
|
|
ref: 'menu'
|
|
}, !this.lazy || visible ? this.normalizeSlot(SLOT_NAME_DEFAULT, {
|
|
hide: this.hide
|
|
}) : [h()]);
|
|
return h('li', {
|
|
staticClass: 'nav-item b-nav-dropdown dropdown',
|
|
class: this.dropdownClasses,
|
|
attrs: {
|
|
id: this.safeId()
|
|
}
|
|
}, [$toggle, $menu]);
|
|
}
|
|
});
|
|
|
|
var NavPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BNav: BNav,
|
|
BNavItem: BNavItem,
|
|
BNavText: BNavText,
|
|
BNavForm: BNavForm,
|
|
BNavItemDropdown: BNavItemDropdown,
|
|
BNavItemDd: BNavItemDropdown,
|
|
BNavDropdown: BNavItemDropdown,
|
|
BNavDd: BNavItemDropdown
|
|
},
|
|
plugins: {
|
|
DropdownPlugin: DropdownPlugin
|
|
}
|
|
});
|
|
|
|
var props$10 = makePropsConfigurable({
|
|
tag: {
|
|
type: String,
|
|
default: 'nav'
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'light'
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
toggleable: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
fixed: {
|
|
type: String
|
|
},
|
|
sticky: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
print: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_NAVBAR); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BNavbar = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAVBAR,
|
|
mixins: [normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvNavbar: this
|
|
};
|
|
},
|
|
props: props$10,
|
|
computed: {
|
|
breakpointClass: function breakpointClass() {
|
|
var breakpoint = null;
|
|
var xs = getBreakpoints()[0];
|
|
var toggleable = this.toggleable;
|
|
|
|
if (toggleable && isString(toggleable) && toggleable !== xs) {
|
|
breakpoint = "navbar-expand-".concat(toggleable);
|
|
} else if (toggleable === false) {
|
|
breakpoint = 'navbar-expand';
|
|
}
|
|
|
|
return breakpoint;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _ref;
|
|
|
|
return h(this.tag, {
|
|
staticClass: 'navbar',
|
|
class: [(_ref = {
|
|
'd-print': this.print,
|
|
'sticky-top': this.sticky
|
|
}, _defineProperty(_ref, "navbar-".concat(this.type), this.type), _defineProperty(_ref, "bg-".concat(this.variant), this.variant), _defineProperty(_ref, "fixed-".concat(this.fixed), this.fixed), _ref), this.breakpointClass],
|
|
attrs: {
|
|
role: isTag(this.tag, 'nav') ? null : 'navigation'
|
|
}
|
|
}, [this.normalizeSlot()]);
|
|
}
|
|
});
|
|
|
|
var props$11 = makePropsConfigurable(pluckProps(['tag', 'fill', 'justified', 'align', 'small'], props$X), NAME_NAVBAR_NAV); // -- Utils --
|
|
|
|
var computeJustifyContent$1 = function computeJustifyContent(value) {
|
|
// Normalize value
|
|
value = value === 'left' ? 'start' : value === 'right' ? 'end' : value;
|
|
return "justify-content-".concat(value);
|
|
}; // @vue/component
|
|
|
|
|
|
var BNavbarNav = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAVBAR_NAV,
|
|
functional: true,
|
|
props: props$11,
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
return h(props.tag, a(data, {
|
|
staticClass: 'navbar-nav',
|
|
class: (_class = {
|
|
'nav-fill': props.fill,
|
|
'nav-justified': props.justified
|
|
}, _defineProperty(_class, computeJustifyContent$1(props.align), props.align), _defineProperty(_class, "small", props.small), _class)
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var linkProps$4 = omit(props$1, ['event', 'routerTag']);
|
|
linkProps$4.href.default = undefined;
|
|
linkProps$4.to.default = undefined;
|
|
var props$12 = makePropsConfigurable(_objectSpread2({
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
}, linkProps$4), NAME_NAVBAR_BRAND); // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BNavbarBrand = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAVBAR_BRAND,
|
|
functional: true,
|
|
props: props$12,
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
children = _ref.children;
|
|
var isLink = props.to || props.href;
|
|
var tag = isLink ? BLink : props.tag;
|
|
return h(tag, a(data, {
|
|
staticClass: 'navbar-brand',
|
|
props: isLink ? pluckProps(linkProps$4, props) : {}
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var CLASS_NAME$2 = 'navbar-toggler'; // --- Main component ---
|
|
// @vue/component
|
|
|
|
var BNavbarToggle = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_NAVBAR_TOGGLE,
|
|
directives: {
|
|
VBToggle: VBToggle
|
|
},
|
|
mixins: [listenOnRootMixin, normalizeSlotMixin],
|
|
props: makePropsConfigurable({
|
|
label: {
|
|
type: String,
|
|
default: 'Toggle navigation'
|
|
},
|
|
target: {
|
|
type: [Array, String],
|
|
required: true
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_NAVBAR_TOGGLE),
|
|
data: function data() {
|
|
return {
|
|
toggleState: false
|
|
};
|
|
},
|
|
created: function created() {
|
|
this.listenOnRoot(EVENT_STATE, this.handleStateEvt);
|
|
this.listenOnRoot(EVENT_STATE_SYNC, this.handleStateEvt);
|
|
},
|
|
methods: {
|
|
onClick: function onClick(evt) {
|
|
if (!this.disabled) {
|
|
// Emit courtesy `click` event
|
|
this.$emit('click', evt);
|
|
}
|
|
},
|
|
handleStateEvt: function handleStateEvt(id, state) {
|
|
// We listen for state events so that we can pass the
|
|
// boolean expanded state to the default scoped slot
|
|
if (id === this.target) {
|
|
this.toggleState = state;
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var disabled = this.disabled;
|
|
return h('button', {
|
|
staticClass: CLASS_NAME$2,
|
|
class: {
|
|
disabled: disabled
|
|
},
|
|
directives: [{
|
|
name: 'VBToggle',
|
|
value: this.target
|
|
}],
|
|
attrs: {
|
|
type: 'button',
|
|
disabled: disabled,
|
|
'aria-label': this.label
|
|
},
|
|
on: {
|
|
click: this.onClick
|
|
}
|
|
}, [this.normalizeSlot(SLOT_NAME_DEFAULT, {
|
|
expanded: this.toggleState
|
|
}) || h('span', {
|
|
staticClass: "".concat(CLASS_NAME$2, "-icon")
|
|
})]);
|
|
}
|
|
});
|
|
|
|
var NavbarPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BNavbar: BNavbar,
|
|
BNavbarNav: BNavbarNav,
|
|
BNavbarBrand: BNavbarBrand,
|
|
BNavbarToggle: BNavbarToggle,
|
|
BNavToggle: BNavbarToggle
|
|
},
|
|
plugins: {
|
|
NavPlugin: NavPlugin,
|
|
CollapsePlugin: CollapsePlugin,
|
|
DropdownPlugin: DropdownPlugin
|
|
}
|
|
});
|
|
|
|
var BSpinner = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SPINNER,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
type: {
|
|
type: String,
|
|
default: 'border' // SCSS currently supports 'border' or 'grow'
|
|
|
|
},
|
|
label: {
|
|
type: String // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
small: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
role: {
|
|
type: String,
|
|
default: 'status'
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'span'
|
|
}
|
|
}, NAME_SPINNER),
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var props = _ref.props,
|
|
data = _ref.data,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var $slots = slots();
|
|
var $scopedSlots = scopedSlots || {};
|
|
var label = normalizeSlot(SLOT_NAME_LABEL, {}, $scopedSlots, $slots) || props.label;
|
|
|
|
if (label) {
|
|
label = h('span', {
|
|
staticClass: 'sr-only'
|
|
}, label);
|
|
}
|
|
|
|
return h(props.tag, a(data, {
|
|
attrs: {
|
|
role: label ? props.role || 'status' : null,
|
|
'aria-hidden': label ? null : 'true'
|
|
},
|
|
class: (_class = {}, _defineProperty(_class, "spinner-".concat(props.type), props.type), _defineProperty(_class, "spinner-".concat(props.type, "-sm"), props.small), _defineProperty(_class, "text-".concat(props.variant), props.variant), _class)
|
|
}), [label || h()]);
|
|
}
|
|
});
|
|
|
|
var positionCover = {
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0
|
|
};
|
|
var BOverlay = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_OVERLAY,
|
|
mixins: [normalizeSlotMixin],
|
|
props: makePropsConfigurable({
|
|
show: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: 'light'
|
|
},
|
|
bgColor: {
|
|
// Alternative to variant, allowing a specific
|
|
// CSS color to be applied to the overlay
|
|
type: String // default: null
|
|
|
|
},
|
|
opacity: {
|
|
type: [Number, String],
|
|
default: 0.85,
|
|
validator: function validator(value) {
|
|
var number = toFloat(value, 0);
|
|
return number >= 0 && number <= 1;
|
|
}
|
|
},
|
|
blur: {
|
|
type: String,
|
|
default: '2px'
|
|
},
|
|
rounded: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
noCenter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noFade: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
spinnerType: {
|
|
type: String,
|
|
default: 'border'
|
|
},
|
|
spinnerVariant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
spinnerSmall: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
overlayTag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
wrapTag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
noWrap: {
|
|
// If set, does not render the default slot
|
|
// and switches to absolute positioning
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fixed: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
zIndex: {
|
|
type: [Number, String],
|
|
default: 10
|
|
}
|
|
}, NAME_OVERLAY),
|
|
computed: {
|
|
computedRounded: function computedRounded() {
|
|
var rounded = this.rounded;
|
|
return rounded === true || rounded === '' ? 'rounded' : !rounded ? '' : "rounded-".concat(rounded);
|
|
},
|
|
computedVariant: function computedVariant() {
|
|
return this.variant && !this.bgColor ? "bg-".concat(this.variant) : '';
|
|
},
|
|
overlayScope: function overlayScope() {
|
|
return {
|
|
spinnerType: this.spinnerType || null,
|
|
spinnerVariant: this.spinnerVariant || null,
|
|
spinnerSmall: this.spinnerSmall
|
|
};
|
|
}
|
|
},
|
|
methods: {
|
|
defaultOverlayFn: function defaultOverlayFn(_ref) {
|
|
var spinnerType = _ref.spinnerType,
|
|
spinnerVariant = _ref.spinnerVariant,
|
|
spinnerSmall = _ref.spinnerSmall;
|
|
return this.$createElement(BSpinner, {
|
|
props: {
|
|
type: spinnerType,
|
|
variant: spinnerVariant,
|
|
small: spinnerSmall
|
|
}
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this = this;
|
|
|
|
var $overlay = h();
|
|
|
|
if (this.show) {
|
|
var scope = this.overlayScope; // Overlay backdrop
|
|
|
|
var $background = h('div', {
|
|
staticClass: 'position-absolute',
|
|
class: [this.computedVariant, this.computedRounded],
|
|
style: _objectSpread2(_objectSpread2({}, positionCover), {}, {
|
|
opacity: this.opacity,
|
|
backgroundColor: this.bgColor || null,
|
|
backdropFilter: this.blur ? "blur(".concat(this.blur, ")") : null
|
|
})
|
|
}); // Overlay content
|
|
|
|
var $content = h('div', {
|
|
staticClass: 'position-absolute',
|
|
style: this.noCenter ?
|
|
/* istanbul ignore next */
|
|
_objectSpread2({}, positionCover) : {
|
|
top: '50%',
|
|
left: '50%',
|
|
transform: 'translateX(-50%) translateY(-50%)'
|
|
}
|
|
}, [this.normalizeSlot('overlay', scope) || this.defaultOverlayFn(scope)]); // Overlay positioning
|
|
|
|
$overlay = h(this.overlayTag, {
|
|
key: 'overlay',
|
|
staticClass: 'b-overlay',
|
|
class: {
|
|
'position-absolute': !this.noWrap || this.noWrap && !this.fixed,
|
|
'position-fixed': this.noWrap && this.fixed
|
|
},
|
|
style: _objectSpread2(_objectSpread2({}, positionCover), {}, {
|
|
zIndex: this.zIndex || 10
|
|
}),
|
|
on: {
|
|
click: function click(evt) {
|
|
return _this.$emit('click', evt);
|
|
}
|
|
}
|
|
}, [$background, $content]);
|
|
} // Wrap in a fade transition
|
|
|
|
|
|
$overlay = h(BVTransition, {
|
|
props: {
|
|
noFade: this.noFade,
|
|
appear: true
|
|
},
|
|
on: {
|
|
'after-enter': function afterEnter() {
|
|
return _this.$emit('shown');
|
|
},
|
|
'after-leave': function afterLeave() {
|
|
return _this.$emit('hidden');
|
|
}
|
|
}
|
|
}, [$overlay]);
|
|
|
|
if (this.noWrap) {
|
|
return $overlay;
|
|
}
|
|
|
|
return h(this.wrapTag, {
|
|
staticClass: 'b-overlay-wrap position-relative',
|
|
attrs: {
|
|
'aria-busy': this.show ? 'true' : null
|
|
}
|
|
}, this.noWrap ? [$overlay] : [this.normalizeSlot(), $overlay]);
|
|
}
|
|
});
|
|
|
|
var OverlayPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BOverlay: BOverlay
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @param {number} length
|
|
* @return {Array}
|
|
*/
|
|
var range = function range(length) {
|
|
return Array.apply(null, {
|
|
length: length
|
|
});
|
|
};
|
|
|
|
// for `<b-pagination>` and `<b-pagination-nav>`
|
|
// --- Constants ---
|
|
// Threshold of limit size when we start/stop showing ellipsis
|
|
|
|
var ELLIPSIS_THRESHOLD = 3; // Default # of buttons limit
|
|
|
|
var DEFAULT_LIMIT = 5; // --- Helper methods ---
|
|
// Make an array of N to N+X
|
|
|
|
var makePageArray = function makePageArray(startNumber, numberOfPages) {
|
|
return range(numberOfPages).map(function (val, i) {
|
|
return {
|
|
number: startNumber + i,
|
|
classes: null
|
|
};
|
|
});
|
|
}; // Sanitize the provided limit value (converting to a number)
|
|
|
|
|
|
var sanitizeLimit = function sanitizeLimit(val) {
|
|
var limit = toInteger(val) || 1;
|
|
return limit < 1 ? DEFAULT_LIMIT : limit;
|
|
}; // Sanitize the provided current page number (converting to a number)
|
|
|
|
|
|
var sanitizeCurrentPage = function sanitizeCurrentPage(val, numberOfPages) {
|
|
var page = toInteger(val) || 1;
|
|
return page > numberOfPages ? numberOfPages : page < 1 ? 1 : page;
|
|
}; // Links don't normally respond to SPACE, so we add that
|
|
// functionality via this handler
|
|
|
|
|
|
var onSpaceKey = function onSpaceKey(evt) {
|
|
if (evt.keyCode === CODE_SPACE) {
|
|
// Stop page from scrolling
|
|
stopEvent(evt, {
|
|
immediatePropagation: true
|
|
}); // Trigger the click event on the link
|
|
|
|
evt.currentTarget.click();
|
|
return false;
|
|
}
|
|
}; // --- Props ---
|
|
|
|
|
|
var props$13 = makePropsConfigurable({
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
value: {
|
|
type: [Number, String],
|
|
default: null,
|
|
|
|
/* istanbul ignore next */
|
|
validator: function validator(value) {
|
|
if (!isNull(value) && toInteger(value, 0) < 1) {
|
|
warn('"v-model" value must be a number greater than "0"', NAME_PAGINATION);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
limit: {
|
|
type: [Number, String],
|
|
default: DEFAULT_LIMIT,
|
|
|
|
/* istanbul ignore next */
|
|
validator: function validator(value) {
|
|
if (toInteger(value, 0) < 1) {
|
|
warn('Prop "limit" must be a number greater than "0"', NAME_PAGINATION);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
align: {
|
|
type: String,
|
|
default: 'left'
|
|
},
|
|
pills: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hideGotoEndButtons: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ariaLabel: {
|
|
type: String,
|
|
default: 'Pagination'
|
|
},
|
|
labelFirstPage: {
|
|
type: String,
|
|
default: 'Go to first page'
|
|
},
|
|
firstText: {
|
|
type: String,
|
|
default: "\xAB" // '«'
|
|
|
|
},
|
|
firstNumber: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
firstClass: {
|
|
type: [String, Array, Object],
|
|
default: null
|
|
},
|
|
labelPrevPage: {
|
|
type: String,
|
|
default: 'Go to previous page'
|
|
},
|
|
prevText: {
|
|
type: String,
|
|
default: "\u2039" // '‹'
|
|
|
|
},
|
|
prevClass: {
|
|
type: [String, Array, Object],
|
|
default: null
|
|
},
|
|
labelNextPage: {
|
|
type: String,
|
|
default: 'Go to next page'
|
|
},
|
|
nextText: {
|
|
type: String,
|
|
default: "\u203A" // '›'
|
|
|
|
},
|
|
nextClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
labelLastPage: {
|
|
type: String,
|
|
default: 'Go to last page'
|
|
},
|
|
lastText: {
|
|
type: String,
|
|
default: "\xBB" // '»'
|
|
|
|
},
|
|
lastNumber: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lastClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
labelPage: {
|
|
type: [String, Function],
|
|
default: 'Go to page'
|
|
},
|
|
pageClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
hideEllipsis: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ellipsisText: {
|
|
type: String,
|
|
default: "\u2026" // '…'
|
|
|
|
},
|
|
ellipsisClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}, NAME_PAGINATION); // --- Mixin ---
|
|
// @vue/component
|
|
|
|
var paginationMixin = {
|
|
mixins: [normalizeSlotMixin],
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: props$13,
|
|
data: function data() {
|
|
// `-1` signifies no page initially selected
|
|
var currentPage = toInteger(this.value, 0);
|
|
currentPage = currentPage > 0 ? currentPage : -1;
|
|
return {
|
|
currentPage: currentPage,
|
|
localNumberOfPages: 1,
|
|
localLimit: DEFAULT_LIMIT
|
|
};
|
|
},
|
|
computed: {
|
|
btnSize: function btnSize() {
|
|
return this.size ? "pagination-".concat(this.size) : '';
|
|
},
|
|
alignment: function alignment() {
|
|
var align = this.align;
|
|
|
|
if (align === 'center') {
|
|
return 'justify-content-center';
|
|
} else if (align === 'end' || align === 'right') {
|
|
return 'justify-content-end';
|
|
} else if (align === 'fill') {
|
|
// The page-items will also have 'flex-fill' added
|
|
// We add text centering to make the button appearance better in fill mode
|
|
return 'text-center';
|
|
}
|
|
|
|
return '';
|
|
},
|
|
styleClass: function styleClass() {
|
|
return this.pills ? 'b-pagination-pills' : '';
|
|
},
|
|
computedCurrentPage: function computedCurrentPage() {
|
|
return sanitizeCurrentPage(this.currentPage, this.localNumberOfPages);
|
|
},
|
|
paginationParams: function paginationParams() {
|
|
// Determine if we should show the the ellipsis
|
|
var limit = this.localLimit,
|
|
numberOfPages = this.localNumberOfPages,
|
|
currentPage = this.computedCurrentPage,
|
|
hideEllipsis = this.hideEllipsis,
|
|
firstNumber = this.firstNumber,
|
|
lastNumber = this.lastNumber;
|
|
var showFirstDots = false;
|
|
var showLastDots = false;
|
|
var numberOfLinks = limit;
|
|
var startNumber = 1;
|
|
|
|
if (numberOfPages <= limit) {
|
|
// Special case: Less pages available than the limit of displayed pages
|
|
numberOfLinks = numberOfPages;
|
|
} else if (currentPage < limit - 1 && limit > ELLIPSIS_THRESHOLD) {
|
|
if (!hideEllipsis || lastNumber) {
|
|
showLastDots = true;
|
|
numberOfLinks = limit - (firstNumber ? 0 : 1);
|
|
}
|
|
|
|
numberOfLinks = mathMin(numberOfLinks, limit);
|
|
} else if (numberOfPages - currentPage + 2 < limit && limit > ELLIPSIS_THRESHOLD) {
|
|
if (!hideEllipsis || firstNumber) {
|
|
showFirstDots = true;
|
|
numberOfLinks = limit - (lastNumber ? 0 : 1);
|
|
}
|
|
|
|
startNumber = numberOfPages - numberOfLinks + 1;
|
|
} else {
|
|
// We are somewhere in the middle of the page list
|
|
if (limit > ELLIPSIS_THRESHOLD) {
|
|
numberOfLinks = limit - (hideEllipsis ? 0 : 2);
|
|
showFirstDots = !!(!hideEllipsis || firstNumber);
|
|
showLastDots = !!(!hideEllipsis || lastNumber);
|
|
}
|
|
|
|
startNumber = currentPage - mathFloor(numberOfLinks / 2);
|
|
} // Sanity checks
|
|
|
|
/* istanbul ignore if */
|
|
|
|
|
|
if (startNumber < 1) {
|
|
startNumber = 1;
|
|
showFirstDots = false;
|
|
} else if (startNumber > numberOfPages - numberOfLinks) {
|
|
startNumber = numberOfPages - numberOfLinks + 1;
|
|
showLastDots = false;
|
|
}
|
|
|
|
if (showFirstDots && firstNumber && startNumber < 4) {
|
|
numberOfLinks = numberOfLinks + 2;
|
|
startNumber = 1;
|
|
showFirstDots = false;
|
|
}
|
|
|
|
var lastPageNumber = startNumber + numberOfLinks - 1;
|
|
|
|
if (showLastDots && lastNumber && lastPageNumber > numberOfPages - 3) {
|
|
numberOfLinks = numberOfLinks + (lastPageNumber === numberOfPages - 2 ? 2 : 3);
|
|
showLastDots = false;
|
|
} // Special handling for lower limits (where ellipsis are never shown)
|
|
|
|
|
|
if (limit <= ELLIPSIS_THRESHOLD) {
|
|
if (firstNumber && startNumber === 1) {
|
|
numberOfLinks = mathMin(numberOfLinks + 1, numberOfPages, limit + 1);
|
|
} else if (lastNumber && numberOfPages === startNumber + numberOfLinks - 1) {
|
|
startNumber = mathMax(startNumber - 1, 1);
|
|
numberOfLinks = mathMin(numberOfPages - startNumber + 1, numberOfPages, limit + 1);
|
|
}
|
|
}
|
|
|
|
numberOfLinks = mathMin(numberOfLinks, numberOfPages - startNumber + 1);
|
|
return {
|
|
showFirstDots: showFirstDots,
|
|
showLastDots: showLastDots,
|
|
numberOfLinks: numberOfLinks,
|
|
startNumber: startNumber
|
|
};
|
|
},
|
|
pageList: function pageList() {
|
|
// Generates the pageList array
|
|
var _this$paginationParam = this.paginationParams,
|
|
numberOfLinks = _this$paginationParam.numberOfLinks,
|
|
startNumber = _this$paginationParam.startNumber;
|
|
var currentPage = this.computedCurrentPage; // Generate list of page numbers
|
|
|
|
var pages = makePageArray(startNumber, numberOfLinks); // We limit to a total of 3 page buttons on XS screens
|
|
// So add classes to page links to hide them for XS breakpoint
|
|
// Note: Ellipsis will also be hidden on XS screens
|
|
// TODO: Make this visual limit configurable based on breakpoint(s)
|
|
|
|
if (pages.length > 3) {
|
|
var idx = currentPage - startNumber; // THe following is a bootstrap-vue custom utility class
|
|
|
|
var classes = 'bv-d-xs-down-none';
|
|
|
|
if (idx === 0) {
|
|
// Keep leftmost 3 buttons visible when current page is first page
|
|
for (var i = 3; i < pages.length; i++) {
|
|
pages[i].classes = classes;
|
|
}
|
|
} else if (idx === pages.length - 1) {
|
|
// Keep rightmost 3 buttons visible when current page is last page
|
|
for (var _i = 0; _i < pages.length - 3; _i++) {
|
|
pages[_i].classes = classes;
|
|
}
|
|
} else {
|
|
// Hide all except current page, current page - 1 and current page + 1
|
|
for (var _i2 = 0; _i2 < idx - 1; _i2++) {
|
|
// hide some left button(s)
|
|
pages[_i2].classes = classes;
|
|
}
|
|
|
|
for (var _i3 = pages.length - 1; _i3 > idx + 1; _i3--) {
|
|
// hide some right button(s)
|
|
pages[_i3].classes = classes;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
},
|
|
watch: {
|
|
value: function value(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
this.currentPage = sanitizeCurrentPage(newValue, this.localNumberOfPages);
|
|
}
|
|
},
|
|
currentPage: function currentPage(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
// Emit `null` if no page selected
|
|
this.$emit('input', newValue > 0 ? newValue : null);
|
|
}
|
|
},
|
|
limit: function limit(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
this.localLimit = sanitizeLimit(newValue);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
// Set our default values in data
|
|
this.localLimit = sanitizeLimit(this.limit);
|
|
this.$nextTick(function () {
|
|
// Sanity check
|
|
_this.currentPage = _this.currentPage > _this.localNumberOfPages ? _this.localNumberOfPages : _this.currentPage;
|
|
});
|
|
},
|
|
methods: {
|
|
handleKeyNav: function handleKeyNav(evt) {
|
|
var keyCode = evt.keyCode,
|
|
shiftKey = evt.shiftKey;
|
|
/* istanbul ignore if */
|
|
|
|
if (this.isNav) {
|
|
// We disable left/right keyboard navigation in `<b-pagination-nav>`
|
|
return;
|
|
}
|
|
|
|
if (keyCode === CODE_LEFT || keyCode === CODE_UP) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
shiftKey ? this.focusFirst() : this.focusPrev();
|
|
} else if (keyCode === CODE_RIGHT || keyCode === CODE_DOWN) {
|
|
stopEvent(evt, {
|
|
propagation: false
|
|
});
|
|
shiftKey ? this.focusLast() : this.focusNext();
|
|
}
|
|
},
|
|
getButtons: function getButtons() {
|
|
// Return only buttons that are visible
|
|
return selectAll('button.page-link, a.page-link', this.$el).filter(function (btn) {
|
|
return isVisible(btn);
|
|
});
|
|
},
|
|
focusCurrent: function focusCurrent() {
|
|
var _this2 = this;
|
|
|
|
// We do this in `$nextTick()` to ensure buttons have finished rendering
|
|
this.$nextTick(function () {
|
|
var btn = _this2.getButtons().find(function (el) {
|
|
return toInteger(getAttr(el, 'aria-posinset'), 0) === _this2.computedCurrentPage;
|
|
});
|
|
|
|
if (!attemptFocus(btn)) {
|
|
// Fallback if current page is not in button list
|
|
_this2.focusFirst();
|
|
}
|
|
});
|
|
},
|
|
focusFirst: function focusFirst() {
|
|
var _this3 = this;
|
|
|
|
// We do this in `$nextTick()` to ensure buttons have finished rendering
|
|
this.$nextTick(function () {
|
|
var btn = _this3.getButtons().find(function (el) {
|
|
return !isDisabled(el);
|
|
});
|
|
|
|
attemptFocus(btn);
|
|
});
|
|
},
|
|
focusLast: function focusLast() {
|
|
var _this4 = this;
|
|
|
|
// We do this in `$nextTick()` to ensure buttons have finished rendering
|
|
this.$nextTick(function () {
|
|
var btn = _this4.getButtons().reverse().find(function (el) {
|
|
return !isDisabled(el);
|
|
});
|
|
|
|
attemptFocus(btn);
|
|
});
|
|
},
|
|
focusPrev: function focusPrev() {
|
|
var _this5 = this;
|
|
|
|
// We do this in `$nextTick()` to ensure buttons have finished rendering
|
|
this.$nextTick(function () {
|
|
var buttons = _this5.getButtons();
|
|
|
|
var index = buttons.indexOf(getActiveElement());
|
|
|
|
if (index > 0 && !isDisabled(buttons[index - 1])) {
|
|
attemptFocus(buttons[index - 1]);
|
|
}
|
|
});
|
|
},
|
|
focusNext: function focusNext() {
|
|
var _this6 = this;
|
|
|
|
// We do this in `$nextTick()` to ensure buttons have finished rendering
|
|
this.$nextTick(function () {
|
|
var buttons = _this6.getButtons();
|
|
|
|
var index = buttons.indexOf(getActiveElement());
|
|
|
|
if (index < buttons.length - 1 && !isDisabled(buttons[index + 1])) {
|
|
attemptFocus(buttons[index + 1]);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this7 = this;
|
|
|
|
var buttons = [];
|
|
var numberOfPages = this.localNumberOfPages;
|
|
var pageNumbers = this.pageList.map(function (p) {
|
|
return p.number;
|
|
});
|
|
var disabled = this.disabled;
|
|
var _this$paginationParam2 = this.paginationParams,
|
|
showFirstDots = _this$paginationParam2.showFirstDots,
|
|
showLastDots = _this$paginationParam2.showLastDots;
|
|
var currentPage = this.computedCurrentPage;
|
|
var fill = this.align === 'fill'; // Used to control what type of aria attributes are rendered and wrapper
|
|
|
|
var isNav = this.isNav; // Helper function and flag
|
|
|
|
var isActivePage = function isActivePage(pageNumber) {
|
|
return pageNumber === currentPage;
|
|
};
|
|
|
|
var noCurrentPage = this.currentPage < 1; // Factory function for prev/next/first/last buttons
|
|
|
|
var makeEndBtn = function makeEndBtn(linkTo, ariaLabel, btnSlot, btnText, btnClass, pageTest, key) {
|
|
var isDisabled = disabled || isActivePage(pageTest) || noCurrentPage || linkTo < 1 || linkTo > numberOfPages;
|
|
var pageNumber = linkTo < 1 ? 1 : linkTo > numberOfPages ? numberOfPages : linkTo;
|
|
var scope = {
|
|
disabled: isDisabled,
|
|
page: pageNumber,
|
|
index: pageNumber - 1
|
|
};
|
|
var $btnContent = _this7.normalizeSlot(btnSlot, scope) || toString$1(btnText) || h();
|
|
var $inner = h(isDisabled ? 'span' : isNav ? BLink : 'button', {
|
|
staticClass: 'page-link',
|
|
class: {
|
|
'flex-grow-1': !isNav && !isDisabled && fill
|
|
},
|
|
props: isDisabled || !isNav ? {} : _this7.linkProps(linkTo),
|
|
attrs: {
|
|
role: isNav ? null : 'menuitem',
|
|
type: isNav || isDisabled ? null : 'button',
|
|
tabindex: isDisabled || isNav ? null : '-1',
|
|
'aria-label': ariaLabel,
|
|
'aria-controls': _this7.ariaControls || null,
|
|
'aria-disabled': isDisabled ? 'true' : null
|
|
},
|
|
on: isDisabled ? {} : {
|
|
'!click': function click(evt) {
|
|
_this7.onClick(evt, linkTo);
|
|
},
|
|
keydown: onSpaceKey
|
|
}
|
|
}, [$btnContent]);
|
|
return h('li', {
|
|
key: key,
|
|
staticClass: 'page-item',
|
|
class: [{
|
|
disabled: isDisabled,
|
|
'flex-fill': fill,
|
|
'd-flex': fill && !isNav && !isDisabled
|
|
}, btnClass],
|
|
attrs: {
|
|
role: isNav ? null : 'presentation',
|
|
'aria-hidden': isDisabled ? 'true' : null
|
|
}
|
|
}, [$inner]);
|
|
}; // Ellipsis factory
|
|
|
|
|
|
var makeEllipsis = function makeEllipsis(isLast) {
|
|
return h('li', {
|
|
key: "ellipsis-".concat(isLast ? 'last' : 'first'),
|
|
staticClass: 'page-item',
|
|
class: ['disabled', 'bv-d-xs-down-none', fill ? 'flex-fill' : '', _this7.ellipsisClass],
|
|
attrs: {
|
|
role: 'separator'
|
|
}
|
|
}, [h('span', {
|
|
staticClass: 'page-link'
|
|
}, [_this7.normalizeSlot('ellipsis-text') || toString$1(_this7.ellipsisText) || h()])]);
|
|
}; // Page button factory
|
|
|
|
|
|
var makePageButton = function makePageButton(page, idx) {
|
|
var active = isActivePage(page.number) && !noCurrentPage; // Active page will have tabindex of 0, or if no current page and first page button
|
|
|
|
var tabIndex = disabled ? null : active || noCurrentPage && idx === 0 ? '0' : '-1';
|
|
var attrs = {
|
|
role: isNav ? null : 'menuitemradio',
|
|
type: isNav || disabled ? null : 'button',
|
|
'aria-disabled': disabled ? 'true' : null,
|
|
'aria-controls': _this7.ariaControls || null,
|
|
'aria-label': isFunction(_this7.labelPage) && !isUndefined(_this7.labelPage(page.number)) ?
|
|
/* istanbul ignore next */
|
|
_this7.labelPage(page.number) : "".concat(_this7.labelPage, " ").concat(page.number),
|
|
'aria-checked': isNav ? null : active ? 'true' : 'false',
|
|
'aria-current': isNav && active ? 'page' : null,
|
|
'aria-posinset': isNav ? null : page.number,
|
|
'aria-setsize': isNav ? null : numberOfPages,
|
|
// ARIA "roving tabindex" method (except in `isNav` mode)
|
|
tabindex: isNav ? null : tabIndex
|
|
};
|
|
var btnContent = toString$1(_this7.makePage(page.number));
|
|
var scope = {
|
|
page: page.number,
|
|
index: page.number - 1,
|
|
content: btnContent,
|
|
active: active,
|
|
disabled: disabled
|
|
};
|
|
var $inner = h(disabled ? 'span' : isNav ? BLink : 'button', {
|
|
props: disabled || !isNav ? {} : _this7.linkProps(page.number),
|
|
staticClass: 'page-link',
|
|
class: {
|
|
'flex-grow-1': !isNav && !disabled && fill
|
|
},
|
|
attrs: attrs,
|
|
on: disabled ? {} : {
|
|
'!click': function click(evt) {
|
|
_this7.onClick(evt, page.number);
|
|
},
|
|
keydown: onSpaceKey
|
|
}
|
|
}, [_this7.normalizeSlot('page', scope) || btnContent]);
|
|
return h('li', {
|
|
key: "page-".concat(page.number),
|
|
staticClass: 'page-item',
|
|
class: [{
|
|
disabled: disabled,
|
|
active: active,
|
|
'flex-fill': fill,
|
|
'd-flex': fill && !isNav && !disabled
|
|
}, page.classes, _this7.pageClass],
|
|
attrs: {
|
|
role: isNav ? null : 'presentation'
|
|
}
|
|
}, [$inner]);
|
|
}; // Goto first page button
|
|
// Don't render button when `hideGotoEndButtons` or `firstNumber` is set
|
|
|
|
|
|
var $firstPageBtn = h();
|
|
|
|
if (!this.firstNumber && !this.hideGotoEndButtons) {
|
|
$firstPageBtn = makeEndBtn(1, this.labelFirstPage, 'first-text', this.firstText, this.firstClass, 1, 'pagination-goto-first');
|
|
}
|
|
|
|
buttons.push($firstPageBtn); // Goto previous page button
|
|
|
|
buttons.push(makeEndBtn(currentPage - 1, this.labelPrevPage, 'prev-text', this.prevText, this.prevClass, 1, 'pagination-goto-prev')); // Show first (1) button?
|
|
|
|
buttons.push(this.firstNumber && pageNumbers[0] !== 1 ? makePageButton({
|
|
number: 1
|
|
}, 0) : h()); // First ellipsis
|
|
|
|
buttons.push(showFirstDots ? makeEllipsis(false) : h()); // Individual page links
|
|
|
|
this.pageList.forEach(function (page, idx) {
|
|
var offset = showFirstDots && _this7.firstNumber && pageNumbers[0] !== 1 ? 1 : 0;
|
|
buttons.push(makePageButton(page, idx + offset));
|
|
}); // Last ellipsis
|
|
|
|
buttons.push(showLastDots ? makeEllipsis(true) : h()); // Show last page button?
|
|
|
|
buttons.push(this.lastNumber && pageNumbers[pageNumbers.length - 1] !== numberOfPages ? makePageButton({
|
|
number: numberOfPages
|
|
}, -1) : h()); // Goto next page button
|
|
|
|
buttons.push(makeEndBtn(currentPage + 1, this.labelNextPage, 'next-text', this.nextText, this.nextClass, numberOfPages, 'pagination-goto-next')); // Goto last page button
|
|
// Don't render button when `hideGotoEndButtons` or `lastNumber` is set
|
|
|
|
var $lastPageBtn = h();
|
|
|
|
if (!this.lastNumber && !this.hideGotoEndButtons) {
|
|
$lastPageBtn = makeEndBtn(numberOfPages, this.labelLastPage, 'last-text', this.lastText, this.lastClass, numberOfPages, 'pagination-goto-last');
|
|
}
|
|
|
|
buttons.push($lastPageBtn); // Assemble the pagination buttons
|
|
|
|
var $pagination = h('ul', {
|
|
ref: 'ul',
|
|
staticClass: 'pagination',
|
|
class: ['b-pagination', this.btnSize, this.alignment, this.styleClass],
|
|
attrs: {
|
|
role: isNav ? null : 'menubar',
|
|
'aria-disabled': disabled ? 'true' : 'false',
|
|
'aria-label': isNav ? null : this.ariaLabel || null
|
|
},
|
|
// We disable keyboard left/right nav when `<b-pagination-nav>`
|
|
on: isNav ? {} : {
|
|
keydown: this.handleKeyNav
|
|
}
|
|
}, buttons); // If we are `<b-pagination-nav>`, wrap in `<nav>` wrapper
|
|
|
|
if (isNav) {
|
|
return h('nav', {
|
|
attrs: {
|
|
'aria-disabled': disabled ? 'true' : null,
|
|
'aria-hidden': disabled ? 'true' : 'false',
|
|
'aria-label': isNav ? this.ariaLabel || null : null
|
|
}
|
|
}, [$pagination]);
|
|
}
|
|
|
|
return $pagination;
|
|
}
|
|
};
|
|
|
|
var DEFAULT_PER_PAGE = 20;
|
|
var DEFAULT_TOTAL_ROWS = 0; // --- Helper methods ---
|
|
// Sanitize the provided per page number (converting to a number)
|
|
|
|
var sanitizePerPage = function sanitizePerPage(val) {
|
|
return mathMax(toInteger(val) || DEFAULT_PER_PAGE, 1);
|
|
}; // Sanitize the provided total rows number (converting to a number)
|
|
|
|
|
|
var sanitizeTotalRows = function sanitizeTotalRows(val) {
|
|
return mathMax(toInteger(val) || DEFAULT_TOTAL_ROWS, 0);
|
|
}; // --- Main component ---
|
|
// The render function is brought in via the `paginationMixin`
|
|
// @vue/component
|
|
|
|
|
|
var BPagination = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_PAGINATION,
|
|
mixins: [paginationMixin],
|
|
props: makePropsConfigurable({
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
perPage: {
|
|
type: [Number, String],
|
|
default: DEFAULT_PER_PAGE
|
|
},
|
|
totalRows: {
|
|
type: [Number, String],
|
|
default: DEFAULT_TOTAL_ROWS
|
|
},
|
|
ariaControls: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_PAGINATION),
|
|
computed: {
|
|
numberOfPages: function numberOfPages() {
|
|
var result = mathCeil(sanitizeTotalRows(this.totalRows) / sanitizePerPage(this.perPage));
|
|
return result < 1 ? 1 : result;
|
|
},
|
|
pageSizeNumberOfPages: function pageSizeNumberOfPages() {
|
|
// Used for watching changes to `perPage` and `numberOfPages`
|
|
return {
|
|
perPage: sanitizePerPage(this.perPage),
|
|
totalRows: sanitizeTotalRows(this.totalRows),
|
|
numberOfPages: this.numberOfPages
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
pageSizeNumberOfPages: function pageSizeNumberOfPages(newVal, oldVal) {
|
|
if (!isUndefinedOrNull(oldVal)) {
|
|
if (newVal.perPage !== oldVal.perPage && newVal.totalRows === oldVal.totalRows) {
|
|
// If the page size changes, reset to page 1
|
|
this.currentPage = 1;
|
|
} else if (newVal.numberOfPages !== oldVal.numberOfPages && this.currentPage > newVal.numberOfPages) {
|
|
// If `numberOfPages` changes and is less than
|
|
// the `currentPage` number, reset to page 1
|
|
this.currentPage = 1;
|
|
}
|
|
}
|
|
|
|
this.localNumberOfPages = newVal.numberOfPages;
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
// Set the initial page count
|
|
this.localNumberOfPages = this.numberOfPages; // Set the initial page value
|
|
|
|
var currentPage = toInteger(this.value, 0);
|
|
|
|
if (currentPage > 0) {
|
|
this.currentPage = currentPage;
|
|
} else {
|
|
this.$nextTick(function () {
|
|
// If this value parses to `NaN` or a value less than `1`
|
|
// trigger an initial emit of `null` if no page specified
|
|
_this.currentPage = 0;
|
|
});
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
// Set the initial page count
|
|
this.localNumberOfPages = this.numberOfPages;
|
|
},
|
|
methods: {
|
|
// These methods are used by the render function
|
|
onClick: function onClick(evt, pageNumber) {
|
|
var _this2 = this;
|
|
|
|
// Dont do anything if clicking the current active page
|
|
if (pageNumber === this.currentPage) {
|
|
return;
|
|
}
|
|
|
|
var target = evt.target; // Emit a user-cancelable `page-click` event
|
|
|
|
var clickEvt = new BvEvent('page-click', {
|
|
cancelable: true,
|
|
vueTarget: this,
|
|
target: target
|
|
});
|
|
this.$emit(clickEvt.type, clickEvt, pageNumber);
|
|
|
|
if (clickEvt.defaultPrevented) {
|
|
return;
|
|
} // Update the `v-model`
|
|
|
|
|
|
this.currentPage = pageNumber; // Emit event triggered by user interaction
|
|
|
|
this.$emit('change', this.currentPage); // Keep the current button focused if possible
|
|
|
|
this.$nextTick(function () {
|
|
if (isVisible(target) && _this2.$el.contains(target)) {
|
|
attemptFocus(target);
|
|
} else {
|
|
_this2.focusCurrent();
|
|
}
|
|
});
|
|
},
|
|
makePage: function makePage(pageNum) {
|
|
return pageNum;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
linkProps: function linkProps() {
|
|
// No props, since we render a plain button
|
|
|
|
/* istanbul ignore next */
|
|
return {};
|
|
}
|
|
}
|
|
});
|
|
|
|
var PaginationPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BPagination: BPagination
|
|
}
|
|
});
|
|
|
|
// Sanitize the provided number of pages (converting to a number)
|
|
|
|
var sanitizeNumberOfPages = function sanitizeNumberOfPages(value) {
|
|
return mathMax(toInteger(value, 0), 1);
|
|
}; // --- Props ---
|
|
|
|
var _linkProps = omit(props$1, ['event', 'routerTag']); // --- Main component ---
|
|
// The render function is brought in via the pagination mixin
|
|
// @vue/component
|
|
|
|
|
|
var BPaginationNav = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_PAGINATION_NAV,
|
|
mixins: [paginationMixin],
|
|
props: makePropsConfigurable(_objectSpread2(_objectSpread2(_objectSpread2({}, props$13), _linkProps), {}, {
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
numberOfPages: {
|
|
type: [Number, String],
|
|
default: 1,
|
|
|
|
/* istanbul ignore next */
|
|
validator: function validator(value) {
|
|
var number = toInteger(value, 0);
|
|
|
|
if (number < 1) {
|
|
warn('Prop "number-of-pages" must be a number greater than "0"', NAME_PAGINATION_NAV);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
baseUrl: {
|
|
type: String,
|
|
default: '/'
|
|
},
|
|
useRouter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
linkGen: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
pageGen: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
pages: {
|
|
// Optional array of page links
|
|
type: Array // default: null
|
|
|
|
},
|
|
noPageDetect: {
|
|
// Disable auto page number detection if true
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}), NAME_PAGINATION_NAV),
|
|
computed: {
|
|
// Used by render function to trigger wrapping in '<nav>' element
|
|
isNav: function isNav() {
|
|
return true;
|
|
},
|
|
computedValue: function computedValue() {
|
|
// Returns the value prop as a number or `null` if undefined or < 1
|
|
var value = toInteger(this.value, 0);
|
|
return value < 1 ? null : value;
|
|
}
|
|
},
|
|
watch: {
|
|
numberOfPages: function numberOfPages() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
_this.setNumberOfPages();
|
|
});
|
|
},
|
|
pages: function pages() {
|
|
var _this2 = this;
|
|
|
|
this.$nextTick(function () {
|
|
_this2.setNumberOfPages();
|
|
});
|
|
}
|
|
},
|
|
created: function created() {
|
|
this.setNumberOfPages();
|
|
},
|
|
mounted: function mounted() {
|
|
var _this3 = this;
|
|
|
|
if (this.$router) {
|
|
// We only add the watcher if vue router is detected
|
|
this.$watch('$route', function () {
|
|
_this3.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this3.guessCurrentPage();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
setNumberOfPages: function setNumberOfPages() {
|
|
var _this4 = this;
|
|
|
|
if (isArray(this.pages) && this.pages.length > 0) {
|
|
this.localNumberOfPages = this.pages.length;
|
|
} else {
|
|
this.localNumberOfPages = sanitizeNumberOfPages(this.numberOfPages);
|
|
}
|
|
|
|
this.$nextTick(function () {
|
|
_this4.guessCurrentPage();
|
|
});
|
|
},
|
|
onClick: function onClick(evt, pageNumber) {
|
|
var _this5 = this;
|
|
|
|
// Dont do anything if clicking the current active page
|
|
if (pageNumber === this.currentPage) {
|
|
return;
|
|
}
|
|
|
|
var target = evt.currentTarget || evt.target; // Emit a user-cancelable `page-click` event
|
|
|
|
var clickEvt = new BvEvent('page-click', {
|
|
cancelable: true,
|
|
vueTarget: this,
|
|
target: target
|
|
});
|
|
this.$emit(clickEvt.type, clickEvt, pageNumber);
|
|
|
|
if (clickEvt.defaultPrevented) {
|
|
return;
|
|
} // Update the `v-model`
|
|
// Done in in requestAF() to allow browser to complete the
|
|
// native browser click handling of a link
|
|
|
|
|
|
requestAF(function () {
|
|
_this5.currentPage = pageNumber;
|
|
|
|
_this5.$emit('change', pageNumber);
|
|
}); // Emulate native link click page reloading behaviour by blurring the
|
|
// paginator and returning focus to the document
|
|
// Done in a `nextTick()` to ensure rendering complete
|
|
|
|
this.$nextTick(function () {
|
|
attemptBlur(target);
|
|
});
|
|
},
|
|
getPageInfo: function getPageInfo(pageNum) {
|
|
if (!isArray(this.pages) || this.pages.length === 0 || isUndefined(this.pages[pageNum - 1])) {
|
|
var link = "".concat(this.baseUrl).concat(pageNum);
|
|
return {
|
|
link: this.useRouter ? {
|
|
path: link
|
|
} : link,
|
|
text: toString$1(pageNum)
|
|
};
|
|
}
|
|
|
|
var info = this.pages[pageNum - 1];
|
|
|
|
if (isObject(info)) {
|
|
var _link = info.link;
|
|
return {
|
|
// Normalize link for router use
|
|
link: isObject(_link) ? _link : this.useRouter ? {
|
|
path: _link
|
|
} : _link,
|
|
// Make sure text has a value
|
|
text: toString$1(info.text || pageNum)
|
|
};
|
|
} else {
|
|
return {
|
|
link: toString$1(info),
|
|
text: toString$1(pageNum)
|
|
};
|
|
}
|
|
},
|
|
makePage: function makePage(pageNum) {
|
|
var pageGen = this.pageGen;
|
|
var info = this.getPageInfo(pageNum);
|
|
|
|
if (pageGen && isFunction(pageGen)) {
|
|
var result = pageGen(pageNum, info);
|
|
|
|
if (!isUndefined(result)) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return info.text;
|
|
},
|
|
makeLink: function makeLink(pageNum) {
|
|
var linkGen = this.linkGen;
|
|
var info = this.getPageInfo(pageNum);
|
|
|
|
if (linkGen && isFunction(linkGen)) {
|
|
var result = linkGen(pageNum, info);
|
|
|
|
if (!isUndefined(result)) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return info.link;
|
|
},
|
|
linkProps: function linkProps(pageNum) {
|
|
var props = pluckProps(_linkProps, this);
|
|
var link = this.makeLink(pageNum);
|
|
|
|
if (this.useRouter || isObject(link)) {
|
|
props.to = link;
|
|
} else {
|
|
props.href = link;
|
|
}
|
|
|
|
return props;
|
|
},
|
|
resolveLink: function resolveLink() {
|
|
var to = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
// Given a to (or href string), convert to normalized route-like structure
|
|
// Works only client side!!
|
|
var link;
|
|
|
|
try {
|
|
// Convert the `to` to a HREF via a temporary `a` tag
|
|
link = document.createElement('a');
|
|
link.href = computeHref({
|
|
to: to
|
|
}, 'a', '/', '/'); // We need to add the anchor to the document to make sure the
|
|
// `pathname` is correctly detected in any browser (i.e. IE)
|
|
|
|
document.body.appendChild(link); // Once href is assigned, the link will be normalized to the full URL bits
|
|
|
|
var _link2 = link,
|
|
pathname = _link2.pathname,
|
|
hash = _link2.hash,
|
|
search = _link2.search; // Remove link from document
|
|
|
|
document.body.removeChild(link); // Return the location in a route-like object
|
|
|
|
return {
|
|
path: pathname,
|
|
hash: hash,
|
|
query: parseQuery(search)
|
|
};
|
|
} catch (e) {
|
|
/* istanbul ignore next */
|
|
try {
|
|
link && link.parentNode && link.parentNode.removeChild(link);
|
|
} catch (_unused) {}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
return {};
|
|
}
|
|
},
|
|
resolveRoute: function resolveRoute() {
|
|
var to = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
|
|
// Given a to (or href string), convert to normalized route location structure
|
|
// works only when router available!!
|
|
try {
|
|
var route = this.$router.resolve(to, this.$route).route;
|
|
return {
|
|
path: route.path,
|
|
hash: route.hash,
|
|
query: route.query
|
|
};
|
|
} catch (e) {
|
|
/* istanbul ignore next */
|
|
return {};
|
|
}
|
|
},
|
|
guessCurrentPage: function guessCurrentPage() {
|
|
var guess = this.computedValue;
|
|
var $router = this.$router;
|
|
var $route = this.$route; // This section only occurs if we are client side, or server-side with $router
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (!this.noPageDetect && !guess && (isBrowser || !isBrowser && $router)) {
|
|
// Current route (if router available)
|
|
var currRoute = $router && $route ? {
|
|
path: $route.path,
|
|
hash: $route.hash,
|
|
query: $route.query
|
|
} : {}; // Current page full HREF (if client side). Can't be done as a computed prop!
|
|
|
|
var loc = isBrowser ? window.location || document.location : null;
|
|
var currLink = loc ? {
|
|
path: loc.pathname,
|
|
hash: loc.hash,
|
|
query: parseQuery(loc.search)
|
|
} :
|
|
/* istanbul ignore next */
|
|
{}; // Loop through the possible pages looking for a match until found
|
|
|
|
for (var page = 1; !guess && page <= this.localNumberOfPages; page++) {
|
|
var to = this.makeLink(page);
|
|
|
|
if ($router && (isObject(to) || this.useRouter)) {
|
|
// Resolve the page via the $router
|
|
guess = looseEqual(this.resolveRoute(to), currRoute) ? page : null;
|
|
} else if (isBrowser) {
|
|
// If no $router available (or !this.useRouter when `to` is a string)
|
|
// we compare using parsed URIs
|
|
guess = looseEqual(this.resolveLink(to), currLink) ? page : null;
|
|
} else {
|
|
// probably SSR, but no $router so we can't guess, so lets break out of
|
|
// the loop early
|
|
|
|
/* istanbul ignore next */
|
|
guess = -1;
|
|
}
|
|
}
|
|
} // We set currentPage to 0 to trigger an $emit('input', null)
|
|
// As the default for this.currentPage is -1 when no value is specified
|
|
// And valid page numbers are greater than 0
|
|
|
|
|
|
this.currentPage = guess > 0 ? guess : 0;
|
|
}
|
|
}
|
|
});
|
|
|
|
var PaginationNavPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BPaginationNav: BPaginationNav
|
|
}
|
|
});
|
|
|
|
// Base on-demand component for tooltip / popover templates
|
|
var AttachmentMap = {
|
|
AUTO: 'auto',
|
|
TOP: 'top',
|
|
RIGHT: 'right',
|
|
BOTTOM: 'bottom',
|
|
LEFT: 'left',
|
|
TOPLEFT: 'top',
|
|
TOPRIGHT: 'top',
|
|
RIGHTTOP: 'right',
|
|
RIGHTBOTTOM: 'right',
|
|
BOTTOMLEFT: 'bottom',
|
|
BOTTOMRIGHT: 'bottom',
|
|
LEFTTOP: 'left',
|
|
LEFTBOTTOM: 'left'
|
|
};
|
|
var OffsetMap = {
|
|
AUTO: 0,
|
|
TOPLEFT: -1,
|
|
TOP: 0,
|
|
TOPRIGHT: +1,
|
|
RIGHTTOP: -1,
|
|
RIGHT: 0,
|
|
RIGHTBOTTOM: +1,
|
|
BOTTOMLEFT: -1,
|
|
BOTTOM: 0,
|
|
BOTTOMRIGHT: +1,
|
|
LEFTTOP: -1,
|
|
LEFT: 0,
|
|
LEFTBOTTOM: +1
|
|
}; // @vue/component
|
|
|
|
var BVPopper = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_POPPER,
|
|
props: {
|
|
target: {
|
|
// Element that the tooltip/popover is positioned relative to
|
|
type: [HTMLElement, SVGElement] // default: null
|
|
|
|
},
|
|
placement: {
|
|
type: String,
|
|
default: 'top'
|
|
},
|
|
fallbackPlacement: {
|
|
type: [String, Array],
|
|
default: 'flip'
|
|
},
|
|
offset: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
boundary: {
|
|
// 'scrollParent', 'viewport', 'window', or Element
|
|
type: [String, HTMLElement],
|
|
default: 'scrollParent'
|
|
},
|
|
boundaryPadding: {
|
|
// Tooltip/popover will try and stay away from
|
|
// boundary edge by this many pixels
|
|
type: Number,
|
|
default: 5
|
|
},
|
|
arrowPadding: {
|
|
// The minimum distance (in `px`) from the edge of the
|
|
// tooltip/popover that the arrow can be positioned
|
|
type: Number,
|
|
default: 6
|
|
}
|
|
},
|
|
data: function data() {
|
|
return {
|
|
// reactive props set by parent
|
|
noFade: false,
|
|
// State related data
|
|
localShow: true,
|
|
attachment: this.getAttachment(this.placement)
|
|
};
|
|
},
|
|
computed: {
|
|
/* istanbul ignore next */
|
|
templateType: function templateType() {
|
|
// Overridden by template component
|
|
return 'unknown';
|
|
},
|
|
popperConfig: function popperConfig() {
|
|
var _this = this;
|
|
|
|
var placement = this.placement;
|
|
return {
|
|
placement: this.getAttachment(placement),
|
|
modifiers: {
|
|
offset: {
|
|
offset: this.getOffset(placement)
|
|
},
|
|
flip: {
|
|
behavior: this.fallbackPlacement
|
|
},
|
|
// `arrow.element` can also be a reference to an HTML Element
|
|
// maybe we should make this a `$ref` in the templates?
|
|
arrow: {
|
|
element: '.arrow'
|
|
},
|
|
preventOverflow: {
|
|
padding: this.boundaryPadding,
|
|
boundariesElement: this.boundary
|
|
}
|
|
},
|
|
onCreate: function onCreate(data) {
|
|
// Handle flipping arrow classes
|
|
if (data.originalPlacement !== data.placement) {
|
|
/* istanbul ignore next: can't test in JSDOM */
|
|
_this.popperPlacementChange(data);
|
|
}
|
|
},
|
|
onUpdate: function onUpdate(data) {
|
|
// Handle flipping arrow classes
|
|
_this.popperPlacementChange(data);
|
|
}
|
|
};
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this2 = this;
|
|
|
|
// Note: We are created on-demand, and should be guaranteed that
|
|
// DOM is rendered/ready by the time the created hook runs
|
|
this.$_popper = null; // Ensure we show as we mount
|
|
|
|
this.localShow = true; // Create popper instance before shown
|
|
|
|
this.$on('show', function (el) {
|
|
_this2.popperCreate(el);
|
|
}); // Self destruct handler
|
|
|
|
var handleDestroy = function handleDestroy() {
|
|
_this2.$nextTick(function () {
|
|
// In a `requestAF()` to release control back to application
|
|
requestAF(function () {
|
|
_this2.$destroy();
|
|
});
|
|
});
|
|
}; // Self destruct if parent destroyed
|
|
|
|
|
|
this.$parent.$once('hook:destroyed', handleDestroy); // Self destruct after hidden
|
|
|
|
this.$once('hidden', handleDestroy);
|
|
},
|
|
beforeMount: function beforeMount() {
|
|
// Ensure that the attachment position is correct before mounting
|
|
// as our propsData is added after `new Template({...})`
|
|
this.attachment = this.getAttachment(this.placement);
|
|
},
|
|
updated: function updated() {
|
|
// Update popper if needed
|
|
// TODO: Should this be a watcher on `this.popperConfig` instead?
|
|
this.updatePopper();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.destroyPopper();
|
|
},
|
|
destroyed: function destroyed() {
|
|
// Make sure template is removed from DOM
|
|
var el = this.$el;
|
|
el && el.parentNode && el.parentNode.removeChild(el);
|
|
},
|
|
methods: {
|
|
// "Public" method to trigger hide template
|
|
hide: function hide() {
|
|
this.localShow = false;
|
|
},
|
|
// Private
|
|
getAttachment: function getAttachment(placement) {
|
|
return AttachmentMap[String(placement).toUpperCase()] || 'auto';
|
|
},
|
|
getOffset: function getOffset(placement) {
|
|
if (!this.offset) {
|
|
// Could set a ref for the arrow element
|
|
var arrow = this.$refs.arrow || select('.arrow', this.$el);
|
|
var arrowOffset = toFloat(getCS(arrow).width, 0) + toFloat(this.arrowPadding, 0);
|
|
|
|
switch (OffsetMap[String(placement).toUpperCase()] || 0) {
|
|
/* istanbul ignore next: can't test in JSDOM */
|
|
case +1:
|
|
/* istanbul ignore next: can't test in JSDOM */
|
|
return "+50%p - ".concat(arrowOffset, "px");
|
|
|
|
/* istanbul ignore next: can't test in JSDOM */
|
|
|
|
case -1:
|
|
/* istanbul ignore next: can't test in JSDOM */
|
|
return "-50%p + ".concat(arrowOffset, "px");
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
return this.offset;
|
|
},
|
|
popperCreate: function popperCreate(el) {
|
|
this.destroyPopper(); // We use `el` rather than `this.$el` just in case the original
|
|
// mountpoint root element type was changed by the template
|
|
|
|
this.$_popper = new Popper(this.target, el, this.popperConfig);
|
|
},
|
|
destroyPopper: function destroyPopper() {
|
|
this.$_popper && this.$_popper.destroy();
|
|
this.$_popper = null;
|
|
},
|
|
updatePopper: function updatePopper() {
|
|
this.$_popper && this.$_popper.scheduleUpdate();
|
|
},
|
|
popperPlacementChange: function popperPlacementChange(data) {
|
|
// Callback used by popper to adjust the arrow placement
|
|
this.attachment = this.getAttachment(data.placement);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
renderTemplate: function renderTemplate(h) {
|
|
// Will be overridden by templates
|
|
return h('div');
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this3 = this;
|
|
|
|
// Note: `show` and 'fade' classes are only appled during transition
|
|
return h(BVTransition, {
|
|
// Transitions as soon as mounted
|
|
props: {
|
|
appear: true,
|
|
noFade: this.noFade
|
|
},
|
|
on: {
|
|
// Events used by parent component/instance
|
|
beforeEnter: function beforeEnter(el) {
|
|
return _this3.$emit('show', el);
|
|
},
|
|
afterEnter: function afterEnter(el) {
|
|
return _this3.$emit('shown', el);
|
|
},
|
|
beforeLeave: function beforeLeave(el) {
|
|
return _this3.$emit('hide', el);
|
|
},
|
|
afterLeave: function afterLeave(el) {
|
|
return _this3.$emit('hidden', el);
|
|
}
|
|
}
|
|
}, [this.localShow ? this.renderTemplate(h) : h()]);
|
|
}
|
|
});
|
|
|
|
var BVTooltipTemplate = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TOOLTIP_TEMPLATE,
|
|
extends: BVPopper,
|
|
mixins: [scopedStyleAttrsMixin],
|
|
props: {
|
|
// Other non-reactive (while open) props are pulled in from BVPopper
|
|
id: {
|
|
type: String // default: null
|
|
|
|
},
|
|
html: {
|
|
// Used only by the directive versions
|
|
type: Boolean // default: false
|
|
|
|
}
|
|
},
|
|
data: function data() {
|
|
// We use data, rather than props to ensure reactivity
|
|
// Parent component will directly set this data
|
|
return {
|
|
title: '',
|
|
content: '',
|
|
variant: null,
|
|
customClass: null,
|
|
interactive: true
|
|
};
|
|
},
|
|
computed: {
|
|
templateType: function templateType() {
|
|
return 'tooltip';
|
|
},
|
|
templateClasses: function templateClasses() {
|
|
var _ref;
|
|
|
|
return [(_ref = {
|
|
// Disables pointer events to hide the tooltip when the user
|
|
// hovers over its content
|
|
noninteractive: !this.interactive
|
|
}, _defineProperty(_ref, "b-".concat(this.templateType, "-").concat(this.variant), this.variant), _defineProperty(_ref, "bs-".concat(this.templateType, "-").concat(this.attachment), this.attachment), _ref), this.customClass];
|
|
},
|
|
templateAttributes: function templateAttributes() {
|
|
return _objectSpread2(_objectSpread2({}, this.$parent.$parent.$attrs), {}, {
|
|
id: this.id,
|
|
role: 'tooltip',
|
|
tabindex: '-1'
|
|
}, this.scopedStyleAttrs);
|
|
},
|
|
templateListeners: function templateListeners() {
|
|
var _this = this;
|
|
|
|
// Used for hover/focus trigger listeners
|
|
return {
|
|
mouseenter
|
|
/* istanbul ignore next */
|
|
: function mouseenter(evt) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
_this.$emit('mouseenter', evt);
|
|
},
|
|
mouseleave
|
|
/* istanbul ignore next */
|
|
: function mouseleave(evt) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
_this.$emit('mouseleave', evt);
|
|
},
|
|
focusin
|
|
/* istanbul ignore next */
|
|
: function focusin(evt) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
_this.$emit('focusin', evt);
|
|
},
|
|
focusout
|
|
/* istanbul ignore next */
|
|
: function focusout(evt) {
|
|
/* istanbul ignore next: difficult to test in JSDOM */
|
|
_this.$emit('focusout', evt);
|
|
}
|
|
};
|
|
}
|
|
},
|
|
methods: {
|
|
renderTemplate: function renderTemplate(h) {
|
|
// Title can be a scoped slot function
|
|
var $title = isFunction(this.title) ? this.title({}) : isUndefinedOrNull(this.title) ?
|
|
/* istanbul ignore next */
|
|
h() : this.title; // Directive versions only
|
|
|
|
var domProps = this.html && !isFunction(this.title) ? {
|
|
innerHTML: this.title
|
|
} : {};
|
|
return h('div', {
|
|
staticClass: 'tooltip b-tooltip',
|
|
class: this.templateClasses,
|
|
attrs: this.templateAttributes,
|
|
on: this.templateListeners
|
|
}, [h('div', {
|
|
ref: 'arrow',
|
|
staticClass: 'arrow'
|
|
}), h('div', {
|
|
staticClass: 'tooltip-inner',
|
|
domProps: domProps
|
|
}, [$title])]);
|
|
}
|
|
}
|
|
});
|
|
|
|
var MODAL_SELECTOR = '.modal-content'; // Modal `$root` hidden event
|
|
|
|
var MODAL_CLOSE_EVENT = 'bv::modal::hidden'; // Sidebar container selector for appending tooltip/popover
|
|
|
|
var SIDEBAR_SELECTOR = '.b-sidebar'; // For finding the container to append to
|
|
|
|
var CONTAINER_SELECTOR = [MODAL_SELECTOR, SIDEBAR_SELECTOR].join(', '); // For dropdown sniffing
|
|
|
|
var DROPDOWN_CLASS = 'dropdown';
|
|
var DROPDOWN_OPEN_SELECTOR = '.dropdown-menu.show'; // Data attribute to temporary store the `title` attribute's value
|
|
|
|
var DATA_TITLE_ATTR = 'data-original-title'; // Data specific to popper and template
|
|
// We don't use props, as we need reactivity (we can't pass reactive props)
|
|
|
|
var templateData = {
|
|
// Text string or Scoped slot function
|
|
title: '',
|
|
// Text string or Scoped slot function
|
|
content: '',
|
|
// String
|
|
variant: null,
|
|
// String, Array, Object
|
|
customClass: null,
|
|
// String or array of Strings (overwritten by BVPopper)
|
|
triggers: '',
|
|
// String (overwritten by BVPopper)
|
|
placement: 'auto',
|
|
// String or array of strings
|
|
fallbackPlacement: 'flip',
|
|
// Element or Component reference (or function that returns element) of
|
|
// the element that will have the trigger events bound, and is also
|
|
// default element for positioning
|
|
target: null,
|
|
// HTML ID, Element or Component reference
|
|
container: null,
|
|
// 'body'
|
|
// Boolean
|
|
noFade: false,
|
|
// 'scrollParent', 'viewport', 'window', Element, or Component reference
|
|
boundary: 'scrollParent',
|
|
// Tooltip/popover will try and stay away from
|
|
// boundary edge by this many pixels (Number)
|
|
boundaryPadding: 5,
|
|
// Arrow offset (Number)
|
|
offset: 0,
|
|
// Hover/focus delay (Number or Object)
|
|
delay: 0,
|
|
// Arrow of Tooltip/popover will try and stay away from
|
|
// the edge of tooltip/popover edge by this many pixels
|
|
arrowPadding: 6,
|
|
// Interactive state (Boolean)
|
|
interactive: true,
|
|
// Disabled state (Boolean)
|
|
disabled: false,
|
|
// ID to use for tooltip/popover
|
|
id: null,
|
|
// Flag used by directives only, for HTML content
|
|
html: false
|
|
}; // @vue/component
|
|
|
|
var BVTooltip = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TOOLTIP_HELPER,
|
|
data: function data() {
|
|
return _objectSpread2(_objectSpread2({}, templateData), {}, {
|
|
// State management data
|
|
activeTrigger: {
|
|
// manual: false,
|
|
hover: false,
|
|
click: false,
|
|
focus: false
|
|
},
|
|
localShow: false
|
|
});
|
|
},
|
|
computed: {
|
|
templateType: function templateType() {
|
|
// Overwritten by BVPopover
|
|
return 'tooltip';
|
|
},
|
|
computedId: function computedId() {
|
|
return this.id || "__bv_".concat(this.templateType, "_").concat(this._uid, "__");
|
|
},
|
|
computedDelay: function computedDelay() {
|
|
// Normalizes delay into object form
|
|
var delay = {
|
|
show: 0,
|
|
hide: 0
|
|
};
|
|
|
|
if (isPlainObject(this.delay)) {
|
|
delay.show = mathMax(toInteger(this.delay.show, 0), 0);
|
|
delay.hide = mathMax(toInteger(this.delay.hide, 0), 0);
|
|
} else if (isNumber(this.delay) || isString(this.delay)) {
|
|
delay.show = delay.hide = mathMax(toInteger(this.delay, 0), 0);
|
|
}
|
|
|
|
return delay;
|
|
},
|
|
computedTriggers: function computedTriggers() {
|
|
// Returns the triggers in sorted array form
|
|
// TODO: Switch this to object form for easier lookup
|
|
return concat(this.triggers).filter(Boolean).join(' ').trim().toLowerCase().split(/\s+/).sort();
|
|
},
|
|
isWithActiveTrigger: function isWithActiveTrigger() {
|
|
for (var trigger in this.activeTrigger) {
|
|
if (this.activeTrigger[trigger]) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
computedTemplateData: function computedTemplateData() {
|
|
return {
|
|
title: this.title,
|
|
content: this.content,
|
|
variant: this.variant,
|
|
customClass: this.customClass,
|
|
noFade: this.noFade,
|
|
interactive: this.interactive
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
computedTriggers: function computedTriggers(newTriggers, oldTriggers) {
|
|
var _this = this;
|
|
|
|
// Triggers have changed, so re-register them
|
|
|
|
/* istanbul ignore next */
|
|
if (!looseEqual(newTriggers, oldTriggers)) {
|
|
this.$nextTick(function () {
|
|
// Disable trigger listeners
|
|
_this.unListen(); // Clear any active triggers that are no longer in the list of triggers
|
|
|
|
|
|
oldTriggers.forEach(function (trigger) {
|
|
if (!arrayIncludes(newTriggers, trigger)) {
|
|
if (_this.activeTrigger[trigger]) {
|
|
_this.activeTrigger[trigger] = false;
|
|
}
|
|
}
|
|
}); // Re-enable the trigger listeners
|
|
|
|
_this.listen();
|
|
});
|
|
}
|
|
},
|
|
computedTemplateData: function computedTemplateData() {
|
|
// If any of the while open reactive "props" change,
|
|
// ensure that the template updates accordingly
|
|
this.handleTemplateUpdate();
|
|
},
|
|
title: function title(newValue, oldValue) {
|
|
// Make sure to hide the tooltip when the title is set empty
|
|
if (newValue !== oldValue && !newValue) {
|
|
this.hide();
|
|
}
|
|
},
|
|
disabled: function disabled(newValue) {
|
|
if (newValue) {
|
|
this.disable();
|
|
} else {
|
|
this.enable();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this2 = this;
|
|
|
|
// Create non-reactive properties
|
|
this.$_tip = null;
|
|
this.$_hoverTimeout = null;
|
|
this.$_hoverState = '';
|
|
this.$_visibleInterval = null;
|
|
this.$_enabled = !this.disabled;
|
|
this.$_noop = noop.bind(this); // Destroy ourselves when the parent is destroyed
|
|
|
|
if (this.$parent) {
|
|
this.$parent.$once('hook:beforeDestroy', function () {
|
|
_this2.$nextTick(function () {
|
|
// In a `requestAF()` to release control back to application
|
|
requestAF(function () {
|
|
_this2.$destroy();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
this.$nextTick(function () {
|
|
var target = _this2.getTarget();
|
|
|
|
if (target && contains(document.body, target)) {
|
|
// Copy the parent's scoped style attribute
|
|
_this2.scopeId = getScopeId(_this2.$parent); // Set up all trigger handlers and listeners
|
|
|
|
_this2.listen();
|
|
} else {
|
|
/* istanbul ignore next */
|
|
warn(isString(_this2.target) ? "Unable to find target element by ID \"#".concat(_this2.target, "\" in document.") : 'The provided target is no valid HTML element.', _this2.templateType);
|
|
}
|
|
});
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
updated: function updated() {
|
|
// Usually called when the slots/data changes
|
|
this.$nextTick(this.handleTemplateUpdate);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
// In a keepalive that has been deactivated, so hide
|
|
// the tooltip/popover if it is showing
|
|
this.forceHide();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
// Remove all handler/listeners
|
|
this.unListen();
|
|
this.setWhileOpenListeners(false); // Clear any timeouts/intervals
|
|
|
|
this.clearHoverTimeout();
|
|
this.clearVisibilityInterval(); // Destroy the template
|
|
|
|
this.destroyTemplate(); // Remove any other private properties created during create
|
|
|
|
this.$_noop = null;
|
|
},
|
|
methods: {
|
|
// --- Methods for creating and destroying the template ---
|
|
getTemplate: function getTemplate() {
|
|
// Overridden by BVPopover
|
|
return BVTooltipTemplate;
|
|
},
|
|
updateData: function updateData() {
|
|
var _this3 = this;
|
|
|
|
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
// Method for updating popper/template data
|
|
// We only update data if it exists, and has not changed
|
|
var titleUpdated = false;
|
|
keys(templateData).forEach(function (prop) {
|
|
if (!isUndefined(data[prop]) && _this3[prop] !== data[prop]) {
|
|
_this3[prop] = data[prop];
|
|
|
|
if (prop === 'title') {
|
|
titleUpdated = true;
|
|
}
|
|
}
|
|
}); // If the title has updated, we may need to handle the `title`
|
|
// attribute on the trigger target
|
|
// We only do this while the template is open
|
|
|
|
if (titleUpdated && this.localShow) {
|
|
this.fixTitle();
|
|
}
|
|
},
|
|
createTemplateAndShow: function createTemplateAndShow() {
|
|
// Creates the template instance and show it
|
|
var container = this.getContainer();
|
|
var Template = this.getTemplate();
|
|
var $tip = this.$_tip = new Template({
|
|
parent: this,
|
|
// The following is not reactive to changes in the props data
|
|
propsData: {
|
|
// These values cannot be changed while template is showing
|
|
id: this.computedId,
|
|
html: this.html,
|
|
placement: this.placement,
|
|
fallbackPlacement: this.fallbackPlacement,
|
|
target: this.getPlacementTarget(),
|
|
boundary: this.getBoundary(),
|
|
// Ensure the following are integers
|
|
offset: toInteger(this.offset, 0),
|
|
arrowPadding: toInteger(this.arrowPadding, 0),
|
|
boundaryPadding: toInteger(this.boundaryPadding, 0)
|
|
}
|
|
}); // We set the initial reactive data (values that can be changed while open)
|
|
|
|
this.handleTemplateUpdate(); // Template transition phase events (handled once only)
|
|
// When the template has mounted, but not visibly shown yet
|
|
|
|
$tip.$once('show', this.onTemplateShow); // When the template has completed showing
|
|
|
|
$tip.$once('shown', this.onTemplateShown); // When the template has started to hide
|
|
|
|
$tip.$once('hide', this.onTemplateHide); // When the template has completed hiding
|
|
|
|
$tip.$once('hidden', this.onTemplateHidden); // When the template gets destroyed for any reason
|
|
|
|
$tip.$once('hook:destroyed', this.destroyTemplate); // Convenience events from template
|
|
// To save us from manually adding/removing DOM
|
|
// listeners to tip element when it is open
|
|
|
|
$tip.$on('focusin', this.handleEvent);
|
|
$tip.$on('focusout', this.handleEvent);
|
|
$tip.$on('mouseenter', this.handleEvent);
|
|
$tip.$on('mouseleave', this.handleEvent); // Mount (which triggers the `show`)
|
|
|
|
$tip.$mount(container.appendChild(document.createElement('div'))); // Template will automatically remove its markup from DOM when hidden
|
|
},
|
|
hideTemplate: function hideTemplate() {
|
|
// Trigger the template to start hiding
|
|
// The template will emit the `hide` event after this and
|
|
// then emit the `hidden` event once it is fully hidden
|
|
// The `hook:destroyed` will also be called (safety measure)
|
|
this.$_tip && this.$_tip.hide(); // Clear out any stragging active triggers
|
|
|
|
this.clearActiveTriggers(); // Reset the hover state
|
|
|
|
this.$_hoverState = '';
|
|
},
|
|
// Destroy the template instance and reset state
|
|
destroyTemplate: function destroyTemplate() {
|
|
this.setWhileOpenListeners(false);
|
|
this.clearHoverTimeout();
|
|
this.$_hoverState = '';
|
|
this.clearActiveTriggers();
|
|
this.localPlacementTarget = null;
|
|
|
|
try {
|
|
this.$_tip.$destroy();
|
|
} catch (_unused) {}
|
|
|
|
this.$_tip = null;
|
|
this.removeAriaDescribedby();
|
|
this.restoreTitle();
|
|
this.localShow = false;
|
|
},
|
|
getTemplateElement: function getTemplateElement() {
|
|
return this.$_tip ? this.$_tip.$el : null;
|
|
},
|
|
handleTemplateUpdate: function handleTemplateUpdate() {
|
|
var _this4 = this;
|
|
|
|
// Update our template title/content "props"
|
|
// So that the template updates accordingly
|
|
var $tip = this.$_tip;
|
|
|
|
if ($tip) {
|
|
var props = ['title', 'content', 'variant', 'customClass', 'noFade', 'interactive']; // Only update the values if they have changed
|
|
|
|
props.forEach(function (prop) {
|
|
if ($tip[prop] !== _this4[prop]) {
|
|
$tip[prop] = _this4[prop];
|
|
}
|
|
});
|
|
}
|
|
},
|
|
// --- Show/Hide handlers ---
|
|
// Show the tooltip
|
|
show: function show() {
|
|
var target = this.getTarget();
|
|
|
|
if (!target || !contains(document.body, target) || !isVisible(target) || this.dropdownOpen() || (isUndefinedOrNull(this.title) || this.title === '') && (isUndefinedOrNull(this.content) || this.content === '')) {
|
|
// If trigger element isn't in the DOM or is not visible, or
|
|
// is on an open dropdown toggle, or has no content, then
|
|
// we exit without showing
|
|
return;
|
|
} // If tip already exists, exit early
|
|
|
|
|
|
if (this.$_tip || this.localShow) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // In the process of showing
|
|
|
|
|
|
this.localShow = true; // Create a cancelable BvEvent
|
|
|
|
var showEvt = this.buildEvent('show', {
|
|
cancelable: true
|
|
});
|
|
this.emitEvent(showEvt); // Don't show if event cancelled
|
|
|
|
/* istanbul ignore if */
|
|
|
|
if (showEvt.defaultPrevented) {
|
|
// Destroy the template (if for some reason it was created)
|
|
this.destroyTemplate();
|
|
return;
|
|
} // Fix the title attribute on target
|
|
|
|
|
|
this.fixTitle(); // Set aria-describedby on target
|
|
|
|
this.addAriaDescribedby(); // Create and show the tooltip
|
|
|
|
this.createTemplateAndShow();
|
|
},
|
|
hide: function hide() {
|
|
var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
// Hide the tooltip
|
|
var tip = this.getTemplateElement();
|
|
/* istanbul ignore if */
|
|
|
|
if (!tip || !this.localShow) {
|
|
this.restoreTitle();
|
|
return;
|
|
} // Emit cancelable BvEvent 'hide'
|
|
// We disable cancelling if `force` is true
|
|
|
|
|
|
var hideEvt = this.buildEvent('hide', {
|
|
cancelable: !force
|
|
});
|
|
this.emitEvent(hideEvt);
|
|
/* istanbul ignore if: ignore for now */
|
|
|
|
if (hideEvt.defaultPrevented) {
|
|
// Don't hide if event cancelled
|
|
return;
|
|
} // Tell the template to hide
|
|
|
|
|
|
this.hideTemplate();
|
|
},
|
|
forceHide: function forceHide() {
|
|
// Forcefully hides/destroys the template, regardless of any active triggers
|
|
var tip = this.getTemplateElement();
|
|
|
|
if (!tip || !this.localShow) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Disable while open listeners/watchers
|
|
// This is also done in the template `hide` evt handler
|
|
|
|
|
|
this.setWhileOpenListeners(false); // Clear any hover enter/leave event
|
|
|
|
this.clearHoverTimeout();
|
|
this.$_hoverState = '';
|
|
this.clearActiveTriggers(); // Disable the fade animation on the template
|
|
|
|
if (this.$_tip) {
|
|
this.$_tip.noFade = true;
|
|
} // Hide the tip (with force = true)
|
|
|
|
|
|
this.hide(true);
|
|
},
|
|
enable: function enable() {
|
|
this.$_enabled = true; // Create a non-cancelable BvEvent
|
|
|
|
this.emitEvent(this.buildEvent('enabled'));
|
|
},
|
|
disable: function disable() {
|
|
this.$_enabled = false; // Create a non-cancelable BvEvent
|
|
|
|
this.emitEvent(this.buildEvent('disabled'));
|
|
},
|
|
// --- Handlers for template events ---
|
|
// When template is inserted into DOM, but not yet shown
|
|
onTemplateShow: function onTemplateShow() {
|
|
// Enable while open listeners/watchers
|
|
this.setWhileOpenListeners(true);
|
|
},
|
|
// When template show transition completes
|
|
onTemplateShown: function onTemplateShown() {
|
|
var prevHoverState = this.$_hoverState;
|
|
this.$_hoverState = '';
|
|
/* istanbul ignore next: occasional Node 10 coverage error */
|
|
|
|
if (prevHoverState === 'out') {
|
|
this.leave(null);
|
|
} // Emit a non-cancelable BvEvent 'shown'
|
|
|
|
|
|
this.emitEvent(this.buildEvent('shown'));
|
|
},
|
|
// When template is starting to hide
|
|
onTemplateHide: function onTemplateHide() {
|
|
// Disable while open listeners/watchers
|
|
this.setWhileOpenListeners(false);
|
|
},
|
|
// When template has completed closing (just before it self destructs)
|
|
onTemplateHidden: function onTemplateHidden() {
|
|
// Destroy the template
|
|
this.destroyTemplate(); // Emit a non-cancelable BvEvent 'shown'
|
|
|
|
this.emitEvent(this.buildEvent('hidden'));
|
|
},
|
|
// --- Utility methods ---
|
|
getTarget: function getTarget() {
|
|
var target = this.target;
|
|
|
|
if (isString(target)) {
|
|
target = getById(target.replace(/^#/, ''));
|
|
} else if (isFunction(target)) {
|
|
target = target();
|
|
} else if (target) {
|
|
target = target.$el || target;
|
|
}
|
|
|
|
return isElement(target) ? target : null;
|
|
},
|
|
getPlacementTarget: function getPlacementTarget() {
|
|
// This is the target that the tooltip will be placed on, which may not
|
|
// necessarily be the same element that has the trigger event listeners
|
|
// For now, this is the same as target
|
|
// TODO:
|
|
// Add in child selector support
|
|
// Add in visibility checks for this element
|
|
// Fallback to target if not found
|
|
return this.getTarget();
|
|
},
|
|
getTargetId: function getTargetId() {
|
|
// Returns the ID of the trigger element
|
|
var target = this.getTarget();
|
|
return target && target.id ? target.id : null;
|
|
},
|
|
getContainer: function getContainer() {
|
|
// Handle case where container may be a component ref
|
|
var container = this.container ? this.container.$el || this.container : false;
|
|
var body = document.body;
|
|
var target = this.getTarget(); // If we are in a modal, we append to the modal, If we
|
|
// are in a sidebar, we append to the sidebar, else append
|
|
// to body, unless a container is specified
|
|
// TODO:
|
|
// Template should periodically check to see if it is in dom
|
|
// And if not, self destruct (if container got v-if'ed out of DOM)
|
|
// Or this could possibly be part of the visibility check
|
|
|
|
return container === false ? closest(CONTAINER_SELECTOR, target) || body :
|
|
/*istanbul ignore next */
|
|
isString(container) ?
|
|
/*istanbul ignore next */
|
|
getById(container.replace(/^#/, '')) || body :
|
|
/*istanbul ignore next */
|
|
body;
|
|
},
|
|
getBoundary: function getBoundary() {
|
|
return this.boundary ? this.boundary.$el || this.boundary : 'scrollParent';
|
|
},
|
|
isInModal: function isInModal() {
|
|
var target = this.getTarget();
|
|
return target && closest(MODAL_SELECTOR, target);
|
|
},
|
|
isDropdown: function isDropdown() {
|
|
// Returns true if trigger is a dropdown
|
|
var target = this.getTarget();
|
|
return target && hasClass(target, DROPDOWN_CLASS);
|
|
},
|
|
dropdownOpen: function dropdownOpen() {
|
|
// Returns true if trigger is a dropdown and the dropdown menu is open
|
|
var target = this.getTarget();
|
|
return this.isDropdown() && target && select(DROPDOWN_OPEN_SELECTOR, target);
|
|
},
|
|
clearHoverTimeout: function clearHoverTimeout() {
|
|
clearTimeout(this.$_hoverTimeout);
|
|
this.$_hoverTimeout = null;
|
|
},
|
|
clearVisibilityInterval: function clearVisibilityInterval() {
|
|
clearInterval(this.$_visibleInterval);
|
|
this.$_visibleInterval = null;
|
|
},
|
|
clearActiveTriggers: function clearActiveTriggers() {
|
|
for (var trigger in this.activeTrigger) {
|
|
this.activeTrigger[trigger] = false;
|
|
}
|
|
},
|
|
addAriaDescribedby: function addAriaDescribedby() {
|
|
// Add aria-describedby on trigger element, without removing any other IDs
|
|
var target = this.getTarget();
|
|
var desc = getAttr(target, 'aria-describedby') || '';
|
|
desc = desc.split(/\s+/).concat(this.computedId).join(' ').trim(); // Update/add aria-described by
|
|
|
|
setAttr(target, 'aria-describedby', desc);
|
|
},
|
|
removeAriaDescribedby: function removeAriaDescribedby() {
|
|
var _this5 = this;
|
|
|
|
// Remove aria-describedby on trigger element, without removing any other IDs
|
|
var target = this.getTarget();
|
|
var desc = getAttr(target, 'aria-describedby') || '';
|
|
desc = desc.split(/\s+/).filter(function (d) {
|
|
return d !== _this5.computedId;
|
|
}).join(' ').trim(); // Update or remove aria-describedby
|
|
|
|
if (desc) {
|
|
/* istanbul ignore next */
|
|
setAttr(target, 'aria-describedby', desc);
|
|
} else {
|
|
removeAttr(target, 'aria-describedby');
|
|
}
|
|
},
|
|
fixTitle: function fixTitle() {
|
|
// If the target has a `title` attribute,
|
|
// remove it and store it on a data attribute
|
|
var target = this.getTarget();
|
|
|
|
if (hasAttr(target, 'title')) {
|
|
// Get `title` attribute value and remove it from target
|
|
var title = getAttr(target, 'title');
|
|
setAttr(target, 'title', ''); // Only set the data attribute when the value is truthy
|
|
|
|
if (title) {
|
|
setAttr(target, DATA_TITLE_ATTR, title);
|
|
}
|
|
}
|
|
},
|
|
restoreTitle: function restoreTitle() {
|
|
// If the target had a `title` attribute,
|
|
// restore it and remove the data attribute
|
|
var target = this.getTarget();
|
|
|
|
if (hasAttr(target, DATA_TITLE_ATTR)) {
|
|
// Get data attribute value and remove it from target
|
|
var title = getAttr(target, DATA_TITLE_ATTR);
|
|
removeAttr(target, DATA_TITLE_ATTR); // Only restore the `title` attribute when the value is truthy
|
|
|
|
if (title) {
|
|
setAttr(target, 'title', title);
|
|
}
|
|
}
|
|
},
|
|
// --- BvEvent helpers ---
|
|
buildEvent: function buildEvent(type) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
// Defaults to a non-cancellable event
|
|
return new BvEvent(type, _objectSpread2({
|
|
cancelable: false,
|
|
target: this.getTarget(),
|
|
relatedTarget: this.getTemplateElement() || null,
|
|
componentId: this.computedId,
|
|
vueTarget: this
|
|
}, options));
|
|
},
|
|
emitEvent: function emitEvent(bvEvt) {
|
|
// Emits a BvEvent on $root and this instance
|
|
var evtName = bvEvt.type;
|
|
var $root = this.$root;
|
|
|
|
if ($root && $root.$emit) {
|
|
// Emit an event on $root
|
|
$root.$emit("bv::".concat(this.templateType, "::").concat(evtName), bvEvt);
|
|
}
|
|
|
|
this.$emit(evtName, bvEvt);
|
|
},
|
|
// --- Event handler setup methods ---
|
|
listen: function listen() {
|
|
var _this6 = this;
|
|
|
|
// Enable trigger event handlers
|
|
var el = this.getTarget();
|
|
|
|
if (!el) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Listen for global show/hide events
|
|
|
|
|
|
this.setRootListener(true); // Set up our listeners on the target trigger element
|
|
|
|
this.computedTriggers.forEach(function (trigger) {
|
|
if (trigger === 'click') {
|
|
eventOn(el, 'click', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
} else if (trigger === 'focus') {
|
|
eventOn(el, 'focusin', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOn(el, 'focusout', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
} else if (trigger === 'blur') {
|
|
// Used to close $tip when element looses focus
|
|
|
|
/* istanbul ignore next */
|
|
eventOn(el, 'focusout', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
} else if (trigger === 'hover') {
|
|
eventOn(el, 'mouseenter', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOn(el, 'mouseleave', _this6.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
unListen: function unListen() {
|
|
var _this7 = this;
|
|
|
|
// Remove trigger event handlers
|
|
var events = ['click', 'focusin', 'focusout', 'mouseenter', 'mouseleave'];
|
|
var target = this.getTarget(); // Stop listening for global show/hide/enable/disable events
|
|
|
|
this.setRootListener(false); // Clear out any active target listeners
|
|
|
|
events.forEach(function (evt) {
|
|
target && eventOff(target, evt, _this7.handleEvent, EVENT_OPTIONS_NO_CAPTURE);
|
|
}, this);
|
|
},
|
|
setRootListener: function setRootListener(on) {
|
|
// Listen for global `bv::{hide|show}::{tooltip|popover}` hide request event
|
|
var $root = this.$root;
|
|
|
|
if ($root) {
|
|
var method = on ? '$on' : '$off';
|
|
var type = this.templateType;
|
|
$root[method]("bv::hide::".concat(type), this.doHide);
|
|
$root[method]("bv::show::".concat(type), this.doShow);
|
|
$root[method]("bv::disable::".concat(type), this.doDisable);
|
|
$root[method]("bv::enable::".concat(type), this.doEnable);
|
|
}
|
|
},
|
|
setWhileOpenListeners: function setWhileOpenListeners(on) {
|
|
// Events that are only registered when the template is showing
|
|
// Modal close events
|
|
this.setModalListener(on); // Dropdown open events (if we are attached to a dropdown)
|
|
|
|
this.setDropdownListener(on); // Periodic $element visibility check
|
|
// For handling when tip target is in <keepalive>, tabs, carousel, etc
|
|
|
|
this.visibleCheck(on); // On-touch start listeners
|
|
|
|
this.setOnTouchStartListener(on);
|
|
},
|
|
// Handler for periodic visibility check
|
|
visibleCheck: function visibleCheck(on) {
|
|
var _this8 = this;
|
|
|
|
this.clearVisibilityInterval();
|
|
var target = this.getTarget();
|
|
var tip = this.getTemplateElement();
|
|
|
|
if (on) {
|
|
this.$_visibleInterval = setInterval(function () {
|
|
if (tip && _this8.localShow && (!target.parentNode || !isVisible(target))) {
|
|
// Target element is no longer visible or not in DOM, so force-hide the tooltip
|
|
_this8.forceHide();
|
|
}
|
|
}, 100);
|
|
}
|
|
},
|
|
setModalListener: function setModalListener(on) {
|
|
// Handle case where tooltip/target is in a modal
|
|
if (this.isInModal()) {
|
|
// We can listen for modal hidden events on `$root`
|
|
this.$root[on ? '$on' : '$off'](MODAL_CLOSE_EVENT, this.forceHide);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support `ontouchstart` */
|
|
setOnTouchStartListener: function setOnTouchStartListener(on) {
|
|
var _this9 = this;
|
|
|
|
// If this is a touch-enabled device we add extra empty
|
|
// `mouseover` listeners to the body's immediate children
|
|
// Only needed because of broken event delegation on iOS
|
|
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
|
if ('ontouchstart' in document.documentElement) {
|
|
from(document.body.children).forEach(function (el) {
|
|
eventOnOff(on, el, 'mouseover', _this9.$_noop);
|
|
});
|
|
}
|
|
},
|
|
setDropdownListener: function setDropdownListener(on) {
|
|
var target = this.getTarget();
|
|
|
|
if (!target || !this.$root || !this.isDropdown) {
|
|
return;
|
|
} // We can listen for dropdown shown events on its instance
|
|
// TODO:
|
|
// We could grab the ID from the dropdown, and listen for
|
|
// $root events for that particular dropdown id
|
|
// Dropdown shown and hidden events will need to emit
|
|
// Note: Dropdown auto-ID happens in a `$nextTick()` after mount
|
|
// So the ID lookup would need to be done in a `$nextTick()`
|
|
|
|
|
|
if (target.__vue__) {
|
|
target.__vue__[on ? '$on' : '$off']('shown', this.forceHide);
|
|
}
|
|
},
|
|
// --- Event handlers ---
|
|
handleEvent: function handleEvent(evt) {
|
|
// General trigger event handler
|
|
// target is the trigger element
|
|
var target = this.getTarget();
|
|
|
|
if (!target || isDisabled(target) || !this.$_enabled || this.dropdownOpen()) {
|
|
// If disabled or not enabled, or if a dropdown that is open, don't do anything
|
|
// If tip is shown before element gets disabled, then tip will not
|
|
// close until no longer disabled or forcefully closed
|
|
return;
|
|
}
|
|
|
|
var type = evt.type;
|
|
var triggers = this.computedTriggers;
|
|
|
|
if (type === 'click' && arrayIncludes(triggers, 'click')) {
|
|
this.click(evt);
|
|
} else if (type === 'mouseenter' && arrayIncludes(triggers, 'hover')) {
|
|
// `mouseenter` is a non-bubbling event
|
|
this.enter(evt);
|
|
} else if (type === 'focusin' && arrayIncludes(triggers, 'focus')) {
|
|
// `focusin` is a bubbling event
|
|
// `evt` includes `relatedTarget` (element losing focus)
|
|
this.enter(evt);
|
|
} else if (type === 'focusout' && (arrayIncludes(triggers, 'focus') || arrayIncludes(triggers, 'blur')) || type === 'mouseleave' && arrayIncludes(triggers, 'hover')) {
|
|
// `focusout` is a bubbling event
|
|
// `mouseleave` is a non-bubbling event
|
|
// `tip` is the template (will be null if not open)
|
|
var tip = this.getTemplateElement(); // `evtTarget` is the element which is losing focus/hover and
|
|
|
|
var evtTarget = evt.target; // `relatedTarget` is the element gaining focus/hover
|
|
|
|
var relatedTarget = evt.relatedTarget;
|
|
/* istanbul ignore next */
|
|
|
|
if ( // From tip to target
|
|
tip && contains(tip, evtTarget) && contains(target, relatedTarget) || // From target to tip
|
|
tip && contains(target, evtTarget) && contains(tip, relatedTarget) || // Within tip
|
|
tip && contains(tip, evtTarget) && contains(tip, relatedTarget) || // Within target
|
|
contains(target, evtTarget) && contains(target, relatedTarget)) {
|
|
// If focus/hover moves within `tip` and `target`, don't trigger a leave
|
|
return;
|
|
} // Otherwise trigger a leave
|
|
|
|
|
|
this.leave(evt);
|
|
}
|
|
},
|
|
doHide: function doHide(id) {
|
|
// Programmatically hide tooltip or popover
|
|
if (!id || this.getTargetId() === id || this.computedId === id) {
|
|
// Close all tooltips or popovers, or this specific tip (with ID)
|
|
this.forceHide();
|
|
}
|
|
},
|
|
doShow: function doShow(id) {
|
|
// Programmatically show tooltip or popover
|
|
if (!id || this.getTargetId() === id || this.computedId === id) {
|
|
// Open all tooltips or popovers, or this specific tip (with ID)
|
|
this.show();
|
|
}
|
|
},
|
|
|
|
/*istanbul ignore next: ignore for now */
|
|
doDisable: function doDisable(id)
|
|
/*istanbul ignore next: ignore for now */
|
|
{
|
|
// Programmatically disable tooltip or popover
|
|
if (!id || this.getTargetId() === id || this.computedId === id) {
|
|
// Disable all tooltips or popovers (no ID), or this specific tip (with ID)
|
|
this.disable();
|
|
}
|
|
},
|
|
|
|
/*istanbul ignore next: ignore for now */
|
|
doEnable: function doEnable(id)
|
|
/*istanbul ignore next: ignore for now */
|
|
{
|
|
// Programmatically enable tooltip or popover
|
|
if (!id || this.getTargetId() === id || this.computedId === id) {
|
|
// Enable all tooltips or popovers (no ID), or this specific tip (with ID)
|
|
this.enable();
|
|
}
|
|
},
|
|
click: function click(evt) {
|
|
if (!this.$_enabled || this.dropdownOpen()) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Get around a WebKit bug where `click` does not trigger focus events
|
|
// On most browsers, `click` triggers a `focusin`/`focus` event first
|
|
// Needed so that trigger 'click blur' works on iOS
|
|
// https://github.com/bootstrap-vue/bootstrap-vue/issues/5099
|
|
// We use `currentTarget` rather than `target` to trigger on the
|
|
// element, not the inner content
|
|
|
|
|
|
attemptFocus(evt.currentTarget);
|
|
this.activeTrigger.click = !this.activeTrigger.click;
|
|
|
|
if (this.isWithActiveTrigger) {
|
|
this.enter(null);
|
|
} else {
|
|
/* istanbul ignore next */
|
|
this.leave(null);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
toggle: function toggle() {
|
|
// Manual toggle handler
|
|
if (!this.$_enabled || this.dropdownOpen()) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Should we register as an active trigger?
|
|
// this.activeTrigger.manual = !this.activeTrigger.manual
|
|
|
|
|
|
if (this.localShow) {
|
|
this.leave(null);
|
|
} else {
|
|
this.enter(null);
|
|
}
|
|
},
|
|
enter: function enter() {
|
|
var _this10 = this;
|
|
|
|
var evt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
|
|
// Opening trigger handler
|
|
// Note: Click events are sent with evt === null
|
|
if (evt) {
|
|
this.activeTrigger[evt.type === 'focusin' ? 'focus' : 'hover'] = true;
|
|
}
|
|
/* istanbul ignore next */
|
|
|
|
|
|
if (this.localShow || this.$_hoverState === 'in') {
|
|
this.$_hoverState = 'in';
|
|
return;
|
|
}
|
|
|
|
this.clearHoverTimeout();
|
|
this.$_hoverState = 'in';
|
|
|
|
if (!this.computedDelay.show) {
|
|
this.show();
|
|
} else {
|
|
// Hide any title attribute while enter delay is active
|
|
this.fixTitle();
|
|
this.$_hoverTimeout = setTimeout(function () {
|
|
/* istanbul ignore else */
|
|
if (_this10.$_hoverState === 'in') {
|
|
_this10.show();
|
|
} else if (!_this10.localShow) {
|
|
_this10.restoreTitle();
|
|
}
|
|
}, this.computedDelay.show);
|
|
}
|
|
},
|
|
leave: function leave() {
|
|
var _this11 = this;
|
|
|
|
var evt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
|
|
// Closing trigger handler
|
|
// Note: Click events are sent with evt === null
|
|
if (evt) {
|
|
this.activeTrigger[evt.type === 'focusout' ? 'focus' : 'hover'] = false;
|
|
/* istanbul ignore next */
|
|
|
|
if (evt.type === 'focusout' && arrayIncludes(this.computedTriggers, 'blur')) {
|
|
// Special case for `blur`: we clear out the other triggers
|
|
this.activeTrigger.click = false;
|
|
this.activeTrigger.hover = false;
|
|
}
|
|
}
|
|
/* istanbul ignore next: ignore for now */
|
|
|
|
|
|
if (this.isWithActiveTrigger) {
|
|
return;
|
|
}
|
|
|
|
this.clearHoverTimeout();
|
|
this.$_hoverState = 'out';
|
|
|
|
if (!this.computedDelay.hide) {
|
|
this.hide();
|
|
} else {
|
|
this.$_hoverTimeout = setTimeout(function () {
|
|
if (_this11.$_hoverState === 'out') {
|
|
_this11.hide();
|
|
}
|
|
}, this.computedDelay.hide);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var BTooltip = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TOOLTIP,
|
|
inheritAttrs: false,
|
|
props: makePropsConfigurable({
|
|
title: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
// Added in by BPopover
|
|
// content: {
|
|
// type: String,
|
|
// default: undefined
|
|
// },
|
|
target: {
|
|
// String ID of element, or element/component reference
|
|
// Or function that returns one of the above
|
|
type: [String, HTMLElement, SVGElement, Function, Object],
|
|
required: true
|
|
},
|
|
triggers: {
|
|
type: [String, Array],
|
|
default: 'hover focus'
|
|
},
|
|
placement: {
|
|
type: String,
|
|
default: 'top'
|
|
},
|
|
fallbackPlacement: {
|
|
type: [String, Array],
|
|
default: 'flip',
|
|
validator: function validator(value) {
|
|
return isArray(value) && value.every(function (v) {
|
|
return isString(v);
|
|
}) || arrayIncludes(['flip', 'clockwise', 'counterclockwise'], value);
|
|
}
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
customClass: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
delay: {
|
|
type: [Number, Object, String],
|
|
default: 50
|
|
},
|
|
boundary: {
|
|
// String: scrollParent, window, or viewport
|
|
// Element: element reference
|
|
// Object: Vue component
|
|
type: [String, HTMLElement, Object],
|
|
default: 'scrollParent'
|
|
},
|
|
boundaryPadding: {
|
|
type: [Number, String],
|
|
default: 5
|
|
},
|
|
offset: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
noFade: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
container: {
|
|
// String: HTML ID of container, if null body is used (default)
|
|
// HTMLElement: element reference reference
|
|
// Object: Vue Component
|
|
type: [String, HTMLElement, Object] // default: undefined
|
|
|
|
},
|
|
show: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noninteractive: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
id: {
|
|
// ID to use for tooltip element
|
|
// If not provided on will automatically be generated
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_TOOLTIP),
|
|
data: function data() {
|
|
return {
|
|
localShow: this.show,
|
|
localTitle: '',
|
|
localContent: ''
|
|
};
|
|
},
|
|
computed: {
|
|
templateData: function templateData() {
|
|
// Data that will be passed to the template and popper
|
|
return {
|
|
// We use massaged versions of the title and content props/slots
|
|
title: this.localTitle,
|
|
content: this.localContent,
|
|
// Pass these props as is
|
|
target: this.target,
|
|
triggers: this.triggers,
|
|
placement: this.placement,
|
|
fallbackPlacement: this.fallbackPlacement,
|
|
variant: this.variant,
|
|
customClass: this.customClass,
|
|
container: this.container,
|
|
boundary: this.boundary,
|
|
boundaryPadding: this.boundaryPadding,
|
|
delay: this.delay,
|
|
offset: this.offset,
|
|
noFade: this.noFade,
|
|
interactive: !this.noninteractive,
|
|
disabled: this.disabled,
|
|
id: this.id
|
|
};
|
|
},
|
|
templateTitleContent: function templateTitleContent() {
|
|
// Used to watch for changes to the title and content props
|
|
return {
|
|
title: this.title,
|
|
content: this.content
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
show: function show(_show, oldVal) {
|
|
if (_show !== oldVal && _show !== this.localShow && this.$_toolpop) {
|
|
if (_show) {
|
|
this.$_toolpop.show();
|
|
} else {
|
|
// We use `forceHide()` to override any active triggers
|
|
this.$_toolpop.forceHide();
|
|
}
|
|
}
|
|
},
|
|
disabled: function disabled(newVal) {
|
|
if (newVal) {
|
|
this.doDisable();
|
|
} else {
|
|
this.doEnable();
|
|
}
|
|
},
|
|
localShow: function localShow(newVal) {
|
|
// TODO: May need to be done in a `$nextTick()`
|
|
this.$emit('update:show', newVal);
|
|
},
|
|
templateData: function templateData() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
if (_this.$_toolpop) {
|
|
_this.$_toolpop.updateData(_this.templateData);
|
|
}
|
|
});
|
|
},
|
|
// Watchers for title/content props (prop changes do not trigger the `updated()` hook)
|
|
templateTitleContent: function templateTitleContent() {
|
|
this.$nextTick(this.updateContent);
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_toolpop = null;
|
|
},
|
|
updated: function updated() {
|
|
// Update the `propData` object
|
|
// Done in a `$nextTick()` to ensure slot(s) have updated
|
|
this.$nextTick(this.updateContent);
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
// Shutdown our local event listeners
|
|
this.$off('open', this.doOpen);
|
|
this.$off('close', this.doClose);
|
|
this.$off('disable', this.doDisable);
|
|
this.$off('enable', this.doEnable); // Destroy the tip instance
|
|
|
|
if (this.$_toolpop) {
|
|
this.$_toolpop.$destroy();
|
|
this.$_toolpop = null;
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this2 = this;
|
|
|
|
// Instantiate a new BVTooltip instance
|
|
// Done in a `$nextTick()` to ensure DOM has completed rendering
|
|
// so that target can be found
|
|
this.$nextTick(function () {
|
|
// Load the on demand child instance
|
|
var Component = _this2.getComponent(); // Ensure we have initial content
|
|
|
|
|
|
_this2.updateContent(); // Pass down the scoped style attribute if available
|
|
|
|
|
|
var scopeId = getScopeId(_this2) || getScopeId(_this2.$parent); // Create the instance
|
|
|
|
var $toolpop = _this2.$_toolpop = new Component({
|
|
parent: _this2,
|
|
// Pass down the scoped style ID
|
|
_scopeId: scopeId || undefined
|
|
}); // Set the initial data
|
|
|
|
$toolpop.updateData(_this2.templateData); // Set listeners
|
|
|
|
$toolpop.$on('show', _this2.onShow);
|
|
$toolpop.$on('shown', _this2.onShown);
|
|
$toolpop.$on('hide', _this2.onHide);
|
|
$toolpop.$on('hidden', _this2.onHidden);
|
|
$toolpop.$on('disabled', _this2.onDisabled);
|
|
$toolpop.$on('enabled', _this2.onEnabled); // Initially disabled?
|
|
|
|
if (_this2.disabled) {
|
|
// Initially disabled
|
|
_this2.doDisable();
|
|
} // Listen to open signals from others
|
|
|
|
|
|
_this2.$on('open', _this2.doOpen); // Listen to close signals from others
|
|
|
|
|
|
_this2.$on('close', _this2.doClose); // Listen to disable signals from others
|
|
|
|
|
|
_this2.$on('disable', _this2.doDisable); // Listen to enable signals from others
|
|
|
|
|
|
_this2.$on('enable', _this2.doEnable); // Initially show tooltip?
|
|
|
|
|
|
if (_this2.localShow) {
|
|
$toolpop.show();
|
|
}
|
|
});
|
|
},
|
|
methods: {
|
|
getComponent: function getComponent() {
|
|
// Overridden by BPopover
|
|
return BVTooltip;
|
|
},
|
|
updateContent: function updateContent() {
|
|
// Overridden by BPopover
|
|
// Tooltip: Default slot is `title`
|
|
// Popover: Default slot is `content`, `title` slot is title
|
|
// We pass a scoped slot function reference by default (Vue v2.6x)
|
|
// And pass the title prop as a fallback
|
|
this.setTitle(this.$scopedSlots.default || this.title);
|
|
},
|
|
// Helper methods for `updateContent()`
|
|
setTitle: function setTitle(val) {
|
|
val = isUndefinedOrNull(val) ? '' : val; // We only update the value if it has changed
|
|
|
|
if (this.localTitle !== val) {
|
|
this.localTitle = val;
|
|
}
|
|
},
|
|
setContent: function setContent(val) {
|
|
val = isUndefinedOrNull(val) ? '' : val; // We only update the value if it has changed
|
|
|
|
if (this.localContent !== val) {
|
|
this.localContent = val;
|
|
}
|
|
},
|
|
// --- Template event handlers ---
|
|
onShow: function onShow(bvEvt) {
|
|
// Placeholder
|
|
this.$emit('show', bvEvt);
|
|
|
|
if (bvEvt) {
|
|
this.localShow = !bvEvt.defaultPrevented;
|
|
}
|
|
},
|
|
onShown: function onShown(bvEvt) {
|
|
// Tip is now showing
|
|
this.localShow = true;
|
|
this.$emit('shown', bvEvt);
|
|
},
|
|
onHide: function onHide(bvEvt) {
|
|
this.$emit('hide', bvEvt);
|
|
},
|
|
onHidden: function onHidden(bvEvt) {
|
|
// Tip is no longer showing
|
|
this.$emit('hidden', bvEvt);
|
|
this.localShow = false;
|
|
},
|
|
onDisabled: function onDisabled(bvEvt) {
|
|
// Prevent possible endless loop if user mistakenly
|
|
// fires `disabled` instead of `disable`
|
|
if (bvEvt && bvEvt.type === 'disabled') {
|
|
this.$emit('update:disabled', true);
|
|
this.$emit('disabled', bvEvt);
|
|
}
|
|
},
|
|
onEnabled: function onEnabled(bvEvt) {
|
|
// Prevent possible endless loop if user mistakenly
|
|
// fires `enabled` instead of `enable`
|
|
if (bvEvt && bvEvt.type === 'enabled') {
|
|
this.$emit('update:disabled', false);
|
|
this.$emit('enabled', bvEvt);
|
|
}
|
|
},
|
|
// --- Local event listeners ---
|
|
doOpen: function doOpen() {
|
|
!this.localShow && this.$_toolpop && this.$_toolpop.show();
|
|
},
|
|
doClose: function doClose() {
|
|
this.localShow && this.$_toolpop && this.$_toolpop.hide();
|
|
},
|
|
doDisable: function doDisable() {
|
|
this.$_toolpop && this.$_toolpop.disable();
|
|
},
|
|
doEnable: function doEnable() {
|
|
this.$_toolpop && this.$_toolpop.enable();
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
// Always renders a comment node
|
|
// TODO:
|
|
// Future: Possibly render a target slot (single root element)
|
|
// which we can apply the listeners to (pass `this.$el` to BVTooltip)
|
|
return h();
|
|
}
|
|
});
|
|
|
|
var BVPopoverTemplate = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_POPOVER_TEMPLATE,
|
|
extends: BVTooltipTemplate,
|
|
computed: {
|
|
templateType: function templateType() {
|
|
return 'popover';
|
|
}
|
|
},
|
|
methods: {
|
|
renderTemplate: function renderTemplate(h) {
|
|
// Title and content could be a scoped slot function
|
|
var $title = isFunction(this.title) ? this.title({}) : this.title;
|
|
var $content = isFunction(this.content) ? this.content({}) : this.content; // Directive usage only
|
|
|
|
var titleDomProps = this.html && !isFunction(this.title) ? {
|
|
innerHTML: this.title
|
|
} : {};
|
|
var contentDomProps = this.html && !isFunction(this.content) ? {
|
|
innerHTML: this.content
|
|
} : {};
|
|
return h('div', {
|
|
staticClass: 'popover b-popover',
|
|
class: this.templateClasses,
|
|
attrs: this.templateAttributes,
|
|
on: this.templateListeners
|
|
}, [h('div', {
|
|
ref: 'arrow',
|
|
staticClass: 'arrow'
|
|
}), isUndefinedOrNull($title) || $title === '' ?
|
|
/* istanbul ignore next */
|
|
h() : h('h3', {
|
|
staticClass: 'popover-header',
|
|
domProps: titleDomProps
|
|
}, [$title]), isUndefinedOrNull($content) || $content === '' ?
|
|
/* istanbul ignore next */
|
|
h() : h('div', {
|
|
staticClass: 'popover-body',
|
|
domProps: contentDomProps
|
|
}, [$content])]);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Popover "Class" (Built as a renderless Vue instance)
|
|
|
|
var BVPopover = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_POPOVER_HELPER,
|
|
extends: BVTooltip,
|
|
computed: {
|
|
// Overwrites BVTooltip
|
|
templateType: function templateType() {
|
|
return 'popover';
|
|
}
|
|
},
|
|
methods: {
|
|
getTemplate: function getTemplate() {
|
|
// Overwrites BVTooltip
|
|
return BVPopoverTemplate;
|
|
}
|
|
}
|
|
});
|
|
|
|
var BPopover = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_POPOVER,
|
|
extends: BTooltip,
|
|
inheritAttrs: false,
|
|
props: makePropsConfigurable({
|
|
title: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
content: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
triggers: {
|
|
type: [String, Array],
|
|
default: 'click'
|
|
},
|
|
placement: {
|
|
type: String,
|
|
default: 'right'
|
|
},
|
|
variant: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
customClass: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
delay: {
|
|
type: [Number, Object, String],
|
|
default: 50
|
|
},
|
|
boundary: {
|
|
// String: scrollParent, window, or viewport
|
|
// Element: element reference
|
|
// Object: Vue component
|
|
type: [String, HTMLElement, Object],
|
|
default: 'scrollParent'
|
|
},
|
|
boundaryPadding: {
|
|
type: [Number, String],
|
|
default: 5
|
|
}
|
|
}, NAME_POPOVER),
|
|
methods: {
|
|
getComponent: function getComponent() {
|
|
// Overridden by BPopover
|
|
return BVPopover;
|
|
},
|
|
updateContent: function updateContent() {
|
|
// Tooltip: Default slot is `title`
|
|
// Popover: Default slot is `content`, `title` slot is title
|
|
// We pass a scoped slot function references by default (Vue v2.6x)
|
|
// And pass the title prop as a fallback
|
|
this.setContent(this.$scopedSlots.default || this.content);
|
|
this.setTitle(this.$scopedSlots.title || this.title);
|
|
}
|
|
} // Render function provided by BTooltip
|
|
|
|
});
|
|
|
|
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 = _objectSpread2(_objectSpread2({}, 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: getScopeId($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
|
|
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
var VBPopoverPlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBPopover: VBPopover
|
|
}
|
|
});
|
|
|
|
var PopoverPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BPopover: BPopover
|
|
},
|
|
plugins: {
|
|
VBPopoverPlugin: VBPopoverPlugin
|
|
}
|
|
});
|
|
|
|
var BProgressBar = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_PROGRESS_BAR,
|
|
mixins: [normalizeSlotMixin],
|
|
inject: {
|
|
bvProgress: {
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
props: makePropsConfigurable({
|
|
value: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
label: {
|
|
type: String // default: null
|
|
|
|
},
|
|
labelHtml: {
|
|
type: String
|
|
},
|
|
// $parent (this.bvProgress) prop values may take precedence over the following props
|
|
// Which is why they are defaulted to null
|
|
max: {
|
|
type: [Number, String],
|
|
default: null
|
|
},
|
|
precision: {
|
|
type: [Number, String],
|
|
default: null
|
|
},
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
striped: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
animated: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
showProgress: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
showValue: {
|
|
type: Boolean,
|
|
default: null
|
|
}
|
|
}, NAME_PROGRESS_BAR),
|
|
computed: {
|
|
progressBarClasses: function progressBarClasses() {
|
|
return [this.computedVariant ? "bg-".concat(this.computedVariant) : '', this.computedStriped || this.computedAnimated ? 'progress-bar-striped' : '', this.computedAnimated ? 'progress-bar-animated' : ''];
|
|
},
|
|
progressBarStyles: function progressBarStyles() {
|
|
return {
|
|
width: 100 * (this.computedValue / this.computedMax) + '%'
|
|
};
|
|
},
|
|
computedValue: function computedValue() {
|
|
return toFloat(this.value, 0);
|
|
},
|
|
computedMax: function computedMax() {
|
|
// Prefer our max over parent setting
|
|
// Default to `100` for invalid values (`-x`, `0`, `NaN`)
|
|
var max = toFloat(this.max) || toFloat(this.bvProgress.max, 0);
|
|
return max > 0 ? max : 100;
|
|
},
|
|
computedPrecision: function computedPrecision() {
|
|
// Prefer our precision over parent setting
|
|
// Default to `0` for invalid values (`-x`, `NaN`)
|
|
return mathMax(toInteger(this.precision, toInteger(this.bvProgress.precision, 0)), 0);
|
|
},
|
|
computedProgress: function computedProgress() {
|
|
var precision = this.computedPrecision;
|
|
var p = mathPow(10, precision);
|
|
return toFixed(100 * p * this.computedValue / this.computedMax / p, precision);
|
|
},
|
|
computedVariant: function computedVariant() {
|
|
// Prefer our variant over parent setting
|
|
return this.variant || this.bvProgress.variant;
|
|
},
|
|
computedStriped: function computedStriped() {
|
|
// Prefer our striped over parent setting
|
|
return isBoolean(this.striped) ? this.striped : this.bvProgress.striped || false;
|
|
},
|
|
computedAnimated: function computedAnimated() {
|
|
// Prefer our animated over parent setting
|
|
return isBoolean(this.animated) ? this.animated : this.bvProgress.animated || false;
|
|
},
|
|
computedShowProgress: function computedShowProgress() {
|
|
// Prefer our showProgress over parent setting
|
|
return isBoolean(this.showProgress) ? this.showProgress : this.bvProgress.showProgress || false;
|
|
},
|
|
computedShowValue: function computedShowValue() {
|
|
// Prefer our showValue over parent setting
|
|
return isBoolean(this.showValue) ? this.showValue : this.bvProgress.showValue || false;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var label = this.label,
|
|
labelHtml = this.labelHtml,
|
|
computedValue = this.computedValue,
|
|
computedPrecision = this.computedPrecision;
|
|
var $children;
|
|
var domProps = {};
|
|
|
|
if (this.hasNormalizedSlot()) {
|
|
$children = this.normalizeSlot();
|
|
} else if (label || labelHtml) {
|
|
domProps = htmlOrText(labelHtml, label);
|
|
} else if (this.computedShowProgress) {
|
|
$children = this.computedProgress;
|
|
} else if (this.computedShowValue) {
|
|
$children = toFixed(computedValue, computedPrecision);
|
|
}
|
|
|
|
return h('div', {
|
|
staticClass: 'progress-bar',
|
|
class: this.progressBarClasses,
|
|
style: this.progressBarStyles,
|
|
attrs: {
|
|
role: 'progressbar',
|
|
'aria-valuemin': '0',
|
|
'aria-valuemax': toString$1(this.computedMax),
|
|
'aria-valuenow': toFixed(computedValue, computedPrecision)
|
|
},
|
|
domProps: domProps
|
|
}, $children);
|
|
}
|
|
});
|
|
|
|
var BProgress = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_PROGRESS,
|
|
mixins: [normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvProgress: this
|
|
};
|
|
},
|
|
props: makePropsConfigurable({
|
|
// These props can be inherited via the child b-progress-bar(s)
|
|
variant: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
striped: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
animated: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
height: {
|
|
type: String // default: null
|
|
|
|
},
|
|
precision: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
showProgress: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showValue: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
max: {
|
|
type: [Number, String],
|
|
default: 100
|
|
},
|
|
// This prop is not inherited by child b-progress-bar(s)
|
|
value: {
|
|
type: [Number, String],
|
|
default: 0
|
|
}
|
|
}, NAME_PROGRESS),
|
|
computed: {
|
|
progressHeight: function progressHeight() {
|
|
return {
|
|
height: this.height || null
|
|
};
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var childNodes = this.normalizeSlot();
|
|
|
|
if (!childNodes) {
|
|
childNodes = h(BProgressBar, {
|
|
props: {
|
|
value: this.value,
|
|
max: this.max,
|
|
precision: this.precision,
|
|
variant: this.variant,
|
|
animated: this.animated,
|
|
striped: this.striped,
|
|
showProgress: this.showProgress,
|
|
showValue: this.showValue
|
|
}
|
|
});
|
|
}
|
|
|
|
return h('div', {
|
|
class: ['progress'],
|
|
style: this.progressHeight
|
|
}, [childNodes]);
|
|
}
|
|
});
|
|
|
|
var ProgressPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BProgress: BProgress,
|
|
BProgressBar: BProgressBar
|
|
}
|
|
});
|
|
|
|
var CLASS_NAME$3 = 'b-sidebar'; // --- Render methods ---
|
|
|
|
var renderHeaderTitle = function renderHeaderTitle(h, ctx) {
|
|
// Render a empty `<span>` when to title was provided
|
|
var title = ctx.computedTile;
|
|
|
|
if (!title) {
|
|
return h('span');
|
|
}
|
|
|
|
return h('strong', {
|
|
attrs: {
|
|
id: ctx.safeId('__title__')
|
|
}
|
|
}, [title]);
|
|
};
|
|
|
|
var renderHeaderClose = function renderHeaderClose(h, ctx) {
|
|
if (ctx.noHeaderClose) {
|
|
return h();
|
|
}
|
|
|
|
var closeLabel = ctx.closeLabel,
|
|
textVariant = ctx.textVariant,
|
|
hide = ctx.hide;
|
|
return h(BButtonClose, {
|
|
ref: 'close-button',
|
|
props: {
|
|
ariaLabel: closeLabel,
|
|
textVariant: textVariant
|
|
},
|
|
on: {
|
|
click: hide
|
|
}
|
|
}, [ctx.normalizeSlot('header-close') || h(BIconX)]);
|
|
};
|
|
|
|
var renderHeader = function renderHeader(h, ctx) {
|
|
if (ctx.noHeader) {
|
|
return h();
|
|
}
|
|
|
|
var $title = renderHeaderTitle(h, ctx);
|
|
var $close = renderHeaderClose(h, ctx);
|
|
return h('header', {
|
|
key: 'header',
|
|
staticClass: "".concat(CLASS_NAME$3, "-header"),
|
|
class: ctx.headerClass
|
|
}, ctx.right ? [$close, $title] : [$title, $close]);
|
|
};
|
|
|
|
var renderBody = function renderBody(h, ctx) {
|
|
return h('div', {
|
|
key: 'body',
|
|
staticClass: "".concat(CLASS_NAME$3, "-body"),
|
|
class: ctx.bodyClass
|
|
}, [ctx.normalizeSlot(SLOT_NAME_DEFAULT, ctx.slotScope)]);
|
|
};
|
|
|
|
var renderFooter = function renderFooter(h, ctx) {
|
|
var $footer = ctx.normalizeSlot(SLOT_NAME_FOOTER, ctx.slotScope);
|
|
|
|
if (!$footer) {
|
|
return h();
|
|
}
|
|
|
|
return h('footer', {
|
|
key: 'footer',
|
|
staticClass: "".concat(CLASS_NAME$3, "-footer"),
|
|
class: ctx.footerClass
|
|
}, [$footer]);
|
|
};
|
|
|
|
var renderContent = function renderContent(h, ctx) {
|
|
// We render the header even if `lazy` is enabled as it
|
|
// acts as the accessible label for the sidebar
|
|
var $header = renderHeader(h, ctx);
|
|
|
|
if (ctx.lazy && !ctx.isOpen) {
|
|
return $header;
|
|
}
|
|
|
|
return [$header, renderBody(h, ctx), renderFooter(h, ctx)];
|
|
};
|
|
|
|
var renderBackdrop = function renderBackdrop(h, ctx) {
|
|
if (!ctx.backdrop) {
|
|
return h();
|
|
}
|
|
|
|
var backdropVariant = ctx.backdropVariant;
|
|
return h('div', {
|
|
directives: [{
|
|
name: 'show',
|
|
value: ctx.localShow
|
|
}],
|
|
staticClass: 'b-sidebar-backdrop',
|
|
class: _defineProperty({}, "bg-".concat(backdropVariant), !!backdropVariant),
|
|
on: {
|
|
click: ctx.onBackdropClick
|
|
}
|
|
});
|
|
}; // --- Main component ---
|
|
// @vue/component
|
|
|
|
|
|
var BSidebar = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SIDEBAR,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, idMixin, listenOnRootMixin, normalizeSlotMixin],
|
|
inheritAttrs: false,
|
|
model: {
|
|
prop: 'visible',
|
|
event: 'change'
|
|
},
|
|
props: makePropsConfigurable({
|
|
title: {
|
|
type: String // default: null
|
|
|
|
},
|
|
right: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
bgVariant: {
|
|
type: String,
|
|
default: 'light'
|
|
},
|
|
textVariant: {
|
|
type: String,
|
|
default: 'dark'
|
|
},
|
|
shadow: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
width: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
zIndex: {
|
|
type: [Number, String] // default: null
|
|
|
|
},
|
|
ariaLabel: {
|
|
type: String // default: null
|
|
|
|
},
|
|
ariaLabelledby: {
|
|
type: String // default: null
|
|
|
|
},
|
|
closeLabel: {
|
|
// `aria-label` for close button
|
|
// Defaults to 'Close'
|
|
type: String // default: undefined
|
|
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
sidebarClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
headerClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
bodyClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
footerClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
backdrop: {
|
|
// If `true`, shows a basic backdrop
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
backdropVariant: {
|
|
type: String,
|
|
default: 'dark'
|
|
},
|
|
noSlide: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noHeader: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noHeaderClose: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noCloseOnEsc: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noCloseOnBackdrop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noCloseOnRouteChange: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noEnforceFocus: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lazy: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_SIDEBAR),
|
|
data: function data() {
|
|
return {
|
|
// Internal `v-model` state
|
|
localShow: !!this.visible,
|
|
// For lazy render triggering
|
|
isOpen: !!this.visible
|
|
};
|
|
},
|
|
computed: {
|
|
transitionProps: function transitionProps() {
|
|
return this.noSlide ?
|
|
/* istanbul ignore next */
|
|
{
|
|
css: true
|
|
} : {
|
|
css: true,
|
|
enterClass: '',
|
|
enterActiveClass: 'slide',
|
|
enterToClass: 'show',
|
|
leaveClass: 'show',
|
|
leaveActiveClass: 'slide',
|
|
leaveToClass: ''
|
|
};
|
|
},
|
|
slotScope: function slotScope() {
|
|
return {
|
|
visible: this.localShow,
|
|
right: this.right,
|
|
hide: this.hide
|
|
};
|
|
},
|
|
computedTile: function computedTile() {
|
|
return this.normalizeSlot(SLOT_NAME_TITLE, this.slotScope) || toString$1(this.title) || null;
|
|
},
|
|
titleId: function titleId() {
|
|
return this.computedTile ? this.safeId('__title__') : null;
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
id: this.safeId(),
|
|
tabindex: '-1',
|
|
role: 'dialog',
|
|
'aria-modal': this.backdrop ? 'true' : 'false',
|
|
'aria-hidden': this.localShow ? null : 'true',
|
|
'aria-label': this.ariaLabel || null,
|
|
'aria-labelledby': this.ariaLabelledby || this.titleId || null
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
visible: function visible(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.localShow = newVal;
|
|
}
|
|
},
|
|
localShow: function localShow(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.emitState(newVal);
|
|
this.$emit('change', newVal);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
$route: function $route() {
|
|
var newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var oldVal = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
if (!this.noCloseOnRouteChange && newVal.fullPath !== oldVal.fullPath) {
|
|
this.hide();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Define non-reactive properties
|
|
this.$_returnFocusEl = null;
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// Add `$root` listeners
|
|
this.listenOnRoot(EVENT_TOGGLE, this.handleToggle);
|
|
this.listenOnRoot(EVENT_STATE_REQUEST, this.handleSync); // Send out a gratuitous state event to ensure toggle button is synced
|
|
|
|
this.$nextTick(function () {
|
|
_this.emitState(_this.localShow);
|
|
});
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
this.emitSync();
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.localShow = false;
|
|
this.$_returnFocusEl = null;
|
|
},
|
|
methods: {
|
|
hide: function hide() {
|
|
this.localShow = false;
|
|
},
|
|
emitState: function emitState() {
|
|
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.localShow;
|
|
this.emitOnRoot(EVENT_STATE, this.safeId(), state);
|
|
},
|
|
emitSync: function emitSync() {
|
|
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.localShow;
|
|
this.emitOnRoot(EVENT_STATE_SYNC, this.safeId(), state);
|
|
},
|
|
handleToggle: function handleToggle(id) {
|
|
// Note `safeId()` can be null until after mount
|
|
if (id && id === this.safeId()) {
|
|
this.localShow = !this.localShow;
|
|
}
|
|
},
|
|
handleSync: function handleSync(id) {
|
|
var _this2 = this;
|
|
|
|
// Note `safeId()` can be null until after mount
|
|
if (id && id === this.safeId()) {
|
|
this.$nextTick(function () {
|
|
_this2.emitSync(_this2.localShow);
|
|
});
|
|
}
|
|
},
|
|
onKeydown: function onKeydown(evt) {
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (!this.noCloseOnEsc && keyCode === CODE_ESC && this.localShow) {
|
|
this.hide();
|
|
}
|
|
},
|
|
onBackdropClick: function onBackdropClick() {
|
|
if (this.localShow && !this.noCloseOnBackdrop) {
|
|
this.hide();
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
onTopTrapFocus: function onTopTrapFocus() {
|
|
var tabables = getTabables(this.$refs.content);
|
|
this.enforceFocus(tabables.reverse()[0]);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
onBottomTrapFocus: function onBottomTrapFocus() {
|
|
var tabables = getTabables(this.$refs.content);
|
|
this.enforceFocus(tabables[0]);
|
|
},
|
|
onBeforeEnter: function onBeforeEnter() {
|
|
// Returning focus to `document.body` may cause unwanted scrolls,
|
|
// so we exclude setting focus on body
|
|
this.$_returnFocusEl = getActiveElement(isBrowser ? [document.body] : []); // Trigger lazy render
|
|
|
|
this.isOpen = true;
|
|
},
|
|
onAfterEnter: function onAfterEnter(el) {
|
|
if (!contains(el, getActiveElement())) {
|
|
this.enforceFocus(el);
|
|
}
|
|
|
|
this.$emit('shown');
|
|
},
|
|
onAfterLeave: function onAfterLeave() {
|
|
this.enforceFocus(this.$_returnFocusEl);
|
|
this.$_returnFocusEl = null; // Trigger lazy render
|
|
|
|
this.isOpen = false;
|
|
this.$emit('hidden');
|
|
},
|
|
enforceFocus: function enforceFocus(el) {
|
|
if (!this.noEnforceFocus) {
|
|
attemptFocus(el);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _ref;
|
|
|
|
var localShow = this.localShow;
|
|
var shadow = this.shadow === '' ? true : this.shadow;
|
|
var $sidebar = h(this.tag, {
|
|
ref: 'content',
|
|
directives: [{
|
|
name: 'show',
|
|
value: localShow
|
|
}],
|
|
staticClass: CLASS_NAME$3,
|
|
class: [(_ref = {
|
|
shadow: shadow === true
|
|
}, _defineProperty(_ref, "shadow-".concat(shadow), shadow && shadow !== true), _defineProperty(_ref, "".concat(CLASS_NAME$3, "-right"), this.right), _defineProperty(_ref, "bg-".concat(this.bgVariant), !!this.bgVariant), _defineProperty(_ref, "text-".concat(this.textVariant), !!this.textVariant), _ref), this.sidebarClass],
|
|
attrs: this.computedAttrs,
|
|
style: {
|
|
width: this.width
|
|
}
|
|
}, [renderContent(h, this)]);
|
|
$sidebar = h('transition', {
|
|
props: this.transitionProps,
|
|
on: {
|
|
beforeEnter: this.onBeforeEnter,
|
|
afterEnter: this.onAfterEnter,
|
|
afterLeave: this.onAfterLeave
|
|
}
|
|
}, [$sidebar]);
|
|
var $backdrop = h(BVTransition, {
|
|
props: {
|
|
noFade: this.noSlide
|
|
}
|
|
}, [renderBackdrop(h, this)]);
|
|
var $tabTrapTop = h();
|
|
var $tabTrapBottom = h();
|
|
|
|
if (this.backdrop && this.localShow) {
|
|
$tabTrapTop = h('div', {
|
|
attrs: {
|
|
tabindex: '0'
|
|
},
|
|
on: {
|
|
focus: this.onTopTrapFocus
|
|
}
|
|
});
|
|
$tabTrapBottom = h('div', {
|
|
attrs: {
|
|
tabindex: '0'
|
|
},
|
|
on: {
|
|
focus: this.onBottomTrapFocus
|
|
}
|
|
});
|
|
}
|
|
|
|
return h('div', {
|
|
staticClass: 'b-sidebar-outer',
|
|
style: {
|
|
zIndex: this.zIndex
|
|
},
|
|
attrs: {
|
|
tabindex: '-1'
|
|
},
|
|
on: {
|
|
keydown: this.onKeydown
|
|
}
|
|
}, [$tabTrapTop, $sidebar, $tabTrapBottom, $backdrop]);
|
|
}
|
|
});
|
|
|
|
var SidebarPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BSidebar: BSidebar
|
|
},
|
|
plugins: {
|
|
VBTogglePlugin: VBTogglePlugin
|
|
}
|
|
});
|
|
|
|
var BSkeleton = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SKELETON,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
animation: {
|
|
type: String,
|
|
default: 'wave'
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'text'
|
|
},
|
|
width: {
|
|
type: String // default: null
|
|
|
|
},
|
|
height: {
|
|
type: String // default: null
|
|
|
|
},
|
|
size: {
|
|
type: String // default: null
|
|
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
}
|
|
}, NAME_SKELETON),
|
|
render: function render(h, _ref) {
|
|
var _class;
|
|
|
|
var data = _ref.data,
|
|
props = _ref.props;
|
|
var size = props.size,
|
|
animation = props.animation,
|
|
variant = props.variant;
|
|
return h('div', a(data, {
|
|
staticClass: 'b-skeleton',
|
|
style: {
|
|
width: size || props.width,
|
|
height: size || props.height
|
|
},
|
|
class: (_class = {}, _defineProperty(_class, "b-skeleton-".concat(props.type), true), _defineProperty(_class, "b-skeleton-animate-".concat(animation), animation), _defineProperty(_class, "bg-".concat(variant), variant), _class)
|
|
}));
|
|
}
|
|
});
|
|
|
|
var BIconstack = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_ICONSTACK,
|
|
functional: true,
|
|
props: makePropsConfigurable(commonIconProps, NAME_ICONSTACK),
|
|
render: function render(h, _ref) {
|
|
var data = _ref.data,
|
|
props = _ref.props,
|
|
children = _ref.children;
|
|
return h(BVIconBase, a(data, {
|
|
staticClass: 'b-iconstack',
|
|
props: _objectSpread2(_objectSpread2({}, props), {}, {
|
|
stacked: false
|
|
})
|
|
}), children);
|
|
}
|
|
});
|
|
|
|
var BSkeletonIcon = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SKELETON_ICON,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
animation: {
|
|
type: String,
|
|
default: 'wave'
|
|
},
|
|
icon: {
|
|
type: String
|
|
},
|
|
iconProps: {
|
|
type: Object,
|
|
default: function _default() {}
|
|
}
|
|
}, NAME_SKELETON_ICON),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props;
|
|
var icon = props.icon,
|
|
animation = props.animation;
|
|
var $icon = h(BIcon, {
|
|
props: _objectSpread2({
|
|
icon: icon
|
|
}, props.iconProps),
|
|
staticClass: 'b-skeleton-icon'
|
|
});
|
|
return h('div', {
|
|
staticClass: 'b-skeleton-icon-wrapper position-relative d-inline-block overflow-hidden',
|
|
class: _defineProperty({}, "b-skeleton-animate-".concat(animation), animation)
|
|
}, [$icon]);
|
|
}
|
|
});
|
|
|
|
var BSkeletonImg = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SKELETON_IMG,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
animation: {
|
|
type: String
|
|
},
|
|
aspect: {
|
|
type: String,
|
|
default: '16:9'
|
|
},
|
|
noAspect: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
height: {
|
|
type: String
|
|
},
|
|
width: {
|
|
type: String
|
|
},
|
|
variant: {
|
|
type: String
|
|
},
|
|
cardImg: {
|
|
type: String
|
|
}
|
|
}, NAME_SKELETON_IMG),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props;
|
|
var aspect = props.aspect,
|
|
width = props.width,
|
|
height = props.height,
|
|
animation = props.animation,
|
|
variant = props.variant,
|
|
cardImg = props.cardImg;
|
|
var $img = h(BSkeleton, {
|
|
props: {
|
|
type: 'img',
|
|
width: width,
|
|
height: height,
|
|
animation: animation,
|
|
variant: variant
|
|
},
|
|
class: _defineProperty({}, "card-img-".concat(cardImg), cardImg)
|
|
});
|
|
return props.noAspect ? $img : h(BAspect, {
|
|
props: {
|
|
aspect: aspect
|
|
}
|
|
}, [$img]);
|
|
}
|
|
});
|
|
|
|
// Mixin to determine if an event listener has been registered
|
|
|
|
var hasListenerMixin = {
|
|
methods: {
|
|
hasListener: function hasListener(name) {
|
|
// Only includes listeners registered via `v-on:name`
|
|
var $listeners = this.$listeners || {}; // Includes `v-on:name` and `this.$on('name')` registered listeners
|
|
// Note this property is not part of the public Vue API, but it is
|
|
// the only way to determine if a listener was added via `vm.$on`
|
|
|
|
var $events = this._events || {}; // Registered listeners in `this._events` are always an array,
|
|
// but might be zero length
|
|
|
|
return !isUndefined($listeners[name]) || isArray($events[name]) && $events[name].length > 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$14 = makePropsConfigurable({
|
|
variant: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
}, NAME_TR);
|
|
var LIGHT = 'light';
|
|
var DARK = 'dark'; // TODO:
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BTr = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TR,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTableTr: this
|
|
};
|
|
},
|
|
inject: {
|
|
bvTableRowGroup: {
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$14,
|
|
computed: {
|
|
inTbody: function inTbody() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isTbody;
|
|
},
|
|
inThead: function inThead() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isThead;
|
|
},
|
|
inTfoot: function inTfoot() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isTfoot;
|
|
},
|
|
isDark: function isDark() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isDark;
|
|
},
|
|
isStacked: function isStacked() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isStacked;
|
|
},
|
|
isResponsive: function isResponsive() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.isResponsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
// Sticky headers are only supported in thead
|
|
return this.bvTableRowGroup.isStickyHeader;
|
|
},
|
|
hasStickyHeader: function hasStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
return !this.isStacked && this.bvTableRowGroup.hasStickyHeader;
|
|
},
|
|
tableVariant: function tableVariant() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.bvTableRowGroup.tableVariant;
|
|
},
|
|
headVariant: function headVariant() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.inThead ? this.bvTableRowGroup.headVariant : null;
|
|
},
|
|
footVariant: function footVariant() {
|
|
// Sniffed by <b-td> / <b-th>
|
|
return this.inTfoot ? this.bvTableRowGroup.footVariant : null;
|
|
},
|
|
isRowDark: function isRowDark() {
|
|
return this.headVariant === LIGHT || this.footVariant === LIGHT ?
|
|
/* istanbul ignore next */
|
|
false : this.headVariant === DARK || this.footVariant === DARK ?
|
|
/* istanbul ignore next */
|
|
true : this.isDark;
|
|
},
|
|
trClasses: function trClasses() {
|
|
return [this.variant ? "".concat(this.isRowDark ? 'bg' : 'table', "-").concat(this.variant) : null];
|
|
},
|
|
trAttrs: function trAttrs() {
|
|
return _objectSpread2({
|
|
role: 'row'
|
|
}, this.bvAttrs);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('tr', {
|
|
class: this.trClasses,
|
|
attrs: this.trAttrs,
|
|
// Pass native listeners to child
|
|
on: this.bvListeners
|
|
}, this.normalizeSlot());
|
|
}
|
|
});
|
|
|
|
var slotName = 'bottom-row';
|
|
var bottomRowMixin = {
|
|
methods: {
|
|
renderBottomRow: function renderBottomRow() {
|
|
var h = this.$createElement; // Static bottom row slot (hidden in visibly stacked mode as we can't control the data-label)
|
|
// If in *always* stacked mode, we don't bother rendering the row
|
|
|
|
if (!this.hasNormalizedSlot(slotName) || this.stacked === true || this.stacked === '') {
|
|
return h();
|
|
}
|
|
|
|
var fields = this.computedFields;
|
|
return h(BTr, {
|
|
key: 'b-bottom-row',
|
|
staticClass: 'b-table-bottom-row',
|
|
class: [isFunction(this.tbodyTrClass) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrClass(null, 'row-bottom') : this.tbodyTrClass],
|
|
attrs: isFunction(this.tbodyTrAttr) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrAttr(null, 'row-bottom') : this.tbodyTrAttr
|
|
}, this.normalizeSlot(slotName, {
|
|
columns: fields.length,
|
|
fields: fields
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Parse a rowspan or colspan into a digit (or `null` if < `1` )
|
|
|
|
var parseSpan = function parseSpan(value) {
|
|
value = toInteger(value, 0);
|
|
return value > 0 ? value : null;
|
|
};
|
|
/* istanbul ignore next */
|
|
|
|
|
|
var spanValidator = function spanValidator(val) {
|
|
return isUndefinedOrNull(val) || parseSpan(val) > 0;
|
|
}; // --- Props ---
|
|
|
|
|
|
var props$15 = makePropsConfigurable({
|
|
variant: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
colspan: {
|
|
type: [Number, String],
|
|
default: null,
|
|
validator: spanValidator
|
|
},
|
|
rowspan: {
|
|
type: [Number, String],
|
|
default: null,
|
|
validator: spanValidator
|
|
},
|
|
stackedHeading: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
stickyColumn: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TABLE_CELL); // --- Main component ---
|
|
// TODO:
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BTd = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TABLE_CELL,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
inject: {
|
|
bvTableTr: {
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$15,
|
|
computed: {
|
|
tag: function tag() {
|
|
// Overridden by <b-th>
|
|
return 'td';
|
|
},
|
|
inTbody: function inTbody() {
|
|
return this.bvTableTr.inTbody;
|
|
},
|
|
inThead: function inThead() {
|
|
return this.bvTableTr.inThead;
|
|
},
|
|
inTfoot: function inTfoot() {
|
|
return this.bvTableTr.inTfoot;
|
|
},
|
|
isDark: function isDark() {
|
|
return this.bvTableTr.isDark;
|
|
},
|
|
isStacked: function isStacked() {
|
|
return this.bvTableTr.isStacked;
|
|
},
|
|
isStackedCell: function isStackedCell() {
|
|
// We only support stacked-heading in tbody in stacked mode
|
|
return this.inTbody && this.isStacked;
|
|
},
|
|
isResponsive: function isResponsive() {
|
|
return this.bvTableTr.isResponsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
// Sticky headers only apply to cells in table `thead`
|
|
return this.bvTableTr.isStickyHeader;
|
|
},
|
|
hasStickyHeader: function hasStickyHeader() {
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
return this.bvTableTr.hasStickyHeader;
|
|
},
|
|
isStickyColumn: function isStickyColumn() {
|
|
// Needed to handle background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
// Sticky column cells are only available in responsive
|
|
// mode (horizontal scrolling) or when sticky header mode
|
|
// Applies to cells in `thead`, `tbody` and `tfoot`
|
|
return !this.isStacked && (this.isResponsive || this.hasStickyHeader) && this.stickyColumn;
|
|
},
|
|
rowVariant: function rowVariant() {
|
|
return this.bvTableTr.variant;
|
|
},
|
|
headVariant: function headVariant() {
|
|
return this.bvTableTr.headVariant;
|
|
},
|
|
footVariant: function footVariant() {
|
|
return this.bvTableTr.footVariant;
|
|
},
|
|
tableVariant: function tableVariant() {
|
|
return this.bvTableTr.tableVariant;
|
|
},
|
|
computedColspan: function computedColspan() {
|
|
return parseSpan(this.colspan);
|
|
},
|
|
computedRowspan: function computedRowspan() {
|
|
return parseSpan(this.rowspan);
|
|
},
|
|
cellClasses: function cellClasses() {
|
|
// We use computed props here for improved performance by caching
|
|
// the results of the string interpolation
|
|
var variant = this.variant;
|
|
|
|
if (!variant && this.isStickyHeader && !this.headVariant || !variant && this.isStickyColumn && this.inTfoot && !this.footVariant || !variant && this.isStickyColumn && this.inThead && !this.headVariant || !variant && this.isStickyColumn && this.inTbody) {
|
|
// Needed for sticky-header mode as Bootstrap v4 table cells do
|
|
// not inherit parent's background-color. Boo!
|
|
variant = this.rowVariant || this.tableVariant || 'b-table-default';
|
|
}
|
|
|
|
return [variant ? "".concat(this.isDark ? 'bg' : 'table', "-").concat(variant) : null, this.isStickyColumn ? 'b-table-sticky-column' : null];
|
|
},
|
|
cellAttrs: function cellAttrs() {
|
|
// We use computed props here for improved performance by caching
|
|
// the results of the object spread (Object.assign)
|
|
var headOrFoot = this.inThead || this.inTfoot; // Make sure col/rowspan's are > 0 or null
|
|
|
|
var colspan = this.computedColspan;
|
|
var rowspan = this.computedRowspan; // Default role and scope
|
|
|
|
var role = 'cell';
|
|
var scope = null; // Compute role and scope
|
|
// We only add scopes with an explicit span of 1 or greater
|
|
|
|
if (headOrFoot) {
|
|
// Header or footer cells
|
|
role = 'columnheader';
|
|
scope = colspan > 0 ? 'colspan' : 'col';
|
|
} else if (isTag(this.tag, 'th')) {
|
|
// th's in tbody
|
|
role = 'rowheader';
|
|
scope = rowspan > 0 ? 'rowgroup' : 'row';
|
|
}
|
|
|
|
return _objectSpread2(_objectSpread2({
|
|
colspan: colspan,
|
|
rowspan: rowspan,
|
|
role: role,
|
|
scope: scope
|
|
}, this.bvAttrs), {}, {
|
|
// Add in the stacked cell label data-attribute if in
|
|
// stacked mode (if a stacked heading label is provided)
|
|
'data-label': this.isStackedCell && !isUndefinedOrNull(this.stackedHeading) ?
|
|
/* istanbul ignore next */
|
|
toString$1(this.stackedHeading) : null
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var content = [this.normalizeSlot()];
|
|
return h(this.tag, {
|
|
class: this.cellClasses,
|
|
attrs: this.cellAttrs,
|
|
// Transfer any native listeners
|
|
on: this.bvListeners
|
|
}, [this.isStackedCell ? h('div', [content]) : content]);
|
|
}
|
|
});
|
|
|
|
var busySlotName = 'table-busy';
|
|
var busyMixin = {
|
|
props: makePropsConfigurable({
|
|
busy: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TABLE),
|
|
data: function data() {
|
|
return {
|
|
localBusy: false
|
|
};
|
|
},
|
|
computed: {
|
|
computedBusy: function computedBusy() {
|
|
return this.busy || this.localBusy;
|
|
}
|
|
},
|
|
watch: {
|
|
localBusy: function localBusy(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.$emit('update:busy', newVal);
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// Event handler helper
|
|
stopIfBusy: function stopIfBusy(evt) {
|
|
if (this.computedBusy) {
|
|
// If table is busy (via provider) then don't propagate
|
|
stopEvent(evt);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
// Render the busy indicator or return `null` if not busy
|
|
renderBusy: function renderBusy() {
|
|
var h = this.$createElement; // Return a busy indicator row, or `null` if not busy
|
|
|
|
if (this.computedBusy && this.hasNormalizedSlot(busySlotName)) {
|
|
// Show the busy slot
|
|
return h(BTr, {
|
|
key: 'table-busy-slot',
|
|
staticClass: 'b-table-busy-slot',
|
|
class: [isFunction(this.tbodyTrClass) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrClass(null, busySlotName) : this.tbodyTrClass],
|
|
attrs: isFunction(this.tbodyTrAttr) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrAttr(null, busySlotName) : this.tbodyTrAttr
|
|
}, [h(BTd, {
|
|
props: {
|
|
colspan: this.computedFields.length || null
|
|
}
|
|
}, [this.normalizeSlot(busySlotName)])]);
|
|
} else {
|
|
// We return `null` here so that we can determine if we need to
|
|
// render the table items rows or not
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var captionMixin = {
|
|
props: makePropsConfigurable({
|
|
// `caption-top` is part of table-render mixin (styling)
|
|
// captionTop: {
|
|
// type: Boolean,
|
|
// default: false
|
|
// },
|
|
caption: {
|
|
type: String // default: null
|
|
|
|
},
|
|
captionHtml: {
|
|
type: String
|
|
}
|
|
}, NAME_TABLE),
|
|
computed: {
|
|
captionId: function captionId() {
|
|
// Even though `this.safeId` looks like a method, it is a computed prop
|
|
// that returns a new function if the underlying ID changes
|
|
return this.isStacked ? this.safeId('_caption_') : null;
|
|
}
|
|
},
|
|
methods: {
|
|
renderCaption: function renderCaption() {
|
|
var caption = this.caption,
|
|
captionHtml = this.captionHtml;
|
|
var h = this.$createElement;
|
|
var $caption = h();
|
|
var hasCaptionSlot = this.hasNormalizedSlot('table-caption');
|
|
|
|
if (hasCaptionSlot || caption || captionHtml) {
|
|
$caption = h('caption', {
|
|
key: 'caption',
|
|
attrs: {
|
|
id: this.captionId
|
|
},
|
|
domProps: hasCaptionSlot ? {} : htmlOrText(captionHtml, caption)
|
|
}, this.normalizeSlot('table-caption'));
|
|
}
|
|
|
|
return $caption;
|
|
}
|
|
}
|
|
};
|
|
|
|
var colgroupMixin = {
|
|
methods: {
|
|
renderColgroup: function renderColgroup() {
|
|
var h = this.$createElement;
|
|
var fields = this.computedFields;
|
|
var $colgroup = h();
|
|
|
|
if (this.hasNormalizedSlot('table-colgroup')) {
|
|
$colgroup = h('colgroup', {
|
|
key: 'colgroup'
|
|
}, [this.normalizeSlot('table-colgroup', {
|
|
columns: fields.length,
|
|
fields: fields
|
|
})]);
|
|
}
|
|
|
|
return $colgroup;
|
|
}
|
|
}
|
|
};
|
|
|
|
var emptyMixin = {
|
|
props: makePropsConfigurable({
|
|
showEmpty: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
emptyText: {
|
|
type: String,
|
|
default: 'There are no records to show'
|
|
},
|
|
emptyHtml: {
|
|
type: String
|
|
},
|
|
emptyFilteredText: {
|
|
type: String,
|
|
default: 'There are no records matching your request'
|
|
},
|
|
emptyFilteredHtml: {
|
|
type: String
|
|
}
|
|
}, NAME_TABLE),
|
|
methods: {
|
|
renderEmpty: function renderEmpty() {
|
|
var h = this.$createElement;
|
|
var items = this.computedItems;
|
|
var $empty = h();
|
|
|
|
if (this.showEmpty && (!items || items.length === 0) && !(this.computedBusy && this.hasNormalizedSlot('table-busy'))) {
|
|
var isFiltered = this.isFiltered,
|
|
emptyText = this.emptyText,
|
|
emptyHtml = this.emptyHtml,
|
|
emptyFilteredText = this.emptyFilteredText,
|
|
emptyFilteredHtml = this.emptyFilteredHtml,
|
|
computedFields = this.computedFields,
|
|
tbodyTrClass = this.tbodyTrClass,
|
|
tbodyTrAttr = this.tbodyTrAttr;
|
|
$empty = this.normalizeSlot(this.isFiltered ? 'emptyfiltered' : 'empty', {
|
|
emptyFilteredHtml: emptyFilteredHtml,
|
|
emptyFilteredText: emptyFilteredText,
|
|
emptyHtml: emptyHtml,
|
|
emptyText: emptyText,
|
|
fields: computedFields,
|
|
// Not sure why this is included, as it will always be an empty array
|
|
items: this.computedItems
|
|
});
|
|
|
|
if (!$empty) {
|
|
$empty = h('div', {
|
|
class: ['text-center', 'my-2'],
|
|
domProps: isFiltered ? htmlOrText(emptyFilteredHtml, emptyFilteredText) : htmlOrText(emptyHtml, emptyText)
|
|
});
|
|
}
|
|
|
|
$empty = h(BTd, {
|
|
props: {
|
|
colspan: computedFields.length || null
|
|
}
|
|
}, [h('div', {
|
|
attrs: {
|
|
role: 'alert',
|
|
'aria-live': 'polite'
|
|
}
|
|
}, [$empty])]);
|
|
$empty = h(BTr, {
|
|
staticClass: 'b-table-empty-row',
|
|
class: [isFunction(tbodyTrClass) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrClass(null, 'row-empty') : tbodyTrClass],
|
|
attrs: isFunction(tbodyTrAttr) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrAttr(null, 'row-empty') : tbodyTrAttr,
|
|
key: isFiltered ? 'b-empty-filtered-row' : 'b-empty-row'
|
|
}, [$empty]);
|
|
}
|
|
|
|
return $empty;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Constants used by table helpers
|
|
// Object of item keys that should be ignored for headers and
|
|
// stringification and filter events
|
|
var IGNORED_FIELD_KEYS = {
|
|
_rowVariant: true,
|
|
_cellVariants: true,
|
|
_showDetails: true
|
|
}; // Filter CSS selector for click/dblclick/etc. events
|
|
// If any of these selectors match the clicked element, we ignore the event
|
|
|
|
var EVENT_FILTER = ['a', 'a *', // Include content inside links
|
|
'button', 'button *', // Include content inside buttons
|
|
'input:not(.disabled):not([disabled])', 'select:not(.disabled):not([disabled])', 'textarea:not(.disabled):not([disabled])', '[role="link"]', '[role="link"] *', '[role="button"]', '[role="button"] *', '[tabindex]:not(.disabled):not([disabled])'].join(',');
|
|
|
|
var sanitizeRow = function sanitizeRow(row, ignoreFields, includeFields) {
|
|
var fieldsObj = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
// We first need to format the row based on the field configurations
|
|
// This ensures that we add formatted values for keys that may not
|
|
// exist in the row itself
|
|
var formattedRow = keys(fieldsObj).reduce(function (result, key) {
|
|
var field = fieldsObj[key];
|
|
var filterByFormatted = field.filterByFormatted;
|
|
var formatter = isFunction(filterByFormatted) ?
|
|
/* istanbul ignore next */
|
|
filterByFormatted : filterByFormatted ?
|
|
/* istanbul ignore next */
|
|
field.formatter : null;
|
|
|
|
if (isFunction(formatter)) {
|
|
result[key] = formatter(row[key], key, row);
|
|
}
|
|
|
|
return result;
|
|
}, clone(row)); // Determine the allowed keys:
|
|
// - Ignore special fields that start with `_`
|
|
// - Ignore fields in the `ignoreFields` array
|
|
// - Include only fields in the `includeFields` array
|
|
|
|
var allowedKeys = keys(formattedRow).filter(function (key) {
|
|
return !IGNORED_FIELD_KEYS[key] && !(isArray(ignoreFields) && ignoreFields.length > 0 && arrayIncludes(ignoreFields, key)) && !(isArray(includeFields) && includeFields.length > 0 && !arrayIncludes(includeFields, key));
|
|
});
|
|
return pick(formattedRow, allowedKeys);
|
|
};
|
|
|
|
// SSR safe deterministic way (keys are sorted before stringification)
|
|
//
|
|
// ex:
|
|
// { b: 3, c: { z: 'zzz', d: null, e: 2 }, d: [10, 12, 11], a: 'one' }
|
|
// becomes
|
|
// 'one 3 2 zzz 10 12 11'
|
|
//
|
|
// Primitives (numbers/strings) are returned as-is
|
|
// Null and undefined values are filtered out
|
|
// Dates are converted to their native string format
|
|
|
|
var stringifyObjectValues = function stringifyObjectValues(val) {
|
|
if (isUndefinedOrNull(val)) {
|
|
/* istanbul ignore next */
|
|
return '';
|
|
} // Arrays are also object, and keys just returns the array indexes
|
|
// Date objects we convert to strings
|
|
|
|
|
|
if (isObject(val) && !isDate(val)) {
|
|
return keys(val).sort() // Sort to prevent SSR issues on pre-rendered sorted tables
|
|
.filter(function (v) {
|
|
return !isUndefinedOrNull(v);
|
|
}) // Ignore undefined/null values
|
|
.map(function (k) {
|
|
return stringifyObjectValues(val[k]);
|
|
}).join(' ');
|
|
}
|
|
|
|
return toString$1(val);
|
|
};
|
|
|
|
// TODO: Add option to stringify `scopedSlot` items
|
|
|
|
var stringifyRecordValues = function stringifyRecordValues(row, ignoreFields, includeFields, fieldsObj) {
|
|
return isObject(row) ? stringifyObjectValues(sanitizeRow(row, ignoreFields, includeFields, fieldsObj)) :
|
|
/* istanbul ignore next */
|
|
'';
|
|
};
|
|
|
|
var DEBOUNCE_DEPRECATED_MSG = 'Prop "filter-debounce" is deprecated. Use the debounce feature of "<b-form-input>" instead.';
|
|
var filteringMixin = {
|
|
props: makePropsConfigurable({
|
|
filter: {
|
|
type: [String, RegExp, Object, Array],
|
|
default: null
|
|
},
|
|
filterFunction: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
filterIgnoredFields: {
|
|
type: Array // default: undefined
|
|
|
|
},
|
|
filterIncludedFields: {
|
|
type: Array // default: undefined
|
|
|
|
},
|
|
filterDebounce: {
|
|
type: [Number, String],
|
|
deprecated: DEBOUNCE_DEPRECATED_MSG,
|
|
default: 0,
|
|
validator: function validator(value) {
|
|
return /^\d+/.test(String(value));
|
|
}
|
|
}
|
|
}, NAME_TABLE),
|
|
data: function data() {
|
|
return {
|
|
// Flag for displaying which empty slot to show and some event triggering
|
|
isFiltered: false,
|
|
// Where we store the copy of the filter criteria after debouncing
|
|
// We pre-set it with the sanitized filter value
|
|
localFilter: this.filterSanitize(this.filter)
|
|
};
|
|
},
|
|
computed: {
|
|
computedFilterIgnored: function computedFilterIgnored() {
|
|
return concat(this.filterIgnoredFields || []).filter(identity);
|
|
},
|
|
computedFilterIncluded: function computedFilterIncluded() {
|
|
return concat(this.filterIncludedFields || []).filter(identity);
|
|
},
|
|
computedFilterDebounce: function computedFilterDebounce() {
|
|
var ms = toInteger(this.filterDebounce, 0);
|
|
/* istanbul ignore next */
|
|
|
|
if (ms > 0) {
|
|
warn(DEBOUNCE_DEPRECATED_MSG, NAME_TABLE);
|
|
}
|
|
|
|
return ms;
|
|
},
|
|
localFiltering: function localFiltering() {
|
|
return this.hasProvider ? !!this.noProviderFiltering : true;
|
|
},
|
|
// For watching changes to `filteredItems` vs `localItems`
|
|
filteredCheck: function filteredCheck() {
|
|
return {
|
|
filteredItems: this.filteredItems,
|
|
localItems: this.localItems,
|
|
localFilter: this.localFilter
|
|
};
|
|
},
|
|
// Sanitized/normalize filter-function prop
|
|
localFilterFn: function localFilterFn() {
|
|
// Return `null` to signal to use internal filter function
|
|
var filterFunction = this.filterFunction;
|
|
var result = null;
|
|
|
|
try {
|
|
result = filterFunction();
|
|
} catch (_unused) {}
|
|
|
|
return isUndefined(result) ? null : filterFunction;
|
|
},
|
|
// Returns the records in `localItems` that match the filter criteria
|
|
// Returns the original `localItems` array if not sorting
|
|
filteredItems: function filteredItems() {
|
|
var items = this.localItems || []; // Note the criteria is debounced and sanitized
|
|
|
|
var criteria = this.localFilter; // Resolve the filtering function, when requested
|
|
// We prefer the provided filtering function and fallback to the internal one
|
|
// When no filtering criteria is specified the filtering factories will return `null`
|
|
|
|
var filterFn = this.localFiltering ? this.filterFnFactory(this.localFilterFn, criteria) || this.defaultFilterFnFactory(criteria) : null; // We only do local filtering when requested and there are records to filter
|
|
|
|
return filterFn && items.length > 0 ? items.filter(filterFn) : items;
|
|
}
|
|
},
|
|
watch: {
|
|
// Watch for debounce being set to 0
|
|
computedFilterDebounce: function computedFilterDebounce(newVal) {
|
|
if (!newVal && this.$_filterTimer) {
|
|
this.clearFilterTimer();
|
|
this.localFilter = this.filterSanitize(this.filter);
|
|
}
|
|
},
|
|
// Watch for changes to the filter criteria, and debounce if necessary
|
|
filter: {
|
|
// We need a deep watcher in case the user passes
|
|
// an object when using `filter-function`
|
|
deep: true,
|
|
handler: function handler(newCriteria) {
|
|
var _this = this;
|
|
|
|
var timeout = this.computedFilterDebounce;
|
|
this.clearFilterTimer();
|
|
|
|
if (timeout && timeout > 0) {
|
|
// If we have a debounce time, delay the update of `localFilter`
|
|
this.$_filterTimer = setTimeout(function () {
|
|
_this.localFilter = _this.filterSanitize(newCriteria);
|
|
}, timeout);
|
|
} else {
|
|
// Otherwise, immediately update `localFilter` with `newFilter` value
|
|
this.localFilter = this.filterSanitize(newCriteria);
|
|
}
|
|
}
|
|
},
|
|
// Watch for changes to the filter criteria and filtered items vs `localItems`
|
|
// Set visual state and emit events as required
|
|
filteredCheck: function filteredCheck(_ref) {
|
|
var filteredItems = _ref.filteredItems,
|
|
localFilter = _ref.localFilter;
|
|
// Determine if the dataset is filtered or not
|
|
var isFiltered = false;
|
|
|
|
if (!localFilter) {
|
|
// If filter criteria is falsey
|
|
isFiltered = false;
|
|
} else if (looseEqual(localFilter, []) || looseEqual(localFilter, {})) {
|
|
// If filter criteria is an empty array or object
|
|
isFiltered = false;
|
|
} else if (localFilter) {
|
|
// If filter criteria is truthy
|
|
isFiltered = true;
|
|
}
|
|
|
|
if (isFiltered) {
|
|
this.$emit('filtered', filteredItems, filteredItems.length);
|
|
}
|
|
|
|
this.isFiltered = isFiltered;
|
|
},
|
|
isFiltered: function isFiltered(newVal, oldVal) {
|
|
if (newVal === false && oldVal === true) {
|
|
// We need to emit a filtered event if isFiltered transitions from true to
|
|
// false so that users can update their pagination controls.
|
|
this.$emit('filtered', this.localItems, this.localItems.length);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this2 = this;
|
|
|
|
// Create private non-reactive props
|
|
this.$_filterTimer = null; // If filter is "pre-set", set the criteria
|
|
// This will trigger any watchers/dependents
|
|
// this.localFilter = this.filterSanitize(this.filter)
|
|
// Set the initial filtered state in a `$nextTick()` so that
|
|
// we trigger a filtered event if needed
|
|
|
|
this.$nextTick(function () {
|
|
_this2.isFiltered = Boolean(_this2.localFilter);
|
|
});
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.clearFilterTimer();
|
|
},
|
|
methods: {
|
|
clearFilterTimer: function clearFilterTimer() {
|
|
clearTimeout(this.$_filterTimer);
|
|
this.$_filterTimer = null;
|
|
},
|
|
filterSanitize: function filterSanitize(criteria) {
|
|
// Sanitizes filter criteria based on internal or external filtering
|
|
if (this.localFiltering && !this.localFilterFn && !(isString(criteria) || isRegExp(criteria))) {
|
|
// If using internal filter function, which only accepts string or RegExp,
|
|
// return '' to signify no filter
|
|
return '';
|
|
} // Could be a string, object or array, as needed by external filter function
|
|
// We use `cloneDeep` to ensure we have a new copy of an object or array
|
|
// without Vue's reactive observers
|
|
|
|
|
|
return cloneDeep(criteria);
|
|
},
|
|
// Filter Function factories
|
|
filterFnFactory: function filterFnFactory(filterFn, criteria) {
|
|
// Wrapper factory for external filter functions
|
|
// Wrap the provided filter-function and return a new function
|
|
// Returns `null` if no filter-function defined or if criteria is falsey
|
|
// Rather than directly grabbing `this.computedLocalFilterFn` or `this.filterFunction`
|
|
// we have it passed, so that the caller computed prop will be reactive to changes
|
|
// in the original filter-function (as this routine is a method)
|
|
if (!filterFn || !isFunction(filterFn) || !criteria || looseEqual(criteria, []) || looseEqual(criteria, {})) {
|
|
return null;
|
|
} // Build the wrapped filter test function, passing the criteria to the provided function
|
|
|
|
|
|
var fn = function fn(item) {
|
|
// Generated function returns true if the criteria matches part
|
|
// of the serialized data, otherwise false
|
|
return filterFn(item, criteria);
|
|
}; // Return the wrapped function
|
|
|
|
|
|
return fn;
|
|
},
|
|
defaultFilterFnFactory: function defaultFilterFnFactory(criteria) {
|
|
var _this3 = this;
|
|
|
|
// Generates the default filter function, using the given filter criteria
|
|
// Returns `null` if no criteria or criteria format not supported
|
|
if (!criteria || !(isString(criteria) || isRegExp(criteria))) {
|
|
// Built in filter can only support strings or RegExp criteria (at the moment)
|
|
return null;
|
|
} // Build the RegExp needed for filtering
|
|
|
|
|
|
var regExp = criteria;
|
|
|
|
if (isString(regExp)) {
|
|
// Escape special RegExp characters in the string and convert contiguous
|
|
// whitespace to \s+ matches
|
|
var pattern = escapeRegExp(criteria).replace(RX_SPACES, '\\s+'); // Build the RegExp (no need for global flag, as we only need
|
|
// to find the value once in the string)
|
|
|
|
regExp = new RegExp(".*".concat(pattern, ".*"), 'i');
|
|
} // Generate the wrapped filter test function to use
|
|
|
|
|
|
var fn = function fn(item) {
|
|
// This searches all row values (and sub property values) in the entire (excluding
|
|
// special `_` prefixed keys), because we convert the record to a space-separated
|
|
// string containing all the value properties (recursively), even ones that are
|
|
// not visible (not specified in this.fields)
|
|
// Users can ignore filtering on specific fields, or on only certain fields,
|
|
// and can optionall specify searching results of fields with formatter
|
|
//
|
|
// TODO: Enable searching on scoped slots (optional, as it will be SLOW)
|
|
//
|
|
// Generated function returns true if the criteria matches part of
|
|
// the serialized data, otherwise false
|
|
//
|
|
// We set `lastIndex = 0` on the `RegExp` in case someone specifies the `/g` global flag
|
|
regExp.lastIndex = 0;
|
|
return regExp.test(stringifyRecordValues(item, _this3.computedFilterIgnored, _this3.computedFilterIncluded, _this3.computedFieldsObj));
|
|
}; // Return the generated function
|
|
|
|
|
|
return fn;
|
|
}
|
|
}
|
|
};
|
|
|
|
var processField = function processField(key, value) {
|
|
var field = null;
|
|
|
|
if (isString(value)) {
|
|
// Label shortcut
|
|
field = {
|
|
key: key,
|
|
label: value
|
|
};
|
|
} else if (isFunction(value)) {
|
|
// Formatter shortcut
|
|
field = {
|
|
key: key,
|
|
formatter: value
|
|
};
|
|
} else if (isObject(value)) {
|
|
field = clone(value);
|
|
field.key = field.key || key;
|
|
} else if (value !== false) {
|
|
// Fallback to just key
|
|
|
|
/* istanbul ignore next */
|
|
field = {
|
|
key: key
|
|
};
|
|
}
|
|
|
|
return field;
|
|
}; // We normalize fields into an array of objects
|
|
// [ { key:..., label:..., ...}, {...}, ..., {..}]
|
|
|
|
|
|
var normalizeFields = function normalizeFields(origFields, items) {
|
|
var fields = [];
|
|
|
|
if (isArray(origFields)) {
|
|
// Normalize array Form
|
|
origFields.filter(identity).forEach(function (f) {
|
|
if (isString(f)) {
|
|
fields.push({
|
|
key: f,
|
|
label: startCase(f)
|
|
});
|
|
} else if (isObject(f) && f.key && isString(f.key)) {
|
|
// Full object definition. We use assign so that we don't mutate the original
|
|
fields.push(clone(f));
|
|
} else if (isObject(f) && keys(f).length === 1) {
|
|
// Shortcut object (i.e. { 'foo_bar': 'This is Foo Bar' }
|
|
var key = keys(f)[0];
|
|
var field = processField(key, f[key]);
|
|
|
|
if (field) {
|
|
fields.push(field);
|
|
}
|
|
}
|
|
});
|
|
} // If no field provided, take a sample from first record (if exits)
|
|
|
|
|
|
if (fields.length === 0 && isArray(items) && items.length > 0) {
|
|
var sample = items[0];
|
|
keys(sample).forEach(function (k) {
|
|
if (!IGNORED_FIELD_KEYS[k]) {
|
|
fields.push({
|
|
key: k,
|
|
label: startCase(k)
|
|
});
|
|
}
|
|
});
|
|
} // Ensure we have a unique array of fields and that they have String labels
|
|
|
|
|
|
var memo = {};
|
|
return fields.filter(function (f) {
|
|
if (!memo[f.key]) {
|
|
memo[f.key] = true;
|
|
f.label = isString(f.label) ? f.label : startCase(f.key);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
};
|
|
|
|
var itemsMixin = {
|
|
props: makePropsConfigurable({
|
|
items: {
|
|
// Provider mixin adds in `Function` type
|
|
type: Array,
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
},
|
|
fields: {
|
|
type: Array,
|
|
default: null
|
|
},
|
|
primaryKey: {
|
|
// Primary key for record
|
|
// If provided the value in each row must be unique!
|
|
type: String // default: null
|
|
|
|
},
|
|
value: {
|
|
// `v-model` for retrieving the current displayed rows
|
|
type: Array,
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
}
|
|
}, NAME_TABLE),
|
|
data: function data() {
|
|
return {
|
|
// Our local copy of the items
|
|
// Must be an array
|
|
localItems: isArray(this.items) ? this.items.slice() : []
|
|
};
|
|
},
|
|
computed: {
|
|
computedFields: function computedFields() {
|
|
// We normalize fields into an array of objects
|
|
// `[ { key:..., label:..., ...}, {...}, ..., {..}]`
|
|
return normalizeFields(this.fields, this.localItems);
|
|
},
|
|
computedFieldsObj: function computedFieldsObj() {
|
|
// Fields as a simple lookup hash object
|
|
// Mainly for formatter lookup and use in `scopedSlots` for convenience
|
|
// If the field has a formatter, it normalizes formatter to a
|
|
// function ref or `undefined` if no formatter
|
|
var parent = this.$parent;
|
|
return this.computedFields.reduce(function (obj, f) {
|
|
// We use object spread here so we don't mutate the original field object
|
|
obj[f.key] = clone(f);
|
|
|
|
if (f.formatter) {
|
|
// Normalize formatter to a function ref or `undefined`
|
|
var formatter = f.formatter;
|
|
|
|
if (isString(formatter) && isFunction(parent[formatter])) {
|
|
formatter = parent[formatter];
|
|
} else if (!isFunction(formatter)) {
|
|
/* istanbul ignore next */
|
|
formatter = undefined;
|
|
} // Return formatter function or `undefined` if none
|
|
|
|
|
|
obj[f.key].formatter = formatter;
|
|
}
|
|
|
|
return obj;
|
|
}, {});
|
|
},
|
|
computedItems: function computedItems() {
|
|
// Fallback if various mixins not provided
|
|
return (this.paginatedItems || this.sortedItems || this.filteredItems || this.localItems ||
|
|
/* istanbul ignore next */
|
|
[]).slice();
|
|
},
|
|
context: function context() {
|
|
// Current state of sorting, filtering and pagination props/values
|
|
return {
|
|
filter: this.localFilter,
|
|
sortBy: this.localSortBy,
|
|
sortDesc: this.localSortDesc,
|
|
perPage: mathMax(toInteger(this.perPage, 0), 0),
|
|
currentPage: mathMax(toInteger(this.currentPage, 0), 1),
|
|
apiUrl: this.apiUrl
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
items: function items(newItems) {
|
|
/* istanbul ignore else */
|
|
if (isArray(newItems)) {
|
|
// Set `localItems`/`filteredItems` to a copy of the provided array
|
|
this.localItems = newItems.slice();
|
|
} else if (isUndefinedOrNull(newItems)) {
|
|
/* istanbul ignore next */
|
|
this.localItems = [];
|
|
}
|
|
},
|
|
// Watch for changes on `computedItems` and update the `v-model`
|
|
computedItems: function computedItems(newVal, oldVal) {
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$emit('input', newVal);
|
|
}
|
|
},
|
|
// Watch for context changes
|
|
context: function context(newVal, oldVal) {
|
|
// Emit context information for external paging/filtering/sorting handling
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$emit('context-changed', newVal);
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
// Initially update the `v-model` of displayed items
|
|
this.$emit('input', this.computedItems);
|
|
},
|
|
methods: {
|
|
// Method to get the formatter method for a given field key
|
|
getFieldFormatter: function getFieldFormatter(key) {
|
|
var field = this.computedFieldsObj[key]; // `this.computedFieldsObj` has pre-normalized the formatter to a
|
|
// function ref if present, otherwise `undefined`
|
|
|
|
return field ? field.formatter : undefined;
|
|
}
|
|
}
|
|
};
|
|
|
|
var paginationMixin$1 = {
|
|
props: makePropsConfigurable({
|
|
perPage: {
|
|
type: [Number, String],
|
|
default: 0
|
|
},
|
|
currentPage: {
|
|
type: [Number, String],
|
|
default: 1
|
|
}
|
|
}, NAME_TABLE),
|
|
computed: {
|
|
localPaging: function localPaging() {
|
|
return this.hasProvider ? !!this.noProviderPaging : true;
|
|
},
|
|
paginatedItems: function paginatedItems() {
|
|
var items = this.sortedItems || this.filteredItems || this.localItems || [];
|
|
var currentPage = mathMax(toInteger(this.currentPage, 1), 1);
|
|
var perPage = mathMax(toInteger(this.perPage, 0), 0); // Apply local pagination
|
|
|
|
if (this.localPaging && !!perPage) {
|
|
// Grab the current page of data (which may be past filtered items limit)
|
|
items = items.slice((currentPage - 1) * perPage, currentPage * perPage);
|
|
} // Return the items to display in the table
|
|
|
|
|
|
return items;
|
|
}
|
|
}
|
|
};
|
|
|
|
var providerMixin = {
|
|
mixins: [listenOnRootMixin],
|
|
props: makePropsConfigurable({
|
|
// Prop override(s)
|
|
items: {
|
|
// Adds in 'Function' support
|
|
type: [Array, Function],
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
},
|
|
// Additional props
|
|
noProviderPaging: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noProviderSorting: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noProviderFiltering: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
apiUrl: {
|
|
// Passthrough prop. Passed to the context object. Not used by b-table directly
|
|
type: String,
|
|
default: ''
|
|
}
|
|
}, NAME_TABLE),
|
|
computed: {
|
|
hasProvider: function hasProvider() {
|
|
return isFunction(this.items);
|
|
},
|
|
providerTriggerContext: function providerTriggerContext() {
|
|
// Used to trigger the provider function via a watcher. Only the fields that
|
|
// are needed for triggering a provider update are included. Note that the
|
|
// regular this.context is sent to the provider during fetches though, as they
|
|
// may need all the prop info.
|
|
var ctx = {
|
|
apiUrl: this.apiUrl,
|
|
filter: null,
|
|
sortBy: null,
|
|
sortDesc: null,
|
|
perPage: null,
|
|
currentPage: null
|
|
};
|
|
|
|
if (!this.noProviderFiltering) {
|
|
// Either a string, or could be an object or array.
|
|
ctx.filter = this.localFilter;
|
|
}
|
|
|
|
if (!this.noProviderSorting) {
|
|
ctx.sortBy = this.localSortBy;
|
|
ctx.sortDesc = this.localSortDesc;
|
|
}
|
|
|
|
if (!this.noProviderPaging) {
|
|
ctx.perPage = this.perPage;
|
|
ctx.currentPage = this.currentPage;
|
|
}
|
|
|
|
return clone(ctx);
|
|
}
|
|
},
|
|
watch: {
|
|
// Provider update triggering
|
|
items: function items(newVal) {
|
|
// If a new provider has been specified, trigger an update
|
|
if (this.hasProvider || isFunction(newVal)) {
|
|
this.$nextTick(this._providerUpdate);
|
|
}
|
|
},
|
|
providerTriggerContext: function providerTriggerContext(newVal, oldVal) {
|
|
// Trigger the provider to update as the relevant context values have changed.
|
|
if (!looseEqual(newVal, oldVal)) {
|
|
this.$nextTick(this._providerUpdate);
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// Call the items provider if necessary
|
|
if (this.hasProvider && (!this.localItems || this.localItems.length === 0)) {
|
|
// Fetch on mount if localItems is empty
|
|
this._providerUpdate();
|
|
} // Listen for global messages to tell us to force refresh the table
|
|
|
|
|
|
this.listenOnRoot('bv::refresh::table', function (id) {
|
|
if (id === _this.id || id === _this) {
|
|
_this.refresh();
|
|
}
|
|
});
|
|
},
|
|
methods: {
|
|
refresh: function refresh() {
|
|
// Public Method: Force a refresh of the provider function
|
|
this.$off('refreshed', this.refresh);
|
|
|
|
if (this.computedBusy) {
|
|
// Can't force an update when forced busy by user (busy prop === true)
|
|
if (this.localBusy && this.hasProvider) {
|
|
// But if provider running (localBusy), re-schedule refresh once `refreshed` emitted
|
|
this.$on('refreshed', this.refresh);
|
|
}
|
|
} else {
|
|
this.clearSelected();
|
|
|
|
if (this.hasProvider) {
|
|
this.$nextTick(this._providerUpdate);
|
|
} else {
|
|
/* istanbul ignore next */
|
|
this.localItems = isArray(this.items) ? this.items.slice() : [];
|
|
}
|
|
}
|
|
},
|
|
// Provider related methods
|
|
_providerSetLocal: function _providerSetLocal(items) {
|
|
this.localItems = isArray(items) ? items.slice() : [];
|
|
this.localBusy = false;
|
|
this.$emit('refreshed'); // New root emit
|
|
|
|
if (this.id) {
|
|
this.emitOnRoot('bv::table::refreshed', this.id);
|
|
}
|
|
},
|
|
_providerUpdate: function _providerUpdate() {
|
|
var _this2 = this;
|
|
|
|
// Refresh the provider function items.
|
|
if (!this.hasProvider) {
|
|
// Do nothing if no provider
|
|
return;
|
|
} // If table is busy, wait until refreshed before calling again
|
|
|
|
|
|
if (this.computedBusy) {
|
|
// Schedule a new refresh once `refreshed` is emitted
|
|
this.$nextTick(this.refresh);
|
|
return;
|
|
} // Set internal busy state
|
|
|
|
|
|
this.localBusy = true; // Call provider function with context and optional callback after DOM is fully updated
|
|
|
|
this.$nextTick(function () {
|
|
try {
|
|
// Call provider function passing it the context and optional callback
|
|
var data = _this2.items(_this2.context, _this2._providerSetLocal);
|
|
|
|
if (isPromise(data)) {
|
|
// Provider returned Promise
|
|
data.then(function (items) {
|
|
// Provider resolved with items
|
|
_this2._providerSetLocal(items);
|
|
});
|
|
} else if (isArray(data)) {
|
|
// Provider returned Array data
|
|
_this2._providerSetLocal(data);
|
|
} else {
|
|
/* istanbul ignore if */
|
|
if (_this2.items.length !== 2) {
|
|
// Check number of arguments provider function requested
|
|
// Provider not using callback (didn't request second argument), so we clear
|
|
// busy state as most likely there was an error in the provider function
|
|
|
|
/* istanbul ignore next */
|
|
warn("Provider function didn't request callback and did not return a promise or data.", NAME_TABLE);
|
|
_this2.localBusy = false;
|
|
}
|
|
}
|
|
} catch (e)
|
|
/* istanbul ignore next */
|
|
{
|
|
// Provider function borked on us, so we spew out a warning
|
|
// and clear the busy state
|
|
warn("Provider function error [".concat(e.name, "] ").concat(e.message, "."), NAME_TABLE);
|
|
_this2.localBusy = false;
|
|
|
|
_this2.$off('refreshed', _this2.refresh);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
var SELECT_MODES = ['range', 'multi', 'single'];
|
|
var selectableMixin = {
|
|
props: makePropsConfigurable({
|
|
selectable: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
selectMode: {
|
|
type: String,
|
|
default: 'multi',
|
|
validator: function validator(value) {
|
|
return arrayIncludes(SELECT_MODES, value);
|
|
}
|
|
},
|
|
selectedVariant: {
|
|
type: String,
|
|
default: 'active'
|
|
},
|
|
noSelectOnClick: {
|
|
// Disable use of click handlers for row selection
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TABLE),
|
|
data: function data() {
|
|
return {
|
|
selectedRows: [],
|
|
selectedLastRow: -1
|
|
};
|
|
},
|
|
computed: {
|
|
isSelectable: function isSelectable() {
|
|
return this.selectable && this.selectMode;
|
|
},
|
|
hasSelectableRowClick: function hasSelectableRowClick() {
|
|
return this.isSelectable && !this.noSelectOnClick;
|
|
},
|
|
supportsSelectableRows: function supportsSelectableRows() {
|
|
return true;
|
|
},
|
|
selectableHasSelection: function selectableHasSelection() {
|
|
return this.isSelectable && this.selectedRows && this.selectedRows.length > 0 && this.selectedRows.some(identity);
|
|
},
|
|
selectableIsMultiSelect: function selectableIsMultiSelect() {
|
|
return this.isSelectable && arrayIncludes(['range', 'multi'], this.selectMode);
|
|
},
|
|
selectableTableClasses: function selectableTableClasses() {
|
|
var _ref;
|
|
|
|
return _ref = {
|
|
'b-table-selectable': this.isSelectable
|
|
}, _defineProperty(_ref, "b-table-select-".concat(this.selectMode), this.isSelectable), _defineProperty(_ref, 'b-table-selecting', this.selectableHasSelection), _defineProperty(_ref, 'b-table-selectable-no-click', this.isSelectable && !this.hasSelectableRowClick), _ref;
|
|
},
|
|
selectableTableAttrs: function selectableTableAttrs() {
|
|
return {
|
|
// TODO:
|
|
// Should this attribute not be included when no-select-on-click is set
|
|
// since this attribute implies keyboard navigation?
|
|
'aria-multiselectable': !this.isSelectable ? null : this.selectableIsMultiSelect ? 'true' : 'false'
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
computedItems: function computedItems(newVal, oldVal) {
|
|
// Reset for selectable
|
|
var equal = false;
|
|
|
|
if (this.isSelectable && this.selectedRows.length > 0) {
|
|
// Quick check against array length
|
|
equal = isArray(newVal) && isArray(oldVal) && newVal.length === oldVal.length;
|
|
|
|
for (var i = 0; equal && i < newVal.length; i++) {
|
|
// Look for the first non-loosely equal row, after ignoring reserved fields
|
|
equal = looseEqual(sanitizeRow(newVal[i]), sanitizeRow(oldVal[i]));
|
|
}
|
|
}
|
|
|
|
if (!equal) {
|
|
this.clearSelected();
|
|
}
|
|
},
|
|
selectable: function selectable(newVal) {
|
|
this.clearSelected();
|
|
this.setSelectionHandlers(newVal);
|
|
},
|
|
selectMode: function selectMode() {
|
|
this.clearSelected();
|
|
},
|
|
hasSelectableRowClick: function hasSelectableRowClick(newVal) {
|
|
this.clearSelected();
|
|
this.setSelectionHandlers(!newVal);
|
|
},
|
|
selectedRows: function selectedRows(_selectedRows, oldVal) {
|
|
var _this = this;
|
|
|
|
if (this.isSelectable && !looseEqual(_selectedRows, oldVal)) {
|
|
var items = []; // `.forEach()` skips over non-existent indices (on sparse arrays)
|
|
|
|
_selectedRows.forEach(function (v, idx) {
|
|
if (v) {
|
|
items.push(_this.computedItems[idx]);
|
|
}
|
|
});
|
|
|
|
this.$emit('row-selected', items);
|
|
}
|
|
}
|
|
},
|
|
beforeMount: function beforeMount() {
|
|
// Set up handlers if needed
|
|
if (this.isSelectable) {
|
|
this.setSelectionHandlers(true);
|
|
}
|
|
},
|
|
methods: {
|
|
// Public methods
|
|
selectRow: function selectRow(index) {
|
|
// Select a particular row (indexed based on computedItems)
|
|
if (this.isSelectable && isNumber(index) && index >= 0 && index < this.computedItems.length && !this.isRowSelected(index)) {
|
|
var selectedRows = this.selectableIsMultiSelect ? this.selectedRows.slice() : [];
|
|
selectedRows[index] = true;
|
|
this.selectedLastClicked = -1;
|
|
this.selectedRows = selectedRows;
|
|
}
|
|
},
|
|
unselectRow: function unselectRow(index) {
|
|
// Un-select a particular row (indexed based on `computedItems`)
|
|
if (this.isSelectable && isNumber(index) && this.isRowSelected(index)) {
|
|
var selectedRows = this.selectedRows.slice();
|
|
selectedRows[index] = false;
|
|
this.selectedLastClicked = -1;
|
|
this.selectedRows = selectedRows;
|
|
}
|
|
},
|
|
selectAllRows: function selectAllRows() {
|
|
var length = this.computedItems.length;
|
|
|
|
if (this.isSelectable && length > 0) {
|
|
this.selectedLastClicked = -1;
|
|
this.selectedRows = this.selectableIsMultiSelect ? range(length).map(function () {
|
|
return true;
|
|
}) : [true];
|
|
}
|
|
},
|
|
isRowSelected: function isRowSelected(index) {
|
|
// Determine if a row is selected (indexed based on `computedItems`)
|
|
return !!(isNumber(index) && this.selectedRows[index]);
|
|
},
|
|
clearSelected: function clearSelected() {
|
|
// Clear any active selected row(s)
|
|
this.selectedLastClicked = -1;
|
|
this.selectedRows = [];
|
|
},
|
|
// Internal private methods
|
|
selectableRowClasses: function selectableRowClasses(index) {
|
|
if (this.isSelectable && this.isRowSelected(index)) {
|
|
var variant = this.selectedVariant;
|
|
return _defineProperty({
|
|
'b-table-row-selected': true
|
|
}, "".concat(this.dark ? 'bg' : 'table', "-").concat(variant), variant);
|
|
} else {
|
|
return {};
|
|
}
|
|
},
|
|
selectableRowAttrs: function selectableRowAttrs(index) {
|
|
return {
|
|
'aria-selected': !this.isSelectable ? null : this.isRowSelected(index) ? 'true' : 'false'
|
|
};
|
|
},
|
|
setSelectionHandlers: function setSelectionHandlers(on) {
|
|
var method = on && !this.noSelectOnClick ? '$on' : '$off'; // Handle row-clicked event
|
|
|
|
this[method]('row-clicked', this.selectionHandler); // Clear selection on filter, pagination, and sort changes
|
|
|
|
this[method]('filtered', this.clearSelected);
|
|
this[method]('context-changed', this.clearSelected);
|
|
},
|
|
selectionHandler: function selectionHandler(item, index, evt) {
|
|
/* istanbul ignore if: should never happen */
|
|
if (!this.isSelectable || this.noSelectOnClick) {
|
|
// Don't do anything if table is not in selectable mode
|
|
this.clearSelected();
|
|
return;
|
|
}
|
|
|
|
var selectMode = this.selectMode;
|
|
var selectedRows = this.selectedRows.slice();
|
|
var selected = !selectedRows[index]; // Note 'multi' mode needs no special event handling
|
|
|
|
if (selectMode === 'single') {
|
|
selectedRows = [];
|
|
} else if (selectMode === 'range') {
|
|
if (this.selectedLastRow > -1 && evt.shiftKey) {
|
|
// range
|
|
for (var idx = mathMin(this.selectedLastRow, index); idx <= mathMax(this.selectedLastRow, index); idx++) {
|
|
selectedRows[idx] = true;
|
|
}
|
|
|
|
selected = true;
|
|
} else {
|
|
if (!(evt.ctrlKey || evt.metaKey)) {
|
|
// Clear range selection if any
|
|
selectedRows = [];
|
|
selected = true;
|
|
}
|
|
|
|
this.selectedLastRow = selected ? index : -1;
|
|
}
|
|
}
|
|
|
|
selectedRows[index] = selected;
|
|
this.selectedRows = selectedRows;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Consistent and stable sort function across JavaScript platforms
|
|
*
|
|
* Inconsistent sorts can cause SSR problems between client and server
|
|
* such as in <b-table> if sortBy is applied to the data on server side render.
|
|
* Chrome and V8 native sorts are inconsistent/unstable
|
|
*
|
|
* This function uses native sort with fallback to index compare when the a and b
|
|
* compare returns 0
|
|
*
|
|
* Algorithm based on:
|
|
* https://stackoverflow.com/questions/1427608/fast-stable-sorting-algorithm-implementation-in-javascript/45422645#45422645
|
|
*
|
|
* @param {array} array to sort
|
|
* @param {function} sort compare function
|
|
* @return {array}
|
|
*/
|
|
var stableSort = function stableSort(array, compareFn) {
|
|
// Using `.bind(compareFn)` on the wrapped anonymous function improves
|
|
// performance by avoiding the function call setup. We don't use an arrow
|
|
// function here as it binds `this` to the `stableSort` context rather than
|
|
// the `compareFn` context, which wouldn't give us the performance increase.
|
|
return array.map(function (a, index) {
|
|
return [index, a];
|
|
}).sort(function (a, b) {
|
|
return this(a[1], b[1]) || a[0] - b[0];
|
|
}.bind(compareFn)).map(function (e) {
|
|
return e[1];
|
|
});
|
|
};
|
|
|
|
//
|
|
// TODO: Add option to sort by multiple columns (tri-state per column,
|
|
// plus order of columns in sort) where sortBy could be an array
|
|
// of objects `[ {key: 'foo', sortDir: 'asc'}, {key:'bar', sortDir: 'desc'} ...]`
|
|
// or an array of arrays `[ ['foo','asc'], ['bar','desc'] ]`
|
|
// Multisort will most likely be handled in mixin-sort.js by
|
|
// calling this method for each sortBy
|
|
|
|
var defaultSortCompare = function defaultSortCompare(a, b, sortBy, sortDesc, formatter, localeOpts, locale, nullLast) {
|
|
var aa = get(a, sortBy, null);
|
|
var bb = get(b, sortBy, null);
|
|
|
|
if (isFunction(formatter)) {
|
|
aa = formatter(aa, sortBy, a);
|
|
bb = formatter(bb, sortBy, b);
|
|
}
|
|
|
|
aa = isUndefinedOrNull(aa) ? '' : aa;
|
|
bb = isUndefinedOrNull(bb) ? '' : bb;
|
|
|
|
if (isDate(aa) && isDate(bb) || isNumber(aa) && isNumber(bb)) {
|
|
// Special case for comparing dates and numbers
|
|
// Internally dates are compared via their epoch number values
|
|
return aa < bb ? -1 : aa > bb ? 1 : 0;
|
|
} else if (nullLast && aa === '' && bb !== '') {
|
|
// Special case when sorting null/undefined/empty string last
|
|
return 1;
|
|
} else if (nullLast && aa !== '' && bb === '') {
|
|
// Special case when sorting null/undefined/empty string last
|
|
return -1;
|
|
} // Do localized string comparison
|
|
|
|
|
|
return stringifyObjectValues(aa).localeCompare(stringifyObjectValues(bb), locale, localeOpts);
|
|
};
|
|
|
|
var SORT_DIRECTIONS = ['asc', 'desc', 'last'];
|
|
var sortingMixin = {
|
|
props: makePropsConfigurable({
|
|
sortBy: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
sortDesc: {
|
|
// TODO: Make this tri-state: true, false, null
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
sortDirection: {
|
|
// This prop is named incorrectly
|
|
// It should be `initialSortDirection` as it is a bit misleading
|
|
// (not to mention it screws up the ARIA label on the headers)
|
|
type: String,
|
|
default: 'asc',
|
|
validator: function validator(value) {
|
|
return arrayIncludes(SORT_DIRECTIONS, value);
|
|
}
|
|
},
|
|
sortCompare: {
|
|
type: Function // default: null
|
|
|
|
},
|
|
sortCompareOptions: {
|
|
// Supported localCompare options, see `options` section of:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
|
|
type: Object,
|
|
default: function _default() {
|
|
return {
|
|
numeric: true
|
|
};
|
|
}
|
|
},
|
|
sortCompareLocale: {
|
|
// String: locale code
|
|
// Array: array of Locale strings
|
|
type: [String, Array] // default: undefined
|
|
|
|
},
|
|
sortNullLast: {
|
|
// Sort null and undefined to appear last
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noSortReset: {
|
|
// Another prop that should have had a better name.
|
|
// It should be noSortClear (on non-sortable headers).
|
|
// We will need to make sure the documentation is clear on what
|
|
// this prop does (as well as in the code for future reference)
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
labelSortAsc: {
|
|
type: String,
|
|
default: 'Click to sort Ascending'
|
|
},
|
|
labelSortDesc: {
|
|
type: String,
|
|
default: 'Click to sort Descending'
|
|
},
|
|
labelSortClear: {
|
|
type: String,
|
|
default: 'Click to clear sorting'
|
|
},
|
|
noLocalSorting: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noFooterSorting: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
sortIconLeft: {
|
|
// Place the sorting icon on the left of the header cells
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TABLE),
|
|
data: function data() {
|
|
return {
|
|
localSortBy: this.sortBy || '',
|
|
localSortDesc: this.sortDesc || false
|
|
};
|
|
},
|
|
computed: {
|
|
localSorting: function localSorting() {
|
|
return this.hasProvider ? !!this.noProviderSorting : !this.noLocalSorting;
|
|
},
|
|
isSortable: function isSortable() {
|
|
return this.computedFields.some(function (f) {
|
|
return f.sortable;
|
|
});
|
|
},
|
|
sortedItems: function sortedItems() {
|
|
// Sorts the filtered items and returns a new array of the sorted items
|
|
// or the original items array if not sorted.
|
|
var items = (this.filteredItems || this.localItems || []).slice();
|
|
var sortBy = this.localSortBy;
|
|
var sortDesc = this.localSortDesc;
|
|
var sortCompare = this.sortCompare;
|
|
var localSorting = this.localSorting;
|
|
|
|
var sortOptions = _objectSpread2(_objectSpread2({}, this.sortCompareOptions), {}, {
|
|
usage: 'sort'
|
|
});
|
|
|
|
var sortLocale = this.sortCompareLocale || undefined;
|
|
var nullLast = this.sortNullLast;
|
|
|
|
if (sortBy && localSorting) {
|
|
var field = this.computedFieldsObj[sortBy] || {};
|
|
var sortByFormatted = field.sortByFormatted;
|
|
var formatter = isFunction(sortByFormatted) ?
|
|
/* istanbul ignore next */
|
|
sortByFormatted : sortByFormatted ? this.getFieldFormatter(sortBy) : undefined; // `stableSort` returns a new array, and leaves the original array intact
|
|
|
|
return stableSort(items, function (a, b) {
|
|
var result = null;
|
|
|
|
if (isFunction(sortCompare)) {
|
|
// Call user provided sortCompare routine
|
|
result = sortCompare(a, b, sortBy, sortDesc, formatter, sortOptions, sortLocale);
|
|
}
|
|
|
|
if (isUndefinedOrNull(result) || result === false) {
|
|
// Fallback to built-in defaultSortCompare if sortCompare
|
|
// is not defined or returns null/false
|
|
result = defaultSortCompare(a, b, sortBy, sortDesc, formatter, sortOptions, sortLocale, nullLast);
|
|
} // Negate result if sorting in descending order
|
|
|
|
|
|
return (result || 0) * (sortDesc ? -1 : 1);
|
|
});
|
|
}
|
|
|
|
return items;
|
|
}
|
|
},
|
|
watch: {
|
|
/* istanbul ignore next: pain in the butt to test */
|
|
isSortable: function isSortable(newVal) {
|
|
if (newVal) {
|
|
if (this.isSortable) {
|
|
this.$on('head-clicked', this.handleSort);
|
|
}
|
|
} else {
|
|
this.$off('head-clicked', this.handleSort);
|
|
}
|
|
},
|
|
sortDesc: function sortDesc(newVal) {
|
|
if (newVal === this.localSortDesc) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
this.localSortDesc = newVal || false;
|
|
},
|
|
sortBy: function sortBy(newVal) {
|
|
if (newVal === this.localSortBy) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
this.localSortBy = newVal || '';
|
|
},
|
|
// Update .sync props
|
|
localSortDesc: function localSortDesc(newVal, oldVal) {
|
|
// Emit update to sort-desc.sync
|
|
if (newVal !== oldVal) {
|
|
this.$emit('update:sortDesc', newVal);
|
|
}
|
|
},
|
|
localSortBy: function localSortBy(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
this.$emit('update:sortBy', newVal);
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
if (this.isSortable) {
|
|
this.$on('head-clicked', this.handleSort);
|
|
}
|
|
},
|
|
methods: {
|
|
// Handlers
|
|
// Need to move from thead-mixin
|
|
handleSort: function handleSort(key, field, evt, isFoot) {
|
|
var _this = this;
|
|
|
|
if (!this.isSortable) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
if (isFoot && this.noFooterSorting) {
|
|
return;
|
|
} // TODO: make this tri-state sorting
|
|
// cycle desc => asc => none => desc => ...
|
|
|
|
|
|
var sortChanged = false;
|
|
|
|
var toggleLocalSortDesc = function toggleLocalSortDesc() {
|
|
var sortDirection = field.sortDirection || _this.sortDirection;
|
|
|
|
if (sortDirection === 'asc') {
|
|
_this.localSortDesc = false;
|
|
} else if (sortDirection === 'desc') {
|
|
_this.localSortDesc = true;
|
|
} else ;
|
|
};
|
|
|
|
if (field.sortable) {
|
|
var sortKey = !this.localSorting && field.sortKey ? field.sortKey : key;
|
|
|
|
if (this.localSortBy === sortKey) {
|
|
// Change sorting direction on current column
|
|
this.localSortDesc = !this.localSortDesc;
|
|
} else {
|
|
// Start sorting this column ascending
|
|
this.localSortBy = sortKey; // this.localSortDesc = false
|
|
|
|
toggleLocalSortDesc();
|
|
}
|
|
|
|
sortChanged = true;
|
|
} else if (this.localSortBy && !this.noSortReset) {
|
|
this.localSortBy = '';
|
|
toggleLocalSortDesc();
|
|
sortChanged = true;
|
|
}
|
|
|
|
if (sortChanged) {
|
|
// Sorting parameters changed
|
|
this.$emit('sort-changed', this.context);
|
|
}
|
|
},
|
|
// methods to compute classes and attrs for thead>th cells
|
|
sortTheadThClasses: function sortTheadThClasses(key, field, isFoot) {
|
|
return {
|
|
// If sortable and sortIconLeft are true, then place sort icon on the left
|
|
'b-table-sort-icon-left': field.sortable && this.sortIconLeft && !(isFoot && this.noFooterSorting)
|
|
};
|
|
},
|
|
sortTheadThAttrs: function sortTheadThAttrs(key, field, isFoot) {
|
|
if (!this.isSortable || isFoot && this.noFooterSorting) {
|
|
// No attributes if not a sortable table
|
|
return {};
|
|
}
|
|
|
|
var sortable = field.sortable; // Assemble the aria-sort attribute value
|
|
|
|
var ariaSort = sortable && this.localSortBy === key ? this.localSortDesc ? 'descending' : 'ascending' : sortable ? 'none' : null; // Return the attribute
|
|
|
|
return {
|
|
'aria-sort': ariaSort
|
|
};
|
|
},
|
|
sortTheadThLabel: function sortTheadThLabel(key, field, isFoot) {
|
|
// A label to be placed in an `.sr-only` element in the header cell
|
|
if (!this.isSortable || isFoot && this.noFooterSorting) {
|
|
// No label if not a sortable table
|
|
return null;
|
|
}
|
|
|
|
var sortable = field.sortable; // The correctness of these labels is very important for screen-reader users.
|
|
|
|
var labelSorting = '';
|
|
|
|
if (sortable) {
|
|
if (this.localSortBy === key) {
|
|
// currently sorted sortable column.
|
|
labelSorting = this.localSortDesc ? this.labelSortAsc : this.labelSortDesc;
|
|
} else {
|
|
// Not currently sorted sortable column.
|
|
// Not using nested ternary's here for clarity/readability
|
|
// Default for ariaLabel
|
|
labelSorting = this.localSortDesc ? this.labelSortDesc : this.labelSortAsc; // Handle sortDirection setting
|
|
|
|
var sortDirection = this.sortDirection || field.sortDirection;
|
|
|
|
if (sortDirection === 'asc') {
|
|
labelSorting = this.labelSortAsc;
|
|
} else if (sortDirection === 'desc') {
|
|
labelSorting = this.labelSortDesc;
|
|
}
|
|
}
|
|
} else if (!this.noSortReset) {
|
|
// Non sortable column
|
|
labelSorting = this.localSortBy ? this.labelSortClear : '';
|
|
} // Return the sr-only sort label or null if no label
|
|
|
|
|
|
return trim(labelSorting) || null;
|
|
}
|
|
}
|
|
};
|
|
|
|
var stackedMixin = {
|
|
props: makePropsConfigurable({
|
|
stacked: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
}
|
|
}, NAME_TABLE),
|
|
computed: {
|
|
isStacked: function isStacked() {
|
|
// `true` when always stacked, or returns breakpoint specified
|
|
return this.stacked === '' ? true : this.stacked;
|
|
},
|
|
isStackedAlways: function isStackedAlways() {
|
|
return this.isStacked === true;
|
|
},
|
|
stackedTableClasses: function stackedTableClasses() {
|
|
return _defineProperty({
|
|
'b-table-stacked': this.isStackedAlways
|
|
}, "b-table-stacked-".concat(this.stacked), !this.isStackedAlways && this.isStacked);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Includes all main table styling options
|
|
|
|
var tableRendererMixin = {
|
|
// Don't place attributes on root element automatically,
|
|
// as table could be wrapped in responsive `<div>`
|
|
inheritAttrs: false,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTable: this
|
|
};
|
|
},
|
|
props: makePropsConfigurable({
|
|
striped: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
bordered: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
borderless: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
outlined: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dark: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hover: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
small: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
fixed: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
responsive: {
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
stickyHeader: {
|
|
// If a string, it is assumed to be the table `max-height` value
|
|
type: [Boolean, String],
|
|
default: false
|
|
},
|
|
noBorderCollapse: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
captionTop: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tableVariant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
tableClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}, NAME_TABLE),
|
|
computed: {
|
|
// Layout related computed props
|
|
isResponsive: function isResponsive() {
|
|
var responsive = this.responsive === '' ? true : this.responsive;
|
|
return this.isStacked ? false : responsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
var stickyHeader = this.stickyHeader === '' ? true : this.stickyHeader;
|
|
return this.isStacked ? false : stickyHeader;
|
|
},
|
|
wrapperClasses: function wrapperClasses() {
|
|
return [this.isStickyHeader ? 'b-table-sticky-header' : '', this.isResponsive === true ? 'table-responsive' : this.isResponsive ? "table-responsive-".concat(this.responsive) : ''].filter(identity);
|
|
},
|
|
wrapperStyles: function wrapperStyles() {
|
|
return this.isStickyHeader && !isBoolean(this.isStickyHeader) ? {
|
|
maxHeight: this.isStickyHeader
|
|
} : {};
|
|
},
|
|
tableClasses: function tableClasses() {
|
|
var hover = this.isTableSimple ? this.hover : this.hover && this.computedItems.length > 0 && !this.computedBusy;
|
|
return [// User supplied classes
|
|
this.tableClass, // Styling classes
|
|
{
|
|
'table-striped': this.striped,
|
|
'table-hover': hover,
|
|
'table-dark': this.dark,
|
|
'table-bordered': this.bordered,
|
|
'table-borderless': this.borderless,
|
|
'table-sm': this.small,
|
|
// The following are b-table custom styles
|
|
border: this.outlined,
|
|
'b-table-fixed': this.fixed,
|
|
'b-table-caption-top': this.captionTop,
|
|
'b-table-no-border-collapse': this.noBorderCollapse
|
|
}, this.tableVariant ? "".concat(this.dark ? 'bg' : 'table', "-").concat(this.tableVariant) : '', // Stacked table classes
|
|
this.stackedTableClasses, // Selectable classes
|
|
this.selectableTableClasses];
|
|
},
|
|
tableAttrs: function tableAttrs() {
|
|
// Preserve user supplied aria-describedby, if provided in `$attrs`
|
|
var adb = [(this.bvAttrs || {})['aria-describedby'], this.captionId].filter(identity).join(' ') || null;
|
|
var items = this.computedItems;
|
|
var filteredItems = this.filteredItems;
|
|
var fields = this.computedFields;
|
|
var selectableAttrs = this.selectableTableAttrs || {};
|
|
var ariaAttrs = this.isTableSimple ? {} : {
|
|
'aria-busy': this.computedBusy ? 'true' : 'false',
|
|
'aria-colcount': toString$1(fields.length),
|
|
'aria-describedby': adb
|
|
};
|
|
var rowCount = items && filteredItems && filteredItems.length > items.length ? toString$1(filteredItems.length) : null;
|
|
return _objectSpread2(_objectSpread2(_objectSpread2({
|
|
// We set `aria-rowcount` before merging in `$attrs`,
|
|
// in case user has supplied their own
|
|
'aria-rowcount': rowCount
|
|
}, this.bvAttrs), {}, {
|
|
// Now we can override any `$attrs` here
|
|
id: this.safeId(),
|
|
role: 'table'
|
|
}, ariaAttrs), selectableAttrs);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $content = [];
|
|
|
|
if (this.isTableSimple) {
|
|
$content.push(this.normalizeSlot());
|
|
} else {
|
|
// Build the `<caption>` (from caption mixin)
|
|
$content.push(this.renderCaption ? this.renderCaption() : null); // Build the `<colgroup>`
|
|
|
|
$content.push(this.renderColgroup ? this.renderColgroup() : null); // Build the `<thead>`
|
|
|
|
$content.push(this.renderThead ? this.renderThead() : null); // Build the `<tbody>`
|
|
|
|
$content.push(this.renderTbody ? this.renderTbody() : null); // Build the `<tfoot>`
|
|
|
|
$content.push(this.renderTfoot ? this.renderTfoot() : null);
|
|
} // Assemble `<table>`
|
|
|
|
|
|
var $table = h('table', {
|
|
key: 'b-table',
|
|
staticClass: 'table b-table',
|
|
class: this.tableClasses,
|
|
attrs: this.tableAttrs
|
|
}, $content.filter(identity)); // Add responsive/sticky wrapper if needed and return table
|
|
|
|
return this.wrapperClasses.length > 0 ? h('div', {
|
|
key: 'wrap',
|
|
class: this.wrapperClasses,
|
|
style: this.wrapperStyles
|
|
}, [$table]) : $table;
|
|
}
|
|
};
|
|
|
|
var props$16 = makePropsConfigurable({
|
|
tbodyTransitionProps: {
|
|
type: Object // default: undefined
|
|
|
|
},
|
|
tbodyTransitionHandlers: {
|
|
type: Object // default: undefined
|
|
|
|
}
|
|
}, NAME_TBODY); // TODO:
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BTbody = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TBODY,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTableRowGroup: this
|
|
};
|
|
},
|
|
inject: {
|
|
bvTable: {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$16,
|
|
computed: {
|
|
isTbody: function isTbody() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return true;
|
|
},
|
|
isDark: function isDark() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.dark;
|
|
},
|
|
isStacked: function isStacked() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isStacked;
|
|
},
|
|
isResponsive: function isResponsive() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isResponsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Sticky headers are only supported in thead
|
|
return false;
|
|
},
|
|
hasStickyHeader: function hasStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
return !this.isStacked && this.bvTable.stickyHeader;
|
|
},
|
|
tableVariant: function tableVariant() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.tableVariant;
|
|
},
|
|
isTransitionGroup: function isTransitionGroup() {
|
|
return this.tbodyTransitionProps || this.tbodyTransitionHandlers;
|
|
},
|
|
tbodyAttrs: function tbodyAttrs() {
|
|
return _objectSpread2({
|
|
role: 'rowgroup'
|
|
}, this.bvAttrs);
|
|
},
|
|
tbodyProps: function tbodyProps() {
|
|
return this.tbodyTransitionProps ? _objectSpread2(_objectSpread2({}, this.tbodyTransitionProps), {}, {
|
|
tag: 'tbody'
|
|
}) : {};
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var data = {
|
|
props: this.tbodyProps,
|
|
attrs: this.tbodyAttrs
|
|
};
|
|
|
|
if (this.isTransitionGroup) {
|
|
// We use native listeners if a transition group for any delegated events
|
|
data.on = this.tbodyTransitionHandlers || {};
|
|
data.nativeOn = this.bvListeners;
|
|
} else {
|
|
// Otherwise we place any listeners on the tbody element
|
|
data.on = this.bvListeners;
|
|
}
|
|
|
|
return h(this.isTransitionGroup ? 'transition-group' : 'tbody', data, this.normalizeSlot());
|
|
}
|
|
});
|
|
|
|
var TABLE_TAG_NAMES = ['TD', 'TH', 'TR']; // Returns `true` if we should ignore the click/double-click/keypress event
|
|
// Avoids having the user need to use `@click.stop` on the form control
|
|
|
|
var filterEvent = function filterEvent(evt) {
|
|
// Exit early when we don't have a target element
|
|
if (!evt || !evt.target) {
|
|
/* istanbul ignore next */
|
|
return false;
|
|
}
|
|
|
|
var el = evt.target; // Exit early when element is disabled or a table element
|
|
|
|
if (el.disabled || TABLE_TAG_NAMES.indexOf(el.tagName) !== -1) {
|
|
return false;
|
|
} // Ignore the click when it was inside a dropdown menu
|
|
|
|
|
|
if (closest('.dropdown-menu', el)) {
|
|
return true;
|
|
}
|
|
|
|
var label = el.tagName === 'LABEL' ? el : closest('label', el); // If the label's form control is not disabled then we don't propagate event
|
|
// Modern browsers have `label.control` that references the associated input, but IE 11
|
|
// does not have this property on the label element, so we resort to DOM lookups
|
|
|
|
if (label) {
|
|
var labelFor = getAttr(label, 'for');
|
|
var input = labelFor ? getById(labelFor) : select('input, select, textarea', label);
|
|
|
|
if (input && !input.disabled) {
|
|
return true;
|
|
}
|
|
} // Otherwise check if the event target matches one of the selectors in the
|
|
// event filter (i.e. anchors, non disabled inputs, etc.)
|
|
// Return `true` if we should ignore the event
|
|
|
|
|
|
return matches(el, EVENT_FILTER);
|
|
};
|
|
|
|
// Used to filter out click events caused by the mouse up at end of selection
|
|
//
|
|
// Accepts an element as only argument to test to see if selection overlaps or is
|
|
// contained within the element
|
|
|
|
var textSelectionActive = function textSelectionActive() {
|
|
var el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
|
|
var sel = getSel();
|
|
return sel && sel.toString().trim() !== '' && sel.containsNode && isElement(el) ?
|
|
/* istanbul ignore next */
|
|
sel.containsNode(el, true) : false;
|
|
};
|
|
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BTh = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TH,
|
|
extends: BTd,
|
|
computed: {
|
|
tag: function tag() {
|
|
return 'th';
|
|
}
|
|
}
|
|
});
|
|
|
|
var detailsSlotName = 'row-details';
|
|
var tbodyRowMixin = {
|
|
props: makePropsConfigurable({
|
|
tbodyTrClass: {
|
|
type: [String, Array, Object, Function] // default: null
|
|
|
|
},
|
|
tbodyTrAttr: {
|
|
type: [Object, Function] // default: null
|
|
|
|
},
|
|
detailsTdClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}, NAME_TABLE),
|
|
methods: {
|
|
// Methods for computing classes, attributes and styles for table cells
|
|
getTdValues: function getTdValues(item, key, tdValue, defValue) {
|
|
var parent = this.$parent;
|
|
|
|
if (tdValue) {
|
|
var value = get(item, key, '');
|
|
|
|
if (isFunction(tdValue)) {
|
|
return tdValue(value, key, item);
|
|
} else if (isString(tdValue) && isFunction(parent[tdValue])) {
|
|
return parent[tdValue](value, key, item);
|
|
}
|
|
|
|
return tdValue;
|
|
}
|
|
|
|
return defValue;
|
|
},
|
|
getThValues: function getThValues(item, key, thValue, type, defValue) {
|
|
var parent = this.$parent;
|
|
|
|
if (thValue) {
|
|
var value = get(item, key, '');
|
|
|
|
if (isFunction(thValue)) {
|
|
return thValue(value, key, item, type);
|
|
} else if (isString(thValue) && isFunction(parent[thValue])) {
|
|
return parent[thValue](value, key, item, type);
|
|
}
|
|
|
|
return thValue;
|
|
}
|
|
|
|
return defValue;
|
|
},
|
|
// Method to get the value for a field
|
|
getFormattedValue: function getFormattedValue(item, field) {
|
|
var key = field.key;
|
|
var formatter = this.getFieldFormatter(key);
|
|
var value = get(item, key, null);
|
|
|
|
if (isFunction(formatter)) {
|
|
value = formatter(value, key, item);
|
|
}
|
|
|
|
return isUndefinedOrNull(value) ? '' : value;
|
|
},
|
|
// Factory function methods
|
|
toggleDetailsFactory: function toggleDetailsFactory(hasDetailsSlot, item) {
|
|
var _this = this;
|
|
|
|
// Returns a function to toggle a row's details slot
|
|
return function () {
|
|
if (hasDetailsSlot) {
|
|
_this.$set(item, '_showDetails', !item._showDetails);
|
|
}
|
|
};
|
|
},
|
|
// Row event handlers
|
|
rowHovered: function rowHovered(evt) {
|
|
// `mouseenter` handler (non-bubbling)
|
|
// `this.tbodyRowEvtStopped` from tbody mixin
|
|
if (!this.tbodyRowEvtStopped(evt)) {
|
|
// `this.emitTbodyRowEvent` from tbody mixin
|
|
this.emitTbodyRowEvent('row-hovered', evt);
|
|
}
|
|
},
|
|
rowUnhovered: function rowUnhovered(evt) {
|
|
// `mouseleave` handler (non-bubbling)
|
|
// `this.tbodyRowEvtStopped` from tbody mixin
|
|
if (!this.tbodyRowEvtStopped(evt)) {
|
|
// `this.emitTbodyRowEvent` from tbody mixin
|
|
this.emitTbodyRowEvent('row-unhovered', evt);
|
|
}
|
|
},
|
|
// Render helpers
|
|
renderTbodyRowCell: function renderTbodyRowCell(field, colIndex, item, rowIndex) {
|
|
var _this2 = this;
|
|
|
|
// Renders a TD or TH for a row's field
|
|
var h = this.$createElement;
|
|
var hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName);
|
|
var formatted = this.getFormattedValue(item, field);
|
|
var key = field.key;
|
|
var stickyColumn = !this.isStacked && (this.isResponsive || this.stickyHeader) && field.stickyColumn; // We only uses the helper components for sticky columns to
|
|
// improve performance of BTable/BTableLite by reducing the
|
|
// total number of vue instances created during render
|
|
|
|
var cellTag = stickyColumn ? field.isRowHeader ? BTh : BTd : field.isRowHeader ? 'th' : 'td';
|
|
var cellVariant = item._cellVariants && item._cellVariants[key] ? item._cellVariants[key] : field.variant || null;
|
|
var data = {
|
|
// For the Vue key, we concatenate the column index and
|
|
// field key (as field keys could be duplicated)
|
|
// TODO: Although we do prevent duplicate field keys...
|
|
// So we could change this to: `row-${rowIndex}-cell-${key}`
|
|
key: "row-".concat(rowIndex, "-cell-").concat(colIndex, "-").concat(key),
|
|
class: [field.class ? field.class : '', this.getTdValues(item, key, field.tdClass, '')],
|
|
props: {},
|
|
attrs: _objectSpread2({
|
|
'aria-colindex': String(colIndex + 1)
|
|
}, field.isRowHeader ? this.getThValues(item, key, field.thAttr, 'row', {}) : this.getTdValues(item, key, field.tdAttr, {}))
|
|
};
|
|
|
|
if (stickyColumn) {
|
|
// We are using the helper BTd or BTh
|
|
data.props = {
|
|
stackedHeading: this.isStacked ? field.label : null,
|
|
stickyColumn: true,
|
|
variant: cellVariant
|
|
};
|
|
} else {
|
|
// Using native TD or TH element, so we need to
|
|
// add in the attributes and variant class
|
|
data.attrs['data-label'] = this.isStacked && !isUndefinedOrNull(field.label) ? toString$1(field.label) : null;
|
|
data.attrs.role = field.isRowHeader ? 'rowheader' : 'cell';
|
|
data.attrs.scope = field.isRowHeader ? 'row' : null; // Add in the variant class
|
|
|
|
if (cellVariant) {
|
|
data.class.push("".concat(this.dark ? 'bg' : 'table', "-").concat(cellVariant));
|
|
}
|
|
}
|
|
|
|
var slotScope = {
|
|
item: item,
|
|
index: rowIndex,
|
|
field: field,
|
|
unformatted: get(item, key, ''),
|
|
value: formatted,
|
|
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item),
|
|
detailsShowing: Boolean(item._showDetails)
|
|
}; // If table supports selectable mode, then add in the following scope
|
|
// this.supportsSelectableRows will be undefined if mixin isn't loaded
|
|
|
|
if (this.supportsSelectableRows) {
|
|
slotScope.rowSelected = this.isRowSelected(rowIndex);
|
|
|
|
slotScope.selectRow = function () {
|
|
return _this2.selectRow(rowIndex);
|
|
};
|
|
|
|
slotScope.unselectRow = function () {
|
|
return _this2.unselectRow(rowIndex);
|
|
};
|
|
} // The new `v-slot` syntax doesn't like a slot name starting with
|
|
// a square bracket and if using in-document HTML templates, the
|
|
// v-slot attributes are lower-cased by the browser.
|
|
// Switched to round bracket syntax to prevent confusion with
|
|
// dynamic slot name syntax.
|
|
// We look for slots in this order: `cell(${key})`, `cell(${key.toLowerCase()})`, 'cell()'
|
|
// Slot names are now cached by mixin tbody in `this.$_bodyFieldSlotNameCache`
|
|
// Will be `null` if no slot (or fallback slot) exists
|
|
|
|
|
|
var slotName = this.$_bodyFieldSlotNameCache[key];
|
|
var $childNodes = slotName ? this.normalizeSlot(slotName, slotScope) : toString$1(formatted);
|
|
|
|
if (this.isStacked) {
|
|
// We wrap in a DIV to ensure rendered as a single cell when visually stacked!
|
|
$childNodes = [h('div', [$childNodes])];
|
|
} // Render either a td or th cell
|
|
|
|
|
|
return h(cellTag, data, [$childNodes]);
|
|
},
|
|
renderTbodyRow: function renderTbodyRow(item, rowIndex) {
|
|
var _this3 = this;
|
|
|
|
// Renders an item's row (or rows if details supported)
|
|
var h = this.$createElement;
|
|
var fields = this.computedFields;
|
|
var tableStriped = this.striped;
|
|
var hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName);
|
|
var rowShowDetails = item._showDetails && hasDetailsSlot;
|
|
var hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick; // We can return more than one TR if rowDetails enabled
|
|
|
|
var $rows = []; // Details ID needed for `aria-details` when details showing
|
|
// We set it to `null` when not showing so that attribute
|
|
// does not appear on the element
|
|
|
|
var detailsId = rowShowDetails ? this.safeId("_details_".concat(rowIndex, "_")) : null; // For each item data field in row
|
|
|
|
var $tds = fields.map(function (field, colIndex) {
|
|
return _this3.renderTbodyRowCell(field, colIndex, item, rowIndex);
|
|
}); // Calculate the row number in the dataset (indexed from 1)
|
|
|
|
var ariaRowIndex = null;
|
|
|
|
if (this.currentPage && this.perPage && this.perPage > 0) {
|
|
ariaRowIndex = String((this.currentPage - 1) * this.perPage + rowIndex + 1);
|
|
} // Create a unique :key to help ensure that sub components are re-rendered rather than
|
|
// re-used, which can cause issues. If a primary key is not provided we use the rendered
|
|
// rows index within the tbody.
|
|
// See: https://github.com/bootstrap-vue/bootstrap-vue/issues/2410
|
|
|
|
|
|
var primaryKey = this.primaryKey;
|
|
var primaryKeyValue = toString$1(get(item, primaryKey)) || null;
|
|
var rowKey = primaryKeyValue || toString$1(rowIndex); // If primary key is provided, use it to generate a unique ID on each tbody > tr
|
|
// In the format of '{tableId}__row_{primaryKeyValue}'
|
|
|
|
var rowId = primaryKeyValue ? this.safeId("_row_".concat(primaryKeyValue)) : null; // Selectable classes and attributes
|
|
|
|
var selectableClasses = this.selectableRowClasses ? this.selectableRowClasses(rowIndex) : {};
|
|
var selectableAttrs = this.selectableRowAttrs ? this.selectableRowAttrs(rowIndex) : {}; // Additional classes and attributes
|
|
|
|
var userTrClasses = isFunction(this.tbodyTrClass) ? this.tbodyTrClass(item, 'row') : this.tbodyTrClass;
|
|
var userTrAttrs = isFunction(this.tbodyTrAttr) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrAttr(item, 'row') : this.tbodyTrAttr; // Add the item row
|
|
|
|
$rows.push(h(BTr, {
|
|
key: "__b-table-row-".concat(rowKey, "__"),
|
|
ref: 'itemRows',
|
|
refInFor: true,
|
|
class: [userTrClasses, selectableClasses, rowShowDetails ? 'b-table-has-details' : ''],
|
|
props: {
|
|
variant: item._rowVariant || null
|
|
},
|
|
attrs: _objectSpread2(_objectSpread2({
|
|
id: rowId
|
|
}, userTrAttrs), {}, {
|
|
// Users cannot override the following attributes
|
|
tabindex: hasRowClickHandler ? '0' : null,
|
|
'data-pk': primaryKeyValue || null,
|
|
'aria-details': detailsId,
|
|
'aria-owns': detailsId,
|
|
'aria-rowindex': ariaRowIndex
|
|
}, selectableAttrs),
|
|
on: {
|
|
// Note: These events are not A11Y friendly!
|
|
mouseenter: this.rowHovered,
|
|
mouseleave: this.rowUnhovered
|
|
}
|
|
}, $tds)); // Row Details slot
|
|
|
|
if (rowShowDetails) {
|
|
var detailsScope = {
|
|
item: item,
|
|
index: rowIndex,
|
|
fields: fields,
|
|
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item)
|
|
}; // If table supports selectable mode, then add in the following scope
|
|
// this.supportsSelectableRows will be undefined if mixin isn't loaded
|
|
|
|
if (this.supportsSelectableRows) {
|
|
detailsScope.rowSelected = this.isRowSelected(rowIndex);
|
|
|
|
detailsScope.selectRow = function () {
|
|
return _this3.selectRow(rowIndex);
|
|
};
|
|
|
|
detailsScope.unselectRow = function () {
|
|
return _this3.unselectRow(rowIndex);
|
|
};
|
|
} // Render the details slot in a TD
|
|
|
|
|
|
var $details = h(BTd, {
|
|
props: {
|
|
colspan: fields.length
|
|
},
|
|
class: this.detailsTdClass
|
|
}, [this.normalizeSlot(detailsSlotName, detailsScope)]); // Add a hidden row to keep table row striping consistent when details showing
|
|
// Only added if the table is striped
|
|
|
|
if (tableStriped) {
|
|
$rows.push( // We don't use `BTr` here as we don't need the extra functionality
|
|
h('tr', {
|
|
key: "__b-table-details-stripe__".concat(rowKey),
|
|
staticClass: 'd-none',
|
|
attrs: {
|
|
'aria-hidden': 'true',
|
|
role: 'presentation'
|
|
}
|
|
}));
|
|
} // Add the actual details row
|
|
|
|
|
|
var userDetailsTrClasses = isFunction(this.tbodyTrClass) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrClass(item, detailsSlotName) : this.tbodyTrClass;
|
|
var userDetailsTrAttrs = isFunction(this.tbodyTrAttr) ?
|
|
/* istanbul ignore next */
|
|
this.tbodyTrAttr(item, detailsSlotName) : this.tbodyTrAttr;
|
|
$rows.push(h(BTr, {
|
|
key: "__b-table-details__".concat(rowKey),
|
|
staticClass: 'b-table-details',
|
|
class: [userDetailsTrClasses],
|
|
props: {
|
|
variant: item._rowVariant || null
|
|
},
|
|
attrs: _objectSpread2(_objectSpread2({}, userDetailsTrAttrs), {}, {
|
|
// Users cannot override the following attributes
|
|
id: detailsId,
|
|
tabindex: '-1'
|
|
})
|
|
}, [$details]));
|
|
} else if (hasDetailsSlot) {
|
|
// Only add the placeholder if a the table has a row-details slot defined (but not shown)
|
|
$rows.push(h());
|
|
|
|
if (tableStriped) {
|
|
// Add extra placeholder if table is striped
|
|
$rows.push(h());
|
|
}
|
|
} // Return the row(s)
|
|
|
|
|
|
return $rows;
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$17 = _objectSpread2(_objectSpread2({}, props$16), {}, {
|
|
tbodyClass: {
|
|
type: [String, Array, Object] // default: undefined
|
|
|
|
}
|
|
});
|
|
|
|
var tbodyMixin = {
|
|
mixins: [tbodyRowMixin],
|
|
props: props$17,
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.$_bodyFieldSlotNameCache = null;
|
|
},
|
|
methods: {
|
|
// Helper methods
|
|
getTbodyTrs: function getTbodyTrs() {
|
|
// Returns all the item TR elements (excludes detail and spacer rows)
|
|
// `this.$refs.itemRows` is an array of item TR components/elements
|
|
// Rows should all be B-TR components, but we map to TR elements
|
|
// Also note that `this.$refs.itemRows` may not always be in document order
|
|
var refs = this.$refs || {};
|
|
var tbody = refs.tbody ? refs.tbody.$el || refs.tbody : null;
|
|
var trs = (refs.itemRows || []).map(function (tr) {
|
|
return tr.$el || tr;
|
|
});
|
|
return tbody && tbody.children && tbody.children.length > 0 && trs && trs.length > 0 ? from(tbody.children).filter(function (tr) {
|
|
return arrayIncludes(trs, tr);
|
|
}) :
|
|
/* istanbul ignore next */
|
|
[];
|
|
},
|
|
getTbodyTrIndex: function getTbodyTrIndex(el) {
|
|
// Returns index of a particular TBODY item TR
|
|
// We set `true` on closest to include self in result
|
|
|
|
/* istanbul ignore next: should not normally happen */
|
|
if (!isElement(el)) {
|
|
return -1;
|
|
}
|
|
|
|
var tr = el.tagName === 'TR' ? el : closest('tr', el, true);
|
|
return tr ? this.getTbodyTrs().indexOf(tr) : -1;
|
|
},
|
|
emitTbodyRowEvent: function emitTbodyRowEvent(type, evt) {
|
|
// Emits a row event, with the item object, row index and original event
|
|
if (type && this.hasListener(type) && evt && evt.target) {
|
|
var rowIndex = this.getTbodyTrIndex(evt.target);
|
|
|
|
if (rowIndex > -1) {
|
|
// The array of TRs correlate to the `computedItems` array
|
|
var item = this.computedItems[rowIndex];
|
|
this.$emit(type, item, rowIndex, evt);
|
|
}
|
|
}
|
|
},
|
|
tbodyRowEvtStopped: function tbodyRowEvtStopped(evt) {
|
|
return this.stopIfBusy && this.stopIfBusy(evt);
|
|
},
|
|
// Delegated row event handlers
|
|
onTbodyRowKeydown: function onTbodyRowKeydown(evt) {
|
|
// Keyboard navigation and row click emulation
|
|
var target = evt.target;
|
|
|
|
if (this.tbodyRowEvtStopped(evt) || target.tagName !== 'TR' || !isActiveElement(target) || target.tabIndex !== 0) {
|
|
// Early exit if not an item row TR
|
|
return;
|
|
}
|
|
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (arrayIncludes([CODE_ENTER, CODE_SPACE], keyCode)) {
|
|
// Emulated click for keyboard users, transfer to click handler
|
|
stopEvent(evt);
|
|
this.onTBodyRowClicked(evt);
|
|
} else if (arrayIncludes([CODE_UP, CODE_DOWN, CODE_HOME, CODE_END], keyCode)) {
|
|
// Keyboard navigation
|
|
var rowIndex = this.getTbodyTrIndex(target);
|
|
|
|
if (rowIndex > -1) {
|
|
stopEvent(evt);
|
|
var trs = this.getTbodyTrs();
|
|
var shift = evt.shiftKey;
|
|
|
|
if (keyCode === CODE_HOME || shift && keyCode === CODE_UP) {
|
|
// Focus first row
|
|
attemptFocus(trs[0]);
|
|
} else if (keyCode === CODE_END || shift && keyCode === CODE_DOWN) {
|
|
// Focus last row
|
|
attemptFocus(trs[trs.length - 1]);
|
|
} else if (keyCode === CODE_UP && rowIndex > 0) {
|
|
// Focus previous row
|
|
attemptFocus(trs[rowIndex - 1]);
|
|
} else if (keyCode === CODE_DOWN && rowIndex < trs.length - 1) {
|
|
// Focus next row
|
|
attemptFocus(trs[rowIndex + 1]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
onTBodyRowClicked: function onTBodyRowClicked(evt) {
|
|
if (this.tbodyRowEvtStopped(evt)) {
|
|
// If table is busy, then don't propagate
|
|
return;
|
|
} else if (filterEvent(evt) || textSelectionActive(this.$el)) {
|
|
// Clicked on a non-disabled control so ignore
|
|
// Or user is selecting text, so ignore
|
|
return;
|
|
}
|
|
|
|
this.emitTbodyRowEvent('row-clicked', evt);
|
|
},
|
|
onTbodyRowMiddleMouseRowClicked: function onTbodyRowMiddleMouseRowClicked(evt) {
|
|
if (!this.tbodyRowEvtStopped(evt) && evt.which === 2) {
|
|
this.emitTbodyRowEvent('row-middle-clicked', evt);
|
|
}
|
|
},
|
|
onTbodyRowContextmenu: function onTbodyRowContextmenu(evt) {
|
|
if (!this.tbodyRowEvtStopped(evt)) {
|
|
this.emitTbodyRowEvent('row-contextmenu', evt);
|
|
}
|
|
},
|
|
onTbodyRowDblClicked: function onTbodyRowDblClicked(evt) {
|
|
if (!this.tbodyRowEvtStopped(evt) && !filterEvent(evt)) {
|
|
this.emitTbodyRowEvent('row-dblclicked', evt);
|
|
}
|
|
},
|
|
// Note: Row hover handlers are handled by the tbody-row mixin
|
|
// As mouseenter/mouseleave events do not bubble
|
|
//
|
|
// Render Helper
|
|
renderTbody: function renderTbody() {
|
|
var _this = this;
|
|
|
|
// Render the tbody element and children
|
|
var items = this.computedItems; // Shortcut to `createElement` (could use `this._c()` instead)
|
|
|
|
var h = this.$createElement;
|
|
var hasRowClickHandler = this.hasListener('row-clicked') || this.hasSelectableRowClick; // Prepare the tbody rows
|
|
|
|
var $rows = []; // Add the item data rows or the busy slot
|
|
|
|
var $busy = this.renderBusy ? this.renderBusy() : null;
|
|
|
|
if ($busy) {
|
|
// If table is busy and a busy slot, then return only the busy "row" indicator
|
|
$rows.push($busy);
|
|
} else {
|
|
// Table isn't busy, or we don't have a busy slot
|
|
// Create a slot cache for improved performance when looking up cell slot names
|
|
// Values will be keyed by the field's `key` and will store the slot's name
|
|
// Slots could be dynamic (i.e. `v-if`), so we must compute on each render
|
|
// Used by tbody-row mixin render helper
|
|
var cache = {};
|
|
var defaultSlotName = this.hasNormalizedSlot('cell()') ? 'cell()' : null;
|
|
this.computedFields.forEach(function (field) {
|
|
var key = field.key;
|
|
var fullName = "cell(".concat(key, ")");
|
|
var lowerName = "cell(".concat(key.toLowerCase(), ")");
|
|
cache[key] = _this.hasNormalizedSlot(fullName) ? fullName : _this.hasNormalizedSlot(lowerName) ?
|
|
/* istanbul ignore next */
|
|
lowerName : defaultSlotName;
|
|
}); // Created as a non-reactive property so to not trigger component updates
|
|
// Must be a fresh object each render
|
|
|
|
this.$_bodyFieldSlotNameCache = cache; // Add static top row slot (hidden in visibly stacked mode
|
|
// as we can't control `data-label` attr)
|
|
|
|
$rows.push(this.renderTopRow ? this.renderTopRow() : h()); // Render the rows
|
|
|
|
items.forEach(function (item, rowIndex) {
|
|
// Render the individual item row (rows if details slot)
|
|
$rows.push(_this.renderTbodyRow(item, rowIndex));
|
|
}); // Empty items / empty filtered row slot (only shows if `items.length < 1`)
|
|
|
|
$rows.push(this.renderEmpty ? this.renderEmpty() : h()); // Static bottom row slot (hidden in visibly stacked mode
|
|
// as we can't control `data-label` attr)
|
|
|
|
$rows.push(this.renderBottomRow ? this.renderBottomRow() : h());
|
|
} // Note: these events will only emit if a listener is registered
|
|
|
|
|
|
var handlers = {
|
|
auxclick: this.onTbodyRowMiddleMouseRowClicked,
|
|
// TODO:
|
|
// Perhaps we do want to automatically prevent the
|
|
// default context menu from showing if there is a
|
|
// `row-contextmenu` listener registered
|
|
contextmenu: this.onTbodyRowContextmenu,
|
|
// The following event(s) is not considered A11Y friendly
|
|
dblclick: this.onTbodyRowDblClicked // Hover events (`mouseenter`/`mouseleave`) are handled by `tbody-row` mixin
|
|
|
|
}; // Add in click/keydown listeners if needed
|
|
|
|
if (hasRowClickHandler) {
|
|
handlers.click = this.onTBodyRowClicked;
|
|
handlers.keydown = this.onTbodyRowKeydown;
|
|
} // Assemble rows into the tbody
|
|
|
|
|
|
var $tbody = h(BTbody, {
|
|
ref: 'tbody',
|
|
class: this.tbodyClass || null,
|
|
props: {
|
|
tbodyTransitionProps: this.tbodyTransitionProps,
|
|
tbodyTransitionHandlers: this.tbodyTransitionHandlers
|
|
},
|
|
// BTbody transfers all native event listeners to the root element
|
|
// TODO: Only set the handlers if the table is not busy
|
|
on: handlers
|
|
}, $rows); // Return the assembled tbody
|
|
|
|
return $tbody;
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$18 = makePropsConfigurable({
|
|
footVariant: {
|
|
type: String,
|
|
// Supported values: 'lite', 'dark', or null
|
|
default: null
|
|
}
|
|
}, NAME_TFOOT); // TODO:
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BTfoot = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TFOOT,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTableRowGroup: this
|
|
};
|
|
},
|
|
inject: {
|
|
bvTable: {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$18,
|
|
computed: {
|
|
isTfoot: function isTfoot() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return true;
|
|
},
|
|
isDark: function isDark() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.dark;
|
|
},
|
|
isStacked: function isStacked() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isStacked;
|
|
},
|
|
isResponsive: function isResponsive() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isResponsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Sticky headers are only supported in thead
|
|
return false;
|
|
},
|
|
hasStickyHeader: function hasStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
return !this.isStacked && this.bvTable.stickyHeader;
|
|
},
|
|
tableVariant: function tableVariant() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.tableVariant;
|
|
},
|
|
tfootClasses: function tfootClasses() {
|
|
return [this.footVariant ? "thead-".concat(this.footVariant) : null];
|
|
},
|
|
tfootAttrs: function tfootAttrs() {
|
|
return _objectSpread2({
|
|
role: 'rowgroup'
|
|
}, this.bvAttrs);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('tfoot', {
|
|
class: this.tfootClasses,
|
|
attrs: this.tfootAttrs,
|
|
// Pass down any native listeners
|
|
on: this.bvListeners
|
|
}, this.normalizeSlot());
|
|
}
|
|
});
|
|
|
|
var tfootMixin = {
|
|
props: makePropsConfigurable({
|
|
footClone: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
footVariant: {
|
|
type: String // 'dark', 'light', or `null` (or custom)
|
|
// default: null
|
|
|
|
},
|
|
footRowVariant: {
|
|
// Any Bootstrap theme variant (or custom). Falls back to `headRowVariant`
|
|
type: String // default: null
|
|
|
|
},
|
|
tfootClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
tfootTrClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
}
|
|
}, NAME_TABLE),
|
|
methods: {
|
|
renderTFootCustom: function renderTFootCustom() {
|
|
var h = this.$createElement;
|
|
|
|
if (this.hasNormalizedSlot('custom-foot')) {
|
|
return h(BTfoot, {
|
|
key: 'bv-tfoot-custom',
|
|
class: this.tfootClass || null,
|
|
props: {
|
|
footVariant: this.footVariant || this.headVariant || null
|
|
}
|
|
}, this.normalizeSlot('custom-foot', {
|
|
items: this.computedItems.slice(),
|
|
fields: this.computedFields.slice(),
|
|
columns: this.computedFields.length
|
|
}));
|
|
} else {
|
|
return h();
|
|
}
|
|
},
|
|
renderTfoot: function renderTfoot() {
|
|
// Passing true to renderThead will make it render a tfoot
|
|
return this.footClone ? this.renderThead(true) : this.renderTFootCustom();
|
|
}
|
|
}
|
|
};
|
|
|
|
var props$19 = makePropsConfigurable({
|
|
headVariant: {
|
|
// Also sniffed by <b-tr> / <b-td> / <b-th>
|
|
type: String,
|
|
// Supported values: 'lite', 'dark', or null
|
|
default: null
|
|
}
|
|
}, NAME_THEAD); // TODO:
|
|
// In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit
|
|
// to the child elements, so this can be converted to a functional component
|
|
// @vue/component
|
|
|
|
var BThead = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_THEAD,
|
|
// Mixin order is important!
|
|
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTableRowGroup: this
|
|
};
|
|
},
|
|
inject: {
|
|
bvTable: {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
inheritAttrs: false,
|
|
props: props$19,
|
|
computed: {
|
|
isThead: function isThead() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return true;
|
|
},
|
|
isDark: function isDark() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.dark;
|
|
},
|
|
isStacked: function isStacked() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isStacked;
|
|
},
|
|
isResponsive: function isResponsive() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.isResponsive;
|
|
},
|
|
isStickyHeader: function isStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
// Sticky headers only apply to cells in table `thead`
|
|
return !this.isStacked && this.bvTable.stickyHeader;
|
|
},
|
|
hasStickyHeader: function hasStickyHeader() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
// Needed to handle header background classes, due to lack of
|
|
// background color inheritance with Bootstrap v4 table CSS
|
|
return !this.isStacked && this.bvTable.stickyHeader;
|
|
},
|
|
tableVariant: function tableVariant() {
|
|
// Sniffed by <b-tr> / <b-td> / <b-th>
|
|
return this.bvTable.tableVariant;
|
|
},
|
|
theadClasses: function theadClasses() {
|
|
return [this.headVariant ? "thead-".concat(this.headVariant) : null];
|
|
},
|
|
theadAttrs: function theadAttrs() {
|
|
return _objectSpread2({
|
|
role: 'rowgroup'
|
|
}, this.bvAttrs);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('thead', {
|
|
class: this.theadClasses,
|
|
attrs: this.theadAttrs,
|
|
// Pass down any native listeners
|
|
on: this.bvListeners
|
|
}, this.normalizeSlot());
|
|
}
|
|
});
|
|
|
|
var theadMixin = {
|
|
props: makePropsConfigurable({
|
|
headVariant: {
|
|
type: String // 'light', 'dark' or `null` (or custom)
|
|
// default: null
|
|
|
|
},
|
|
headRowVariant: {
|
|
// Any Bootstrap theme variant (or custom)
|
|
type: String // default: null
|
|
|
|
},
|
|
theadClass: {
|
|
type: [String, Array, Object] // default: undefined
|
|
|
|
},
|
|
theadTrClass: {
|
|
type: [String, Array, Object] // default: undefined
|
|
|
|
}
|
|
}, NAME_TABLE),
|
|
methods: {
|
|
fieldClasses: function fieldClasses(field) {
|
|
// Header field (<th>) classes
|
|
return [field.class ? field.class : '', field.thClass ? field.thClass : ''];
|
|
},
|
|
headClicked: function headClicked(evt, field, isFoot) {
|
|
if (this.stopIfBusy && this.stopIfBusy(evt)) {
|
|
// If table is busy (via provider) then don't propagate
|
|
return;
|
|
} else if (filterEvent(evt)) {
|
|
// Clicked on a non-disabled control so ignore
|
|
return;
|
|
} else if (textSelectionActive(this.$el)) {
|
|
// User is selecting text, so ignore
|
|
|
|
/* istanbul ignore next: JSDOM doesn't support getSelection() */
|
|
return;
|
|
}
|
|
|
|
stopEvent(evt);
|
|
this.$emit('head-clicked', field.key, field, evt, isFoot);
|
|
},
|
|
renderThead: function renderThead() {
|
|
var _this = this;
|
|
|
|
var isFoot = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
var h = this.$createElement;
|
|
var fields = this.computedFields || []; // In always stacked mode, we don't bother rendering the head/foot
|
|
// Or if no field headings (empty table)
|
|
|
|
if (this.isStackedAlways || fields.length === 0) {
|
|
return h();
|
|
}
|
|
|
|
var isSortable = this.isSortable,
|
|
isSelectable = this.isSelectable,
|
|
headVariant = this.headVariant,
|
|
footVariant = this.footVariant,
|
|
headRowVariant = this.headRowVariant,
|
|
footRowVariant = this.footRowVariant;
|
|
var hasHeadClickListener = isSortable || this.hasListener('head-clicked'); // Reference to `selectAllRows` and `clearSelected()`, if table is selectable
|
|
|
|
var selectAllRows = isSelectable ? this.selectAllRows : noop;
|
|
var clearSelected = isSelectable ? this.clearSelected : noop; // Helper function to generate a field <th> cell
|
|
|
|
var makeCell = function makeCell(field, colIndex) {
|
|
var label = field.label,
|
|
labelHtml = field.labelHtml,
|
|
variant = field.variant,
|
|
stickyColumn = field.stickyColumn,
|
|
key = field.key;
|
|
var ariaLabel = null;
|
|
|
|
if (!field.label.trim() && !field.headerTitle) {
|
|
// In case field's label and title are empty/blank
|
|
// We need to add a hint about what the column is about for non-sighted users
|
|
|
|
/* istanbul ignore next */
|
|
ariaLabel = startCase(field.key);
|
|
}
|
|
|
|
var on = {};
|
|
|
|
if (hasHeadClickListener) {
|
|
on.click = function (evt) {
|
|
_this.headClicked(evt, field, isFoot);
|
|
};
|
|
|
|
on.keydown = function (evt) {
|
|
var keyCode = evt.keyCode;
|
|
|
|
if (keyCode === CODE_ENTER || keyCode === CODE_SPACE) {
|
|
_this.headClicked(evt, field, isFoot);
|
|
}
|
|
};
|
|
}
|
|
|
|
var sortAttrs = isSortable ? _this.sortTheadThAttrs(key, field, isFoot) : {};
|
|
var sortClass = isSortable ? _this.sortTheadThClasses(key, field, isFoot) : null;
|
|
var sortLabel = isSortable ? _this.sortTheadThLabel(key, field, isFoot) : null;
|
|
var data = {
|
|
class: [_this.fieldClasses(field), sortClass],
|
|
props: {
|
|
variant: variant,
|
|
stickyColumn: stickyColumn
|
|
},
|
|
style: field.thStyle || {},
|
|
attrs: _objectSpread2(_objectSpread2({
|
|
// We only add a tabindex of 0 if there is a head-clicked listener
|
|
tabindex: hasHeadClickListener ? '0' : null,
|
|
abbr: field.headerAbbr || null,
|
|
title: field.headerTitle || null,
|
|
'aria-colindex': colIndex + 1,
|
|
'aria-label': ariaLabel
|
|
}, _this.getThValues(null, key, field.thAttr, isFoot ? 'foot' : 'head', {})), sortAttrs),
|
|
on: on,
|
|
key: key
|
|
}; // Handle edge case where in-document templates are used with new
|
|
// `v-slot:name` syntax where the browser lower-cases the v-slot's
|
|
// name (attributes become lower cased when parsed by the browser)
|
|
// We have replaced the square bracket syntax with round brackets
|
|
// to prevent confusion with dynamic slot names
|
|
|
|
var slotNames = ["head(".concat(key, ")"), "head(".concat(key.toLowerCase(), ")"), 'head()']; // Footer will fallback to header slot names
|
|
|
|
if (isFoot) {
|
|
slotNames = ["foot(".concat(key, ")"), "foot(".concat(key.toLowerCase(), ")"), 'foot()'].concat(_toConsumableArray(slotNames));
|
|
}
|
|
|
|
var scope = {
|
|
label: label,
|
|
column: key,
|
|
field: field,
|
|
isFoot: isFoot,
|
|
// Add in row select methods
|
|
selectAllRows: selectAllRows,
|
|
clearSelected: clearSelected
|
|
};
|
|
var $content = _this.normalizeSlot(slotNames, scope) || h('div', {
|
|
domProps: htmlOrText(labelHtml, label)
|
|
});
|
|
var $srLabel = sortLabel ? h('span', {
|
|
staticClass: 'sr-only'
|
|
}, " (".concat(sortLabel, ")")) : null; // Return the header cell
|
|
|
|
return h(BTh, data, [$content, $srLabel].filter(identity));
|
|
}; // Generate the array of <th> cells
|
|
|
|
|
|
var $cells = fields.map(makeCell).filter(identity); // Generate the row(s)
|
|
|
|
var $trs = [];
|
|
|
|
if (isFoot) {
|
|
$trs.push(h(BTr, {
|
|
class: this.tfootTrClass,
|
|
props: {
|
|
variant: isUndefinedOrNull(footRowVariant) ? headRowVariant :
|
|
/* istanbul ignore next */
|
|
footRowVariant
|
|
}
|
|
}, $cells));
|
|
} else {
|
|
var scope = {
|
|
columns: fields.length,
|
|
fields: fields,
|
|
// Add in row select methods
|
|
selectAllRows: selectAllRows,
|
|
clearSelected: clearSelected
|
|
};
|
|
$trs.push(this.normalizeSlot('thead-top', scope) || h());
|
|
$trs.push(h(BTr, {
|
|
class: this.theadTrClass,
|
|
props: {
|
|
variant: headRowVariant
|
|
}
|
|
}, $cells));
|
|
}
|
|
|
|
return h(isFoot ? BTfoot : BThead, {
|
|
key: isFoot ? 'bv-tfoot' : 'bv-thead',
|
|
class: (isFoot ? this.tfootClass : this.theadClass) || null,
|
|
props: isFoot ? {
|
|
footVariant: footVariant || headVariant || null
|
|
} : {
|
|
headVariant: headVariant || null
|
|
}
|
|
}, $trs);
|
|
}
|
|
}
|
|
};
|
|
|
|
var slotName$1 = 'top-row';
|
|
var topRowMixin = {
|
|
methods: {
|
|
renderTopRow: function renderTopRow() {
|
|
var h = this.$createElement; // Add static Top Row slot (hidden in visibly stacked mode as we can't control the data-label)
|
|
// If in *always* stacked mode, we don't bother rendering the row
|
|
|
|
if (!this.hasNormalizedSlot(slotName$1) || this.stacked === true || this.stacked === '') {
|
|
return h();
|
|
}
|
|
|
|
var fields = this.computedFields;
|
|
return h(BTr, {
|
|
key: 'b-top-row',
|
|
staticClass: 'b-table-top-row',
|
|
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(null, 'row-top') : this.tbodyTrClass],
|
|
attrs: isFunction(this.tbodyTrAttr) ? this.tbodyTrAttr(null, 'row-top') : this.tbodyTrAttr
|
|
}, [this.normalizeSlot(slotName$1, {
|
|
columns: fields.length,
|
|
fields: fields
|
|
})]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// @vue/component
|
|
|
|
var BTable = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TABLE,
|
|
// Order of mixins is important!
|
|
// They are merged from first to last, followed by this component
|
|
mixins: [// General mixins
|
|
attrsMixin, hasListenerMixin, idMixin, normalizeSlotMixin, // Required table mixins
|
|
itemsMixin, tableRendererMixin, stackedMixin, theadMixin, tfootMixin, tbodyMixin, // Table features mixins
|
|
stackedMixin, filteringMixin, sortingMixin, paginationMixin$1, captionMixin, colgroupMixin, selectableMixin, emptyMixin, topRowMixin, bottomRowMixin, busyMixin, providerMixin] // Render function is provided by table-renderer mixin
|
|
|
|
});
|
|
|
|
// @vue/component
|
|
|
|
var BTableLite = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TABLE_LITE,
|
|
// Order of mixins is important!
|
|
// They are merged from first to last, followed by this component.
|
|
mixins: [// Required mixins
|
|
hasListenerMixin, idMixin, normalizeSlotMixin, itemsMixin, tableRendererMixin, stackedMixin, theadMixin, tfootMixin, tbodyMixin, // Features Mixins
|
|
// These are pretty lightweight, and are useful for lightweight tables
|
|
captionMixin, colgroupMixin] // render function provided by table-renderer mixin
|
|
|
|
});
|
|
|
|
// @vue/component
|
|
|
|
var BTableSimple = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TABLE_SIMPLE,
|
|
// Order of mixins is important!
|
|
// They are merged from first to last, followed by this component.
|
|
mixins: [// Required mixins
|
|
idMixin, normalizeSlotMixin, tableRendererMixin, // feature mixin
|
|
// Stacked requires extra handling by users via
|
|
// the table cell `stacked-heading` prop
|
|
stackedMixin],
|
|
computed: {
|
|
isTableSimple: function isTableSimple() {
|
|
return true;
|
|
}
|
|
} // render function provided by table-renderer mixin
|
|
|
|
});
|
|
|
|
var TableLitePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTableLite: BTableLite
|
|
}
|
|
});
|
|
var TableSimplePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTableSimple: BTableSimple,
|
|
BTbody: BTbody,
|
|
BThead: BThead,
|
|
BTfoot: BTfoot,
|
|
BTr: BTr,
|
|
BTd: BTd,
|
|
BTh: BTh
|
|
}
|
|
});
|
|
var TablePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTable: BTable
|
|
},
|
|
plugins: {
|
|
TableLitePlugin: TableLitePlugin,
|
|
TableSimplePlugin: TableSimplePlugin
|
|
}
|
|
});
|
|
|
|
var BSkeletonTable = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SKELETON_TABLE,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
animation: {
|
|
type: String
|
|
},
|
|
rows: {
|
|
type: Number,
|
|
default: 3,
|
|
validator: function validator(value) {
|
|
return value > 0;
|
|
}
|
|
},
|
|
columns: {
|
|
type: Number,
|
|
default: 5,
|
|
validator: function validator(value) {
|
|
return value > 0;
|
|
}
|
|
},
|
|
hideHeader: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showFooter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tableProps: {
|
|
type: Object,
|
|
default: function _default() {}
|
|
}
|
|
}, NAME_SKELETON_TABLE),
|
|
render: function render(h, _ref) {
|
|
var props = _ref.props;
|
|
var animation = props.animation,
|
|
columns = props.columns;
|
|
var $th = h('th', [h(BSkeleton, {
|
|
props: {
|
|
animation: animation
|
|
}
|
|
})]);
|
|
var $thTr = h('tr', createAndFillArray(columns, $th));
|
|
var $td = h('td', [h(BSkeleton, {
|
|
props: {
|
|
width: '75%',
|
|
animation: animation
|
|
}
|
|
})]);
|
|
var $tdTr = h('tr', createAndFillArray(columns, $td));
|
|
var $tbody = h('tbody', createAndFillArray(props.rows, $tdTr));
|
|
var $thead = !props.hideHeader ? h('thead', [$thTr]) : h();
|
|
var $tfoot = props.showFooter ? h('tfoot', [$thTr]) : h();
|
|
return h(BTableSimple, {
|
|
props: _objectSpread2({}, props.tableProps)
|
|
}, [$thead, $tbody, $tfoot]);
|
|
}
|
|
});
|
|
|
|
var BSkeletonWrapper = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_SKELETON_WRAPPER,
|
|
functional: true,
|
|
props: makePropsConfigurable({
|
|
loading: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_SKELETON_WRAPPER),
|
|
render: function render(h, _ref) {
|
|
var data = _ref.data,
|
|
props = _ref.props,
|
|
slots = _ref.slots,
|
|
scopedSlots = _ref.scopedSlots;
|
|
var $slots = slots();
|
|
var $scopedSlots = scopedSlots || {};
|
|
var slotScope = {};
|
|
|
|
if (props.loading) {
|
|
return h('div', a(data, {
|
|
attrs: {
|
|
role: 'alert',
|
|
'aria-live': 'polite',
|
|
'aria-busy': true
|
|
},
|
|
staticClass: 'b-skeleton-wrapper',
|
|
key: 'loading'
|
|
}), [normalizeSlot('loading', slotScope, $scopedSlots, $slots) || h()]);
|
|
}
|
|
|
|
return normalizeSlot(SLOT_NAME_DEFAULT, slotScope, $scopedSlots, $slots) || h();
|
|
}
|
|
});
|
|
|
|
var SkeletonPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BSkeleton: BSkeleton,
|
|
BSkeletonIcon: BSkeletonIcon,
|
|
BSkeletonImg: BSkeletonImg,
|
|
BSkeletonTable: BSkeletonTable,
|
|
BSkeletonWrapper: BSkeletonWrapper
|
|
}
|
|
});
|
|
|
|
var SpinnerPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BSpinner: BSpinner
|
|
}
|
|
});
|
|
|
|
var navProps = omit(props$X, ['tabs', 'isNavBar', 'cardHeader']); // --- Helper methods ---
|
|
// Filter function to filter out disabled tabs
|
|
|
|
var notDisabled = function notDisabled(tab) {
|
|
return !tab.disabled;
|
|
}; // --- Helper components ---
|
|
// @vue/component
|
|
|
|
|
|
var BVTabButton = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TAB_BUTTON_HELPER,
|
|
inject: {
|
|
bvTabs: {
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
props: makePropsConfigurable({
|
|
// Reference to the child <b-tab> instance
|
|
tab: {
|
|
default: null
|
|
},
|
|
tabs: {
|
|
type: Array,
|
|
|
|
/* istanbul ignore next */
|
|
default: function _default() {
|
|
return [];
|
|
}
|
|
},
|
|
id: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
controls: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
tabIndex: {
|
|
type: Number,
|
|
default: null
|
|
},
|
|
posInSet: {
|
|
type: Number,
|
|
default: null
|
|
},
|
|
setSize: {
|
|
type: Number,
|
|
default: null
|
|
},
|
|
noKeyNav: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TABS),
|
|
methods: {
|
|
focus: function focus() {
|
|
attemptFocus(this.$refs.link);
|
|
},
|
|
handleEvt: function handleEvt(evt) {
|
|
if (this.tab.disabled) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
var type = evt.type,
|
|
keyCode = evt.keyCode,
|
|
shiftKey = evt.shiftKey;
|
|
|
|
if (type === 'click') {
|
|
stopEvent(evt);
|
|
this.$emit('click', evt);
|
|
} else if (type === 'keydown' && keyCode === CODE_SPACE) {
|
|
// For ARIA tabs the SPACE key will also trigger a click/select
|
|
// Even with keyboard navigation disabled, SPACE should "click" the button
|
|
// See: https://github.com/bootstrap-vue/bootstrap-vue/issues/4323
|
|
stopEvent(evt);
|
|
this.$emit('click', evt);
|
|
} else if (type === 'keydown' && !this.noKeyNav) {
|
|
// For keyboard navigation
|
|
if ([CODE_UP, CODE_LEFT, CODE_HOME].indexOf(keyCode) !== -1) {
|
|
stopEvent(evt);
|
|
|
|
if (shiftKey || keyCode === CODE_HOME) {
|
|
this.$emit('first', evt);
|
|
} else {
|
|
this.$emit('prev', evt);
|
|
}
|
|
} else if ([CODE_DOWN, CODE_RIGHT, CODE_END].indexOf(keyCode) !== -1) {
|
|
stopEvent(evt);
|
|
|
|
if (shiftKey || keyCode === CODE_END) {
|
|
this.$emit('last', evt);
|
|
} else {
|
|
this.$emit('next', evt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var id = this.id,
|
|
tabIndex = this.tabIndex,
|
|
setSize = this.setSize,
|
|
posInSet = this.posInSet,
|
|
controls = this.controls,
|
|
handleEvt = this.handleEvt;
|
|
var _this$tab = this.tab,
|
|
title = _this$tab.title,
|
|
localActive = _this$tab.localActive,
|
|
disabled = _this$tab.disabled,
|
|
titleItemClass = _this$tab.titleItemClass,
|
|
titleLinkClass = _this$tab.titleLinkClass,
|
|
titleLinkAttributes = _this$tab.titleLinkAttributes;
|
|
var $link = h(BLink, {
|
|
ref: 'link',
|
|
staticClass: 'nav-link',
|
|
class: [{
|
|
active: localActive && !disabled,
|
|
disabled: disabled
|
|
}, titleLinkClass, // Apply <b-tabs> `activeNavItemClass` styles when the tab is active
|
|
localActive ? this.bvTabs.activeNavItemClass : null],
|
|
props: {
|
|
disabled: disabled
|
|
},
|
|
attrs: _objectSpread2(_objectSpread2({}, titleLinkAttributes), {}, {
|
|
role: 'tab',
|
|
id: id,
|
|
// Roving tab index when keynav enabled
|
|
tabindex: tabIndex,
|
|
'aria-selected': localActive && !disabled ? 'true' : 'false',
|
|
'aria-setsize': setSize,
|
|
'aria-posinset': posInSet,
|
|
'aria-controls': controls
|
|
}),
|
|
on: {
|
|
click: handleEvt,
|
|
keydown: handleEvt
|
|
}
|
|
}, [this.tab.normalizeSlot(SLOT_NAME_TITLE) || title]);
|
|
return h('li', {
|
|
staticClass: 'nav-item',
|
|
class: [titleItemClass],
|
|
attrs: {
|
|
role: 'presentation'
|
|
}
|
|
}, [$link]);
|
|
}
|
|
}); // @vue/component
|
|
|
|
var BTabs = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TABS,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
provide: function provide() {
|
|
return {
|
|
bvTabs: this
|
|
};
|
|
},
|
|
model: {
|
|
prop: 'value',
|
|
event: 'input'
|
|
},
|
|
props: _objectSpread2(_objectSpread2({}, navProps), {}, {
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
card: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
end: {
|
|
// Synonym for 'bottom'
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noFade: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noNavStyle: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noKeyNav: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lazy: {
|
|
// This prop is sniffed by the <b-tab> child
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
contentClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
navClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
navWrapperClass: {
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
activeNavItemClass: {
|
|
// Only applied to the currently active <b-nav-item>
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
activeTabClass: {
|
|
// Only applied to the currently active <b-tab>
|
|
// This prop is sniffed by the <b-tab> child
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
value: {
|
|
// v-model
|
|
type: Number,
|
|
default: null
|
|
}
|
|
}),
|
|
data: function data() {
|
|
return {
|
|
// Index of current tab
|
|
currentTab: toInteger(this.value, -1),
|
|
// Array of direct child <b-tab> instances, in DOM order
|
|
tabs: [],
|
|
// Array of child instances registered (for triggering reactive updates)
|
|
registeredTabs: [],
|
|
// Flag to know if we are mounted or not
|
|
isMounted: false
|
|
};
|
|
},
|
|
computed: {
|
|
fade: function fade() {
|
|
// This computed prop is sniffed by the tab child
|
|
return !this.noFade;
|
|
},
|
|
localNavClass: function localNavClass() {
|
|
var classes = [];
|
|
|
|
if (this.card && this.vertical) {
|
|
classes.push('card-header', 'h-100', 'border-bottom-0', 'rounded-0');
|
|
}
|
|
|
|
return [].concat(classes, [this.navClass]);
|
|
}
|
|
},
|
|
watch: {
|
|
currentTab: function currentTab(newVal) {
|
|
var index = -1; // Ensure only one tab is active at most
|
|
|
|
this.tabs.forEach(function (tab, idx) {
|
|
if (newVal === idx && !tab.disabled) {
|
|
tab.localActive = true;
|
|
index = idx;
|
|
} else {
|
|
tab.localActive = false;
|
|
}
|
|
}); // Update the v-model
|
|
|
|
this.$emit('input', index);
|
|
},
|
|
value: function value(newVal, oldVal) {
|
|
if (newVal !== oldVal) {
|
|
newVal = toInteger(newVal, -1);
|
|
oldVal = toInteger(oldVal, 0);
|
|
var tabs = this.tabs;
|
|
|
|
if (tabs[newVal] && !tabs[newVal].disabled) {
|
|
this.activateTab(tabs[newVal]);
|
|
} else {
|
|
// Try next or prev tabs
|
|
if (newVal < oldVal) {
|
|
this.previousTab();
|
|
} else {
|
|
this.nextTab();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
registeredTabs: function registeredTabs() {
|
|
var _this = this;
|
|
|
|
// Each b-tab will register/unregister itself.
|
|
// We use this to detect when tabs are added/removed
|
|
// to trigger the update of the tabs.
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this.updateTabs();
|
|
});
|
|
});
|
|
},
|
|
tabs: function tabs(newVal, oldVal) {
|
|
var _this2 = this;
|
|
|
|
// If tabs added, removed, or re-ordered, we emit a `changed` event.
|
|
// We use `tab._uid` instead of `tab.safeId()`, as the later is changed
|
|
// in a nextTick if no explicit ID is provided, causing duplicate emits.
|
|
if (!looseEqual(newVal.map(function (t) {
|
|
return t._uid;
|
|
}), oldVal.map(function (t) {
|
|
return t._uid;
|
|
}))) {
|
|
// In a nextTick to ensure currentTab has been set first.
|
|
this.$nextTick(function () {
|
|
// We emit shallow copies of the new and old arrays of tabs, to
|
|
// prevent users from potentially mutating the internal arrays.
|
|
_this2.$emit('changed', newVal.slice(), oldVal.slice());
|
|
});
|
|
}
|
|
},
|
|
isMounted: function isMounted(newVal) {
|
|
var _this3 = this;
|
|
|
|
// Trigger an update after mounted. Needed for tabs inside lazy modals.
|
|
if (newVal) {
|
|
requestAF(function () {
|
|
_this3.updateTabs();
|
|
});
|
|
} // Enable or disable the observer
|
|
|
|
|
|
this.setObserver(newVal);
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this4 = this;
|
|
|
|
// Create private non-reactive props
|
|
this.$_observer = null;
|
|
this.currentTab = toInteger(this.value, -1); // For SSR and to make sure only a single tab is shown on mount
|
|
// We wrap this in a `$nextTick()` to ensure the child tabs have been created
|
|
|
|
this.$nextTick(function () {
|
|
_this4.updateTabs();
|
|
});
|
|
},
|
|
mounted: function mounted() {
|
|
var _this5 = this;
|
|
|
|
// Call `updateTabs()` just in case...
|
|
this.updateTabs();
|
|
this.$nextTick(function () {
|
|
// Flag we are now mounted and to switch to DOM for tab probing.
|
|
// As this.$slots.default appears to lie about component instances
|
|
// after b-tabs is destroyed and re-instantiated.
|
|
// And this.$children does not respect DOM order.
|
|
_this5.isMounted = true;
|
|
});
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated: function deactivated() {
|
|
this.isMounted = false;
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated: function activated() {
|
|
var _this6 = this;
|
|
|
|
this.currentTab = toInteger(this.value, -1);
|
|
this.$nextTick(function () {
|
|
_this6.updateTabs();
|
|
|
|
_this6.isMounted = true;
|
|
});
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.isMounted = false;
|
|
},
|
|
destroyed: function destroyed() {
|
|
// Ensure no references to child instances exist
|
|
this.tabs = [];
|
|
},
|
|
methods: {
|
|
registerTab: function registerTab(tab) {
|
|
var _this7 = this;
|
|
|
|
if (!arrayIncludes(this.registeredTabs, tab)) {
|
|
this.registeredTabs.push(tab);
|
|
tab.$once('hook:destroyed', function () {
|
|
_this7.unregisterTab(tab);
|
|
});
|
|
}
|
|
},
|
|
unregisterTab: function unregisterTab(tab) {
|
|
this.registeredTabs = this.registeredTabs.slice().filter(function (t) {
|
|
return t !== tab;
|
|
});
|
|
},
|
|
// DOM observer is needed to detect changes in order of tabs
|
|
setObserver: function setObserver(on) {
|
|
this.$_observer && this.$_observer.disconnect();
|
|
this.$_observer = null;
|
|
|
|
if (on) {
|
|
var self = this;
|
|
/* istanbul ignore next: difficult to test mutation observer in JSDOM */
|
|
|
|
var handler = function handler() {
|
|
// We delay the update to ensure that `tab.safeId()` has
|
|
// updated with the final ID value.
|
|
self.$nextTick(function () {
|
|
requestAF(function () {
|
|
self.updateTabs();
|
|
});
|
|
});
|
|
}; // Watch for changes to <b-tab> sub components
|
|
|
|
|
|
this.$_observer = observeDom(this.$refs.tabsContainer, handler, {
|
|
childList: true,
|
|
subtree: false,
|
|
attributes: true,
|
|
attributeFilter: ['id']
|
|
});
|
|
}
|
|
},
|
|
getTabs: function getTabs() {
|
|
// We use `registeredTabs` as the source of truth for child tab components
|
|
// We also filter out any `<b-tab>` components that are extended
|
|
// `<b-tab>` with a root child `<b-tab>`
|
|
// See: https://github.com/bootstrap-vue/bootstrap-vue/issues/3260
|
|
var tabs = this.registeredTabs.filter(function (tab) {
|
|
return tab.$children.filter(function (t) {
|
|
return t._isTab;
|
|
}).length === 0;
|
|
}); // DOM Order of Tabs
|
|
|
|
var order = [];
|
|
|
|
if (this.isMounted && tabs.length > 0) {
|
|
// We rely on the DOM when mounted to get the 'true' order of the `<b-tab>` children
|
|
// `querySelectorAll()` always returns elements in document order, regardless of
|
|
// order specified in the selector
|
|
var selector = tabs.map(function (tab) {
|
|
return "#".concat(tab.safeId());
|
|
}).join(', ');
|
|
order = selectAll(selector, this.$el).map(function (el) {
|
|
return el.id;
|
|
}).filter(identity);
|
|
} // Stable sort keeps the original order if not found in the `order` array,
|
|
// which will be an empty array before mount
|
|
|
|
|
|
return stableSort(tabs, function (a, b) {
|
|
return order.indexOf(a.safeId()) - order.indexOf(b.safeId());
|
|
});
|
|
},
|
|
// Update list of `<b-tab>` children
|
|
updateTabs: function updateTabs() {
|
|
// Probe tabs
|
|
var tabs = this.getTabs(); // Find *last* active non-disabled tab in current tabs
|
|
// We trust tab state over `currentTab`, in case tabs were added/removed/re-ordered
|
|
|
|
var tabIndex = tabs.indexOf(tabs.slice().reverse().find(function (tab) {
|
|
return tab.localActive && !tab.disabled;
|
|
})); // Else try setting to `currentTab`
|
|
|
|
if (tabIndex < 0) {
|
|
var currentTab = this.currentTab;
|
|
|
|
if (currentTab >= tabs.length) {
|
|
// Handle last tab being removed, so find the last non-disabled tab
|
|
tabIndex = tabs.indexOf(tabs.slice().reverse().find(notDisabled));
|
|
} else if (tabs[currentTab] && !tabs[currentTab].disabled) {
|
|
// Current tab is not disabled
|
|
tabIndex = currentTab;
|
|
}
|
|
} // Else find *first* non-disabled tab in current tabs
|
|
|
|
|
|
if (tabIndex < 0) {
|
|
tabIndex = tabs.indexOf(tabs.find(notDisabled));
|
|
} // Set the current tab state to active
|
|
|
|
|
|
tabs.forEach(function (tab) {
|
|
// tab.localActive = idx === tabIndex && !tab.disabled
|
|
tab.localActive = false;
|
|
});
|
|
|
|
if (tabs[tabIndex]) {
|
|
tabs[tabIndex].localActive = true;
|
|
} // Update the array of tab children
|
|
|
|
|
|
this.tabs = tabs; // Set the currentTab index (can be -1 if no non-disabled tabs)
|
|
|
|
this.currentTab = tabIndex;
|
|
},
|
|
// Find a button that controls a tab, given the tab reference
|
|
// Returns the button vm instance
|
|
getButtonForTab: function getButtonForTab(tab) {
|
|
return (this.$refs.buttons || []).find(function (btn) {
|
|
return btn.tab === tab;
|
|
});
|
|
},
|
|
// Force a button to re-render its content, given a <b-tab> instance
|
|
// Called by <b-tab> on `update()`
|
|
updateButton: function updateButton(tab) {
|
|
var button = this.getButtonForTab(tab);
|
|
|
|
if (button && button.$forceUpdate) {
|
|
button.$forceUpdate();
|
|
}
|
|
},
|
|
// Activate a tab given a `<b-tab>` instance
|
|
// Also accessed by `<b-tab>`
|
|
activateTab: function activateTab(tab) {
|
|
var result = false;
|
|
|
|
if (tab) {
|
|
var index = this.tabs.indexOf(tab);
|
|
|
|
if (!tab.disabled && index > -1 && index !== this.currentTab) {
|
|
var tabEvt = new BvEvent('activate-tab', {
|
|
cancelable: true,
|
|
vueTarget: this,
|
|
componentId: this.safeId()
|
|
});
|
|
this.$emit(tabEvt.type, index, this.currentTab, tabEvt);
|
|
|
|
if (!tabEvt.defaultPrevented) {
|
|
result = true;
|
|
this.currentTab = index;
|
|
}
|
|
}
|
|
} // Couldn't set tab, so ensure v-model is set to `this.currentTab`
|
|
|
|
/* istanbul ignore next: should rarely happen */
|
|
|
|
|
|
if (!result && this.currentTab !== this.value) {
|
|
this.$emit('input', this.currentTab);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
// Deactivate a tab given a <b-tab> instance
|
|
// Accessed by <b-tab>
|
|
deactivateTab: function deactivateTab(tab) {
|
|
if (tab) {
|
|
// Find first non-disabled tab that isn't the one being deactivated
|
|
// If no tabs are available, then don't deactivate current tab
|
|
return this.activateTab(this.tabs.filter(function (t) {
|
|
return t !== tab;
|
|
}).find(notDisabled));
|
|
}
|
|
/* istanbul ignore next: should never/rarely happen */
|
|
|
|
|
|
return false;
|
|
},
|
|
// Focus a tab button given its <b-tab> instance
|
|
focusButton: function focusButton(tab) {
|
|
var _this8 = this;
|
|
|
|
// Wrap in `$nextTick()` to ensure DOM has completed rendering/updating before focusing
|
|
this.$nextTick(function () {
|
|
attemptFocus(_this8.getButtonForTab(tab));
|
|
});
|
|
},
|
|
// Emit a click event on a specified <b-tab> component instance
|
|
emitTabClick: function emitTabClick(tab, evt) {
|
|
if (isEvent(evt) && tab && tab.$emit && !tab.disabled) {
|
|
tab.$emit('click', evt);
|
|
}
|
|
},
|
|
// Click handler
|
|
clickTab: function clickTab(tab, evt) {
|
|
this.activateTab(tab);
|
|
this.emitTabClick(tab, evt);
|
|
},
|
|
// Move to first non-disabled tab
|
|
firstTab: function firstTab(focus) {
|
|
var tab = this.tabs.find(notDisabled);
|
|
|
|
if (this.activateTab(tab) && focus) {
|
|
this.focusButton(tab);
|
|
this.emitTabClick(tab, focus);
|
|
}
|
|
},
|
|
// Move to previous non-disabled tab
|
|
previousTab: function previousTab(focus) {
|
|
var currentIndex = mathMax(this.currentTab, 0);
|
|
var tab = this.tabs.slice(0, currentIndex).reverse().find(notDisabled);
|
|
|
|
if (this.activateTab(tab) && focus) {
|
|
this.focusButton(tab);
|
|
this.emitTabClick(tab, focus);
|
|
}
|
|
},
|
|
// Move to next non-disabled tab
|
|
nextTab: function nextTab(focus) {
|
|
var currentIndex = mathMax(this.currentTab, -1);
|
|
var tab = this.tabs.slice(currentIndex + 1).find(notDisabled);
|
|
|
|
if (this.activateTab(tab) && focus) {
|
|
this.focusButton(tab);
|
|
this.emitTabClick(tab, focus);
|
|
}
|
|
},
|
|
// Move to last non-disabled tab
|
|
lastTab: function lastTab(focus) {
|
|
var tab = this.tabs.slice().reverse().find(notDisabled);
|
|
|
|
if (this.activateTab(tab) && focus) {
|
|
this.focusButton(tab);
|
|
this.emitTabClick(tab, focus);
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var _this9 = this;
|
|
|
|
var tabs = this.tabs,
|
|
noKeyNav = this.noKeyNav,
|
|
firstTab = this.firstTab,
|
|
previousTab = this.previousTab,
|
|
nextTab = this.nextTab,
|
|
lastTab = this.lastTab; // Currently active tab
|
|
|
|
var activeTab = tabs.find(function (tab) {
|
|
return tab.localActive && !tab.disabled;
|
|
}); // Tab button to allow focusing when no active tab found (keynav only)
|
|
|
|
var fallbackTab = tabs.find(function (tab) {
|
|
return !tab.disabled;
|
|
}); // For each `<b-tab>` found create the tab buttons
|
|
|
|
var buttons = tabs.map(function (tab, index) {
|
|
var tabIndex = null; // Ensure at least one tab button is focusable when keynav enabled (if possible)
|
|
|
|
if (!noKeyNav) {
|
|
// Buttons are not in tab index unless active, or a fallback tab
|
|
tabIndex = -1;
|
|
|
|
if (activeTab === tab || !activeTab && fallbackTab === tab) {
|
|
// Place tab button in tab sequence
|
|
tabIndex = null;
|
|
}
|
|
}
|
|
|
|
return h(BVTabButton, {
|
|
key: tab._uid || index,
|
|
ref: 'buttons',
|
|
// Needed to make `this.$refs.buttons` an array
|
|
refInFor: true,
|
|
props: {
|
|
tab: tab,
|
|
tabs: tabs,
|
|
id: tab.controlledBy || (tab.safeId ? tab.safeId("_BV_tab_button_") : null),
|
|
controls: tab.safeId ? tab.safeId() : null,
|
|
tabIndex: tabIndex,
|
|
setSize: tabs.length,
|
|
posInSet: index + 1,
|
|
noKeyNav: noKeyNav
|
|
},
|
|
on: {
|
|
click: function click(evt) {
|
|
_this9.clickTab(tab, evt);
|
|
},
|
|
first: firstTab,
|
|
prev: previousTab,
|
|
next: nextTab,
|
|
last: lastTab
|
|
}
|
|
});
|
|
}); // Nav
|
|
|
|
var nav = h(BNav, {
|
|
ref: 'nav',
|
|
class: this.localNavClass,
|
|
attrs: {
|
|
role: 'tablist',
|
|
id: this.safeId('_BV_tab_controls_')
|
|
},
|
|
props: {
|
|
fill: this.fill,
|
|
justified: this.justified,
|
|
align: this.align,
|
|
tabs: !this.noNavStyle && !this.pills,
|
|
pills: !this.noNavStyle && this.pills,
|
|
vertical: this.vertical,
|
|
small: this.small,
|
|
cardHeader: this.card && !this.vertical
|
|
}
|
|
}, [this.normalizeSlot('tabs-start') || h(), buttons, this.normalizeSlot('tabs-end') || h()]);
|
|
nav = h('div', {
|
|
key: 'bv-tabs-nav',
|
|
class: [{
|
|
'card-header': this.card && !this.vertical && !this.end,
|
|
'card-footer': this.card && !this.vertical && this.end,
|
|
'col-auto': this.vertical
|
|
}, this.navWrapperClass]
|
|
}, [nav]);
|
|
var empty = h();
|
|
|
|
if (!tabs || tabs.length === 0) {
|
|
empty = h('div', {
|
|
key: 'bv-empty-tab',
|
|
class: ['tab-pane', 'active', {
|
|
'card-body': this.card
|
|
}]
|
|
}, this.normalizeSlot('empty'));
|
|
} // Main content section
|
|
|
|
|
|
var content = h('div', {
|
|
ref: 'tabsContainer',
|
|
key: 'bv-tabs-container',
|
|
staticClass: 'tab-content',
|
|
class: [{
|
|
col: this.vertical
|
|
}, this.contentClass],
|
|
attrs: {
|
|
id: this.safeId('_BV_tab_container_')
|
|
}
|
|
}, concat(this.normalizeSlot(), empty)); // Render final output
|
|
|
|
return h(this.tag, {
|
|
staticClass: 'tabs',
|
|
class: {
|
|
row: this.vertical,
|
|
'no-gutters': this.vertical && this.card
|
|
},
|
|
attrs: {
|
|
id: this.safeId()
|
|
}
|
|
}, [this.end ? content : h(), [nav], this.end ? h() : content]);
|
|
}
|
|
});
|
|
|
|
var BTab = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TAB,
|
|
mixins: [idMixin, normalizeSlotMixin],
|
|
inject: {
|
|
bvTabs: {
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
}
|
|
},
|
|
props: makePropsConfigurable({
|
|
active: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
buttonId: {
|
|
type: String // default: ''
|
|
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
titleItemClass: {
|
|
// Sniffed by `<b-tabs>` and added to nav `li.nav-item`
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
titleLinkClass: {
|
|
// Sniffed by `<b-tabs>` and added to nav `a.nav-link`
|
|
type: [String, Array, Object] // default: null
|
|
|
|
},
|
|
titleLinkAttributes: {
|
|
type: Object // default: null
|
|
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noBody: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
lazy: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, NAME_TAB),
|
|
data: function data() {
|
|
return {
|
|
localActive: this.active && !this.disabled,
|
|
show: false
|
|
};
|
|
},
|
|
computed: {
|
|
tabClasses: function tabClasses() {
|
|
return [{
|
|
active: this.localActive,
|
|
disabled: this.disabled,
|
|
'card-body': this.bvTabs.card && !this.noBody
|
|
}, // Apply <b-tabs> `activeTabClass` styles when this tab is active
|
|
this.localActive ? this.bvTabs.activeTabClass : null];
|
|
},
|
|
controlledBy: function controlledBy() {
|
|
return this.buttonId || this.safeId('__BV_tab_button__');
|
|
},
|
|
computedNoFade: function computedNoFade() {
|
|
return !(this.bvTabs.fade || false);
|
|
},
|
|
computedLazy: function computedLazy() {
|
|
return this.bvTabs.lazy || this.lazy;
|
|
},
|
|
// For parent sniffing of child
|
|
_isTab: function _isTab() {
|
|
return true;
|
|
}
|
|
},
|
|
watch: {
|
|
localActive: function localActive(newValue) {
|
|
// Make `active` prop work with `.sync` modifier
|
|
this.$emit('update:active', newValue);
|
|
},
|
|
active: function active(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
if (newValue) {
|
|
// If activated post mount
|
|
this.activate();
|
|
} else {
|
|
/* istanbul ignore next */
|
|
if (!this.deactivate()) {
|
|
// Tab couldn't be deactivated, so we reset the synced active prop
|
|
// Deactivation will fail if no other tabs to activate
|
|
this.$emit('update:active', this.localActive);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
disabled: function disabled(newValue, oldValue) {
|
|
if (newValue !== oldValue) {
|
|
var firstTab = this.bvTabs.firstTab;
|
|
|
|
if (newValue && this.localActive && firstTab) {
|
|
this.localActive = false;
|
|
firstTab();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
// Inform b-tabs of our presence
|
|
this.registerTab(); // Initially show on mount if active and not disabled
|
|
|
|
this.show = this.localActive;
|
|
},
|
|
updated: function updated() {
|
|
// Force the tab button content to update (since slots are not reactive)
|
|
// Only done if we have a title slot, as the title prop is reactive
|
|
var updateButton = this.bvTabs.updateButton;
|
|
|
|
if (updateButton && this.hasNormalizedSlot(SLOT_NAME_TITLE)) {
|
|
updateButton(this);
|
|
}
|
|
},
|
|
destroyed: function destroyed() {
|
|
// inform b-tabs of our departure
|
|
this.unregisterTab();
|
|
},
|
|
methods: {
|
|
// Private methods
|
|
registerTab: function registerTab() {
|
|
// Inform `<b-tabs>` of our presence
|
|
var registerTab = this.bvTabs.registerTab;
|
|
|
|
if (registerTab) {
|
|
registerTab(this);
|
|
}
|
|
},
|
|
unregisterTab: function unregisterTab() {
|
|
// Inform `<b-tabs>` of our departure
|
|
var unregisterTab = this.bvTabs.unregisterTab;
|
|
|
|
if (unregisterTab) {
|
|
unregisterTab(this);
|
|
}
|
|
},
|
|
// Public methods
|
|
activate: function activate() {
|
|
// Not inside a `<b-tabs>` component or tab is disabled
|
|
var activateTab = this.bvTabs.activateTab;
|
|
return activateTab && !this.disabled ? activateTab(this) : false;
|
|
},
|
|
deactivate: function deactivate() {
|
|
// Not inside a `<b-tabs>` component or not active to begin with
|
|
var deactivateTab = this.bvTabs.deactivateTab;
|
|
return deactivateTab && this.localActive ? deactivateTab(this) : false;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var localActive = this.localActive;
|
|
var $content = h(this.tag, {
|
|
ref: 'panel',
|
|
staticClass: 'tab-pane',
|
|
class: this.tabClasses,
|
|
directives: [{
|
|
name: 'show',
|
|
rawName: 'v-show',
|
|
value: localActive,
|
|
expression: 'localActive'
|
|
}],
|
|
attrs: {
|
|
role: 'tabpanel',
|
|
id: this.safeId(),
|
|
'aria-hidden': localActive ? 'false' : 'true',
|
|
'aria-labelledby': this.controlledBy || null
|
|
}
|
|
}, // Render content lazily if requested
|
|
[localActive || !this.computedLazy ? this.normalizeSlot() : h()]);
|
|
return h(BVTransition, {
|
|
props: {
|
|
mode: 'out-in',
|
|
noFade: this.computedNoFade
|
|
}
|
|
}, [$content]);
|
|
}
|
|
});
|
|
|
|
var TabsPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTabs: BTabs,
|
|
BTab: BTab
|
|
}
|
|
});
|
|
|
|
var TimePlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTime: BTime
|
|
}
|
|
});
|
|
|
|
function _typeof$1(obj) {
|
|
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
_typeof$1 = function (obj) {
|
|
return typeof obj;
|
|
};
|
|
} else {
|
|
_typeof$1 = function (obj) {
|
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
};
|
|
}
|
|
|
|
return _typeof$1(obj);
|
|
}
|
|
|
|
function _toConsumableArray$1(arr) {
|
|
return _arrayWithoutHoles$1(arr) || _iterableToArray$1(arr) || _nonIterableSpread$1();
|
|
}
|
|
|
|
function _arrayWithoutHoles$1(arr) {
|
|
if (Array.isArray(arr)) {
|
|
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
|
|
|
|
return arr2;
|
|
}
|
|
}
|
|
|
|
function _iterableToArray$1(iter) {
|
|
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
|
|
}
|
|
|
|
function _nonIterableSpread$1() {
|
|
throw new TypeError("Invalid attempt to spread non-iterable instance");
|
|
}
|
|
|
|
var inBrowser = typeof window !== 'undefined';
|
|
function freeze(item) {
|
|
if (Array.isArray(item) || _typeof$1(item) === 'object') {
|
|
return Object.freeze(item);
|
|
}
|
|
|
|
return item;
|
|
}
|
|
function combinePassengers(transports) {
|
|
var slotProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
return transports.reduce(function (passengers, transport) {
|
|
var temp = transport.passengers[0];
|
|
var newPassengers = typeof temp === 'function' ? temp(slotProps) : transport.passengers;
|
|
return passengers.concat(newPassengers);
|
|
}, []);
|
|
}
|
|
function stableSort$1(array, compareFn) {
|
|
return array.map(function (v, idx) {
|
|
return [idx, v];
|
|
}).sort(function (a, b) {
|
|
return compareFn(a[1], b[1]) || a[0] - b[0];
|
|
}).map(function (c) {
|
|
return c[1];
|
|
});
|
|
}
|
|
function pick$1(obj, keys) {
|
|
return keys.reduce(function (acc, key) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
acc[key] = obj[key];
|
|
}
|
|
|
|
return acc;
|
|
}, {});
|
|
}
|
|
|
|
var transports = {};
|
|
var targets = {};
|
|
var sources = {};
|
|
var Wormhole = Vue__default['default'].extend({
|
|
data: function data() {
|
|
return {
|
|
transports: transports,
|
|
targets: targets,
|
|
sources: sources,
|
|
trackInstances: inBrowser
|
|
};
|
|
},
|
|
methods: {
|
|
open: function open(transport) {
|
|
if (!inBrowser) return;
|
|
var to = transport.to,
|
|
from = transport.from,
|
|
passengers = transport.passengers,
|
|
_transport$order = transport.order,
|
|
order = _transport$order === void 0 ? Infinity : _transport$order;
|
|
if (!to || !from || !passengers) return;
|
|
var newTransport = {
|
|
to: to,
|
|
from: from,
|
|
passengers: freeze(passengers),
|
|
order: order
|
|
};
|
|
var keys = Object.keys(this.transports);
|
|
|
|
if (keys.indexOf(to) === -1) {
|
|
Vue__default['default'].set(this.transports, to, []);
|
|
}
|
|
|
|
var currentIndex = this.$_getTransportIndex(newTransport); // Copying the array here so that the PortalTarget change event will actually contain two distinct arrays
|
|
|
|
var newTransports = this.transports[to].slice(0);
|
|
|
|
if (currentIndex === -1) {
|
|
newTransports.push(newTransport);
|
|
} else {
|
|
newTransports[currentIndex] = newTransport;
|
|
}
|
|
|
|
this.transports[to] = stableSort$1(newTransports, function (a, b) {
|
|
return a.order - b.order;
|
|
});
|
|
},
|
|
close: function close(transport) {
|
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
var to = transport.to,
|
|
from = transport.from;
|
|
if (!to || !from && force === false) return;
|
|
|
|
if (!this.transports[to]) {
|
|
return;
|
|
}
|
|
|
|
if (force) {
|
|
this.transports[to] = [];
|
|
} else {
|
|
var index = this.$_getTransportIndex(transport);
|
|
|
|
if (index >= 0) {
|
|
// Copying the array here so that the PortalTarget change event will actually contain two distinct arrays
|
|
var newTransports = this.transports[to].slice(0);
|
|
newTransports.splice(index, 1);
|
|
this.transports[to] = newTransports;
|
|
}
|
|
}
|
|
},
|
|
registerTarget: function registerTarget(target, vm, force) {
|
|
if (!inBrowser) return;
|
|
|
|
if (this.trackInstances && !force && this.targets[target]) {
|
|
console.warn("[portal-vue]: Target ".concat(target, " already exists"));
|
|
}
|
|
|
|
this.$set(this.targets, target, Object.freeze([vm]));
|
|
},
|
|
unregisterTarget: function unregisterTarget(target) {
|
|
this.$delete(this.targets, target);
|
|
},
|
|
registerSource: function registerSource(source, vm, force) {
|
|
if (!inBrowser) return;
|
|
|
|
if (this.trackInstances && !force && this.sources[source]) {
|
|
console.warn("[portal-vue]: source ".concat(source, " already exists"));
|
|
}
|
|
|
|
this.$set(this.sources, source, Object.freeze([vm]));
|
|
},
|
|
unregisterSource: function unregisterSource(source) {
|
|
this.$delete(this.sources, source);
|
|
},
|
|
hasTarget: function hasTarget(to) {
|
|
return !!(this.targets[to] && this.targets[to][0]);
|
|
},
|
|
hasSource: function hasSource(to) {
|
|
return !!(this.sources[to] && this.sources[to][0]);
|
|
},
|
|
hasContentFor: function hasContentFor(to) {
|
|
return !!this.transports[to] && !!this.transports[to].length;
|
|
},
|
|
// Internal
|
|
$_getTransportIndex: function $_getTransportIndex(_ref) {
|
|
var to = _ref.to,
|
|
from = _ref.from;
|
|
|
|
for (var i in this.transports[to]) {
|
|
if (this.transports[to][i].from === from) {
|
|
return +i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
});
|
|
var wormhole = new Wormhole(transports);
|
|
|
|
var _id = 1;
|
|
var Portal = Vue__default['default'].extend({
|
|
name: 'portal',
|
|
props: {
|
|
disabled: {
|
|
type: Boolean
|
|
},
|
|
name: {
|
|
type: String,
|
|
default: function _default() {
|
|
return String(_id++);
|
|
}
|
|
},
|
|
order: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
slim: {
|
|
type: Boolean
|
|
},
|
|
slotProps: {
|
|
type: Object,
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'DIV'
|
|
},
|
|
to: {
|
|
type: String,
|
|
default: function _default() {
|
|
return String(Math.round(Math.random() * 10000000));
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
wormhole.registerSource(_this.name, _this);
|
|
});
|
|
},
|
|
mounted: function mounted() {
|
|
if (!this.disabled) {
|
|
this.sendUpdate();
|
|
}
|
|
},
|
|
updated: function updated() {
|
|
if (this.disabled) {
|
|
this.clear();
|
|
} else {
|
|
this.sendUpdate();
|
|
}
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
wormhole.unregisterSource(this.name);
|
|
this.clear();
|
|
},
|
|
watch: {
|
|
to: function to(newValue, oldValue) {
|
|
oldValue && oldValue !== newValue && this.clear(oldValue);
|
|
this.sendUpdate();
|
|
}
|
|
},
|
|
methods: {
|
|
clear: function clear(target) {
|
|
var closer = {
|
|
from: this.name,
|
|
to: target || this.to
|
|
};
|
|
wormhole.close(closer);
|
|
},
|
|
normalizeSlots: function normalizeSlots() {
|
|
return this.$scopedSlots.default ? [this.$scopedSlots.default] : this.$slots.default;
|
|
},
|
|
normalizeOwnChildren: function normalizeOwnChildren(children) {
|
|
return typeof children === 'function' ? children(this.slotProps) : children;
|
|
},
|
|
sendUpdate: function sendUpdate() {
|
|
var slotContent = this.normalizeSlots();
|
|
|
|
if (slotContent) {
|
|
var transport = {
|
|
from: this.name,
|
|
to: this.to,
|
|
passengers: _toConsumableArray$1(slotContent),
|
|
order: this.order
|
|
};
|
|
wormhole.open(transport);
|
|
} else {
|
|
this.clear();
|
|
}
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var children = this.$slots.default || this.$scopedSlots.default || [];
|
|
var Tag = this.tag;
|
|
|
|
if (children && this.disabled) {
|
|
return children.length <= 1 && this.slim ? this.normalizeOwnChildren(children)[0] : h(Tag, [this.normalizeOwnChildren(children)]);
|
|
} else {
|
|
return this.slim ? h() : h(Tag, {
|
|
class: {
|
|
'v-portal': true
|
|
},
|
|
style: {
|
|
display: 'none'
|
|
},
|
|
key: 'v-portal-placeholder'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
var PortalTarget = Vue__default['default'].extend({
|
|
name: 'portalTarget',
|
|
props: {
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
name: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
slim: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
slotProps: {
|
|
type: Object,
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
transition: {
|
|
type: [String, Object, Function]
|
|
}
|
|
},
|
|
data: function data() {
|
|
return {
|
|
transports: wormhole.transports,
|
|
firstRender: true
|
|
};
|
|
},
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
this.$nextTick(function () {
|
|
wormhole.registerTarget(_this.name, _this);
|
|
});
|
|
},
|
|
watch: {
|
|
ownTransports: function ownTransports() {
|
|
this.$emit('change', this.children().length > 0);
|
|
},
|
|
name: function name(newVal, oldVal) {
|
|
/**
|
|
* TODO
|
|
* This should warn as well ...
|
|
*/
|
|
wormhole.unregisterTarget(oldVal);
|
|
wormhole.registerTarget(newVal, this);
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this2 = this;
|
|
|
|
if (this.transition) {
|
|
this.$nextTick(function () {
|
|
// only when we have a transition, because it causes a re-render
|
|
_this2.firstRender = false;
|
|
});
|
|
}
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
wormhole.unregisterTarget(this.name);
|
|
},
|
|
computed: {
|
|
ownTransports: function ownTransports() {
|
|
var transports = this.transports[this.name] || [];
|
|
|
|
if (this.multiple) {
|
|
return transports;
|
|
}
|
|
|
|
return transports.length === 0 ? [] : [transports[transports.length - 1]];
|
|
},
|
|
passengers: function passengers() {
|
|
return combinePassengers(this.ownTransports, this.slotProps);
|
|
}
|
|
},
|
|
methods: {
|
|
// can't be a computed prop because it has to "react" to $slot changes.
|
|
children: function children() {
|
|
return this.passengers.length !== 0 ? this.passengers : this.$scopedSlots.default ? this.$scopedSlots.default(this.slotProps) : this.$slots.default || [];
|
|
},
|
|
// can't be a computed prop because it has to "react" to this.children().
|
|
noWrapper: function noWrapper() {
|
|
var noWrapper = this.slim && !this.transition;
|
|
|
|
if (noWrapper && this.children().length > 1) {
|
|
console.warn('[portal-vue]: PortalTarget with `slim` option received more than one child element.');
|
|
}
|
|
|
|
return noWrapper;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var noWrapper = this.noWrapper();
|
|
var children = this.children();
|
|
var Tag = this.transition || this.tag;
|
|
return noWrapper ? children[0] : this.slim && !Tag ? h() : h(Tag, {
|
|
props: {
|
|
// if we have a transition component, pass the tag if it exists
|
|
tag: this.transition && this.tag ? this.tag : undefined
|
|
},
|
|
class: {
|
|
'vue-portal-target': true
|
|
}
|
|
}, children);
|
|
}
|
|
});
|
|
|
|
var _id$1 = 0;
|
|
var portalProps = ['disabled', 'name', 'order', 'slim', 'slotProps', 'tag', 'to'];
|
|
var targetProps = ['multiple', 'transition'];
|
|
var MountingPortal = Vue__default['default'].extend({
|
|
name: 'MountingPortal',
|
|
inheritAttrs: false,
|
|
props: {
|
|
append: {
|
|
type: [Boolean, String]
|
|
},
|
|
bail: {
|
|
type: Boolean
|
|
},
|
|
mountTo: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
// Portal
|
|
disabled: {
|
|
type: Boolean
|
|
},
|
|
// name for the portal
|
|
name: {
|
|
type: String,
|
|
default: function _default() {
|
|
return 'mounted_' + String(_id$1++);
|
|
}
|
|
},
|
|
order: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
slim: {
|
|
type: Boolean
|
|
},
|
|
slotProps: {
|
|
type: Object,
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
},
|
|
tag: {
|
|
type: String,
|
|
default: 'DIV'
|
|
},
|
|
// name for the target
|
|
to: {
|
|
type: String,
|
|
default: function _default() {
|
|
return String(Math.round(Math.random() * 10000000));
|
|
}
|
|
},
|
|
// Target
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
targetSlim: {
|
|
type: Boolean
|
|
},
|
|
targetSlotProps: {
|
|
type: Object,
|
|
default: function _default() {
|
|
return {};
|
|
}
|
|
},
|
|
targetTag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
transition: {
|
|
type: [String, Object, Function]
|
|
}
|
|
},
|
|
created: function created() {
|
|
if (typeof document === 'undefined') return;
|
|
var el = document.querySelector(this.mountTo);
|
|
|
|
if (!el) {
|
|
console.error("[portal-vue]: Mount Point '".concat(this.mountTo, "' not found in document"));
|
|
return;
|
|
}
|
|
|
|
var props = this.$props; // Target already exists
|
|
|
|
if (wormhole.targets[props.name]) {
|
|
if (props.bail) {
|
|
console.warn("[portal-vue]: Target ".concat(props.name, " is already mounted.\n Aborting because 'bail: true' is set"));
|
|
} else {
|
|
this.portalTarget = wormhole.targets[props.name];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var append = props.append;
|
|
|
|
if (append) {
|
|
var type = typeof append === 'string' ? append : 'DIV';
|
|
var mountEl = document.createElement(type);
|
|
el.appendChild(mountEl);
|
|
el = mountEl;
|
|
} // get props for target from $props
|
|
// we have to rename a few of them
|
|
|
|
|
|
var _props = pick$1(this.$props, targetProps);
|
|
|
|
_props.slim = this.targetSlim;
|
|
_props.tag = this.targetTag;
|
|
_props.slotProps = this.targetSlotProps;
|
|
_props.name = this.to;
|
|
this.portalTarget = new PortalTarget({
|
|
el: el,
|
|
parent: this.$parent || this,
|
|
propsData: _props
|
|
});
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
var target = this.portalTarget;
|
|
|
|
if (this.append) {
|
|
var el = target.$el;
|
|
el.parentNode.removeChild(el);
|
|
}
|
|
|
|
target.$destroy();
|
|
},
|
|
render: function render(h) {
|
|
if (!this.portalTarget) {
|
|
console.warn("[portal-vue] Target wasn't mounted");
|
|
return h();
|
|
} // if there's no "manual" scoped slot, so we create a <Portal> ourselves
|
|
|
|
|
|
if (!this.$scopedSlots.manual) {
|
|
var props = pick$1(this.$props, portalProps);
|
|
return h(Portal, {
|
|
props: props,
|
|
attrs: this.$attrs,
|
|
on: this.$listeners,
|
|
scopedSlots: this.$scopedSlots
|
|
}, this.$slots.default);
|
|
} // else, we render the scoped slot
|
|
|
|
|
|
var content = this.$scopedSlots.manual({
|
|
to: this.to
|
|
}); // if user used <template> for the scoped slot
|
|
// content will be an array
|
|
|
|
if (Array.isArray(content)) {
|
|
content = content[0];
|
|
}
|
|
|
|
if (!content) return h();
|
|
return content;
|
|
}
|
|
});
|
|
|
|
var props$1a = makePropsConfigurable({
|
|
name: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
ariaLive: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
// Allowed: 'true' or 'false' or null
|
|
ariaAtomic: {
|
|
type: String // default: undefined
|
|
|
|
},
|
|
role: {
|
|
// Aria role
|
|
type: String // default: undefined
|
|
|
|
}
|
|
}, NAME_TOASTER); // @vue/component
|
|
|
|
var DefaultTransition = /*#__PURE__*/Vue__default['default'].extend({
|
|
data: function data() {
|
|
return {
|
|
// Transition classes base name
|
|
name: 'b-toaster'
|
|
};
|
|
},
|
|
methods: {
|
|
onAfterEnter: function onAfterEnter(el) {
|
|
var _this = this;
|
|
|
|
// Work around a Vue.js bug where `*-enter-to` class is not removed
|
|
// See: https://github.com/vuejs/vue/pull/7901
|
|
// The `*-move` class is also stuck on elements that moved,
|
|
// but there are no JavaScript hooks to handle after move
|
|
// See: https://github.com/vuejs/vue/pull/7906
|
|
requestAF(function () {
|
|
removeClass(el, "".concat(_this.name, "-enter-to"));
|
|
});
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
return h('transition-group', {
|
|
props: {
|
|
tag: 'div',
|
|
name: this.name
|
|
},
|
|
on: {
|
|
afterEnter: this.onAfterEnter
|
|
}
|
|
}, this.$slots.default);
|
|
}
|
|
}); // @vue/component
|
|
|
|
var BToaster = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TOASTER,
|
|
props: props$1a,
|
|
data: function data() {
|
|
return {
|
|
// We don't render on SSR or if a an existing target found
|
|
doRender: false,
|
|
dead: false,
|
|
// Toaster names cannot change once created
|
|
staticName: this.name
|
|
};
|
|
},
|
|
beforeMount: function beforeMount() {
|
|
var _this2 = this;
|
|
|
|
this.staticName = this.name;
|
|
/* istanbul ignore if */
|
|
|
|
if (wormhole.hasTarget(this.staticName)) {
|
|
warn("A \"<portal-target>\" with name \"".concat(this.name, "\" already exists in the document."), NAME_TOASTER);
|
|
this.dead = true;
|
|
} else {
|
|
this.doRender = true;
|
|
this.$once('hook:beforeDestroy', function () {
|
|
// Let toasts made with `this.$bvToast.toast()` know that this toaster
|
|
// is being destroyed and should should also destroy/hide themselves
|
|
_this2.$root.$emit('bv::toaster::destroyed', _this2.staticName);
|
|
});
|
|
}
|
|
},
|
|
destroyed: function destroyed() {
|
|
// Remove from DOM if needed
|
|
|
|
/* istanbul ignore next: difficult to test */
|
|
if (this.$el && this.$el.parentNode) {
|
|
this.$el.parentNode.removeChild(this.$el);
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
var $toaster = h('div', {
|
|
class: ['d-none', {
|
|
'b-dead-toaster': this.dead
|
|
}]
|
|
});
|
|
|
|
if (this.doRender) {
|
|
var $target = h(PortalTarget, {
|
|
staticClass: 'b-toaster-slot',
|
|
props: {
|
|
name: this.staticName,
|
|
multiple: true,
|
|
tag: 'div',
|
|
slim: false,
|
|
// transition: this.transition || DefaultTransition
|
|
transition: DefaultTransition
|
|
}
|
|
});
|
|
$toaster = h('div', {
|
|
staticClass: 'b-toaster',
|
|
class: [this.staticName],
|
|
attrs: {
|
|
id: this.staticName,
|
|
role: this.role || null,
|
|
// Fallback to null to make sure attribute doesn't exist
|
|
'aria-live': this.ariaLive,
|
|
'aria-atomic': this.ariaAtomic
|
|
}
|
|
}, [$target]);
|
|
}
|
|
|
|
return $toaster;
|
|
}
|
|
});
|
|
|
|
var MIN_DURATION = 1000; // --- Props ---
|
|
|
|
var linkProps$5 = pick(props$1, ['href', 'to']);
|
|
var props$1b = makePropsConfigurable(_objectSpread2({
|
|
id: {
|
|
// Even though the ID prop is provided by idMixin, we
|
|
// add it here for $bvToast props filtering
|
|
type: String // default: null
|
|
|
|
},
|
|
title: {
|
|
type: String // default: null
|
|
|
|
},
|
|
toaster: {
|
|
type: String,
|
|
default: 'b-toaster-top-right'
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
variant: {
|
|
type: String // default: null
|
|
|
|
},
|
|
isStatus: {
|
|
// Switches role to 'status' and aria-live to 'polite'
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
appendToast: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noAutoHide: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
autoHideDelay: {
|
|
type: [Number, String],
|
|
default: 5000
|
|
},
|
|
noCloseButton: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noFade: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
noHoverPause: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
solid: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
toastClass: {
|
|
type: [String, Object, Array] // default: undefined
|
|
|
|
},
|
|
headerClass: {
|
|
type: [String, Object, Array] // default: undefined
|
|
|
|
},
|
|
bodyClass: {
|
|
type: [String, Object, Array] // default: undefined
|
|
|
|
},
|
|
static: {
|
|
// Render the toast in place, rather than in a portal-target
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
}, linkProps$5), NAME_TOAST); // @vue/component
|
|
|
|
var BToast = /*#__PURE__*/Vue__default['default'].extend({
|
|
name: NAME_TOAST,
|
|
mixins: [attrsMixin, idMixin, listenOnRootMixin, normalizeSlotMixin, scopedStyleAttrsMixin],
|
|
inheritAttrs: false,
|
|
model: {
|
|
prop: 'visible',
|
|
event: 'change'
|
|
},
|
|
props: props$1b,
|
|
data: function data() {
|
|
return {
|
|
isMounted: false,
|
|
doRender: false,
|
|
localShow: false,
|
|
isTransitioning: false,
|
|
isHiding: false,
|
|
order: 0,
|
|
dismissStarted: 0,
|
|
resumeDismiss: 0
|
|
};
|
|
},
|
|
computed: {
|
|
bToastClasses: function bToastClasses() {
|
|
return _defineProperty({
|
|
'b-toast-solid': this.solid,
|
|
'b-toast-append': this.appendToast,
|
|
'b-toast-prepend': !this.appendToast
|
|
}, "b-toast-".concat(this.variant), this.variant);
|
|
},
|
|
slotScope: function slotScope() {
|
|
return {
|
|
hide: this.hide
|
|
};
|
|
},
|
|
computedDuration: function computedDuration() {
|
|
// Minimum supported duration is 1 second
|
|
return mathMax(toInteger(this.autoHideDelay, 0), MIN_DURATION);
|
|
},
|
|
computedToaster: function computedToaster() {
|
|
return String(this.toaster);
|
|
},
|
|
transitionHandlers: function transitionHandlers() {
|
|
return {
|
|
beforeEnter: this.onBeforeEnter,
|
|
afterEnter: this.onAfterEnter,
|
|
beforeLeave: this.onBeforeLeave,
|
|
afterLeave: this.onAfterLeave
|
|
};
|
|
},
|
|
computedAttrs: function computedAttrs() {
|
|
return _objectSpread2(_objectSpread2({}, this.bvAttrs), {}, {
|
|
id: this.safeId(),
|
|
tabindex: '0'
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
visible: function visible(newVal) {
|
|
newVal ? this.show() : this.hide();
|
|
},
|
|
localShow: function localShow(newVal) {
|
|
if (newVal !== this.visible) {
|
|
this.$emit('change', newVal);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
toaster: function toaster() {
|
|
// If toaster target changed, make sure toaster exists
|
|
this.$nextTick(this.ensureToaster);
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
static: function _static(newVal) {
|
|
// If static changes to true, and the toast is showing,
|
|
// ensure the toaster target exists
|
|
if (newVal && this.localShow) {
|
|
this.ensureToaster();
|
|
}
|
|
}
|
|
},
|
|
created: function created() {
|
|
// Create private non-reactive props
|
|
this.$_dismissTimer = null;
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
this.isMounted = true;
|
|
this.$nextTick(function () {
|
|
if (_this.visible) {
|
|
requestAF(function () {
|
|
_this.show();
|
|
});
|
|
}
|
|
}); // Listen for global $root show events
|
|
|
|
this.listenOnRoot('bv::show::toast', function (id) {
|
|
if (id === _this.safeId()) {
|
|
_this.show();
|
|
}
|
|
}); // Listen for global $root hide events
|
|
|
|
this.listenOnRoot('bv::hide::toast', function (id) {
|
|
if (!id || id === _this.safeId()) {
|
|
_this.hide();
|
|
}
|
|
}); // Make sure we hide when toaster is destroyed
|
|
|
|
/* istanbul ignore next: difficult to test */
|
|
|
|
this.listenOnRoot('bv::toaster::destroyed', function (toaster) {
|
|
/* istanbul ignore next */
|
|
if (toaster === _this.computedToaster) {
|
|
/* istanbul ignore next */
|
|
_this.hide();
|
|
}
|
|
});
|
|
},
|
|
beforeDestroy: function beforeDestroy() {
|
|
this.clearDismissTimer();
|
|
},
|
|
methods: {
|
|
show: function show() {
|
|
var _this2 = this;
|
|
|
|
if (!this.localShow) {
|
|
this.ensureToaster();
|
|
var showEvt = this.buildEvent('show');
|
|
this.emitEvent(showEvt);
|
|
this.dismissStarted = this.resumeDismiss = 0;
|
|
this.order = Date.now() * (this.appendToast ? 1 : -1);
|
|
this.isHiding = false;
|
|
this.doRender = true;
|
|
this.$nextTick(function () {
|
|
// We show the toast after we have rendered the portal and b-toast wrapper
|
|
// so that screen readers will properly announce the toast
|
|
requestAF(function () {
|
|
_this2.localShow = true;
|
|
});
|
|
});
|
|
}
|
|
},
|
|
hide: function hide() {
|
|
var _this3 = this;
|
|
|
|
if (this.localShow) {
|
|
var hideEvt = this.buildEvent('hide');
|
|
this.emitEvent(hideEvt);
|
|
this.setHoverHandler(false);
|
|
this.dismissStarted = this.resumeDismiss = 0;
|
|
this.clearDismissTimer();
|
|
this.isHiding = true;
|
|
requestAF(function () {
|
|
_this3.localShow = false;
|
|
});
|
|
}
|
|
},
|
|
buildEvent: function buildEvent(type) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
return new BvEvent(type, _objectSpread2(_objectSpread2({
|
|
cancelable: false,
|
|
target: this.$el || null,
|
|
relatedTarget: null
|
|
}, options), {}, {
|
|
vueTarget: this,
|
|
componentId: this.safeId()
|
|
}));
|
|
},
|
|
emitEvent: function emitEvent(bvEvt) {
|
|
var type = bvEvt.type;
|
|
this.emitOnRoot("bv::toast:".concat(type), bvEvt);
|
|
this.$emit(type, bvEvt);
|
|
},
|
|
ensureToaster: function ensureToaster() {
|
|
if (this.static) {
|
|
return;
|
|
}
|
|
|
|
if (!wormhole.hasTarget(this.computedToaster)) {
|
|
var div = document.createElement('div');
|
|
document.body.appendChild(div);
|
|
var toaster = new BToaster({
|
|
parent: this.$root,
|
|
propsData: {
|
|
name: this.computedToaster
|
|
}
|
|
});
|
|
toaster.$mount(div);
|
|
}
|
|
},
|
|
startDismissTimer: function startDismissTimer() {
|
|
this.clearDismissTimer();
|
|
|
|
if (!this.noAutoHide) {
|
|
this.$_dismissTimer = setTimeout(this.hide, this.resumeDismiss || this.computedDuration);
|
|
this.dismissStarted = Date.now();
|
|
this.resumeDismiss = 0;
|
|
}
|
|
},
|
|
clearDismissTimer: function clearDismissTimer() {
|
|
clearTimeout(this.$_dismissTimer);
|
|
this.$_dismissTimer = null;
|
|
},
|
|
setHoverHandler: function setHoverHandler(on) {
|
|
var el = this.$refs['b-toast'];
|
|
eventOnOff(on, el, 'mouseenter', this.onPause, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOnOff(on, el, 'mouseleave', this.onUnPause, EVENT_OPTIONS_NO_CAPTURE);
|
|
},
|
|
onPause: function onPause() {
|
|
// Determine time remaining, and then pause timer
|
|
if (this.noAutoHide || this.noHoverPause || !this.$_dismissTimer || this.resumeDismiss) {
|
|
return;
|
|
}
|
|
|
|
var passed = Date.now() - this.dismissStarted;
|
|
|
|
if (passed > 0) {
|
|
this.clearDismissTimer();
|
|
this.resumeDismiss = mathMax(this.computedDuration - passed, MIN_DURATION);
|
|
}
|
|
},
|
|
onUnPause: function onUnPause() {
|
|
// Restart timer with max of time remaining or 1 second
|
|
if (this.noAutoHide || this.noHoverPause || !this.resumeDismiss) {
|
|
this.resumeDismiss = this.dismissStarted = 0;
|
|
return;
|
|
}
|
|
|
|
this.startDismissTimer();
|
|
},
|
|
onLinkClick: function onLinkClick() {
|
|
var _this4 = this;
|
|
|
|
// We delay the close to allow time for the
|
|
// browser to process the link click
|
|
this.$nextTick(function () {
|
|
requestAF(function () {
|
|
_this4.hide();
|
|
});
|
|
});
|
|
},
|
|
onBeforeEnter: function onBeforeEnter() {
|
|
this.isTransitioning = true;
|
|
},
|
|
onAfterEnter: function onAfterEnter() {
|
|
this.isTransitioning = false;
|
|
var hiddenEvt = this.buildEvent('shown');
|
|
this.emitEvent(hiddenEvt);
|
|
this.startDismissTimer();
|
|
this.setHoverHandler(true);
|
|
},
|
|
onBeforeLeave: function onBeforeLeave() {
|
|
this.isTransitioning = true;
|
|
},
|
|
onAfterLeave: function onAfterLeave() {
|
|
this.isTransitioning = false;
|
|
this.order = 0;
|
|
this.resumeDismiss = this.dismissStarted = 0;
|
|
var hiddenEvt = this.buildEvent('hidden');
|
|
this.emitEvent(hiddenEvt);
|
|
this.doRender = false;
|
|
},
|
|
makeToast: function makeToast(h) {
|
|
var _this5 = this;
|
|
|
|
// Render helper for generating the toast
|
|
// Assemble the header content
|
|
var $headerContent = [];
|
|
var $title = this.normalizeSlot('toast-title', this.slotScope);
|
|
|
|
if ($title) {
|
|
$headerContent.push($title);
|
|
} else if (this.title) {
|
|
$headerContent.push(h('strong', {
|
|
staticClass: 'mr-2'
|
|
}, this.title));
|
|
}
|
|
|
|
if (!this.noCloseButton) {
|
|
$headerContent.push(h(BButtonClose, {
|
|
staticClass: 'ml-auto mb-1',
|
|
on: {
|
|
click: function click() {
|
|
_this5.hide();
|
|
}
|
|
}
|
|
}));
|
|
} // Assemble the header (if needed)
|
|
|
|
|
|
var $header = h();
|
|
|
|
if ($headerContent.length > 0) {
|
|
$header = h('header', {
|
|
staticClass: 'toast-header',
|
|
class: this.headerClass
|
|
}, $headerContent);
|
|
} // Toast body
|
|
|
|
|
|
var link = isLink(this);
|
|
var $body = h(link ? BLink : 'div', {
|
|
staticClass: 'toast-body',
|
|
class: this.bodyClass,
|
|
props: link ? pluckProps(linkProps$5, this) : {},
|
|
on: link ? {
|
|
click: this.onLinkClick
|
|
} : {}
|
|
}, [this.normalizeSlot(SLOT_NAME_DEFAULT, this.slotScope) || h()]); // Build the toast
|
|
|
|
var $toast = h('div', {
|
|
key: "toast-".concat(this._uid),
|
|
ref: 'toast',
|
|
staticClass: 'toast',
|
|
class: this.toastClass,
|
|
attrs: this.computedAttrs
|
|
}, [$header, $body]);
|
|
return $toast;
|
|
}
|
|
},
|
|
render: function render(h) {
|
|
if (!this.doRender || !this.isMounted) {
|
|
return h();
|
|
}
|
|
|
|
var name = "b-toast-".concat(this._uid); // If scoped styles are applied and the toast is not static,
|
|
// make sure the scoped style data attribute is applied
|
|
|
|
var scopedStyleAttrs = !this.static ? this.scopedStyleAttrs : {};
|
|
return h(Portal, {
|
|
props: {
|
|
name: name,
|
|
to: this.computedToaster,
|
|
order: this.order,
|
|
slim: true,
|
|
disabled: this.static
|
|
}
|
|
}, [h('div', {
|
|
key: name,
|
|
ref: 'b-toast',
|
|
staticClass: 'b-toast',
|
|
class: this.bToastClasses,
|
|
attrs: _objectSpread2(_objectSpread2({}, scopedStyleAttrs), {}, {
|
|
id: this.safeId('_toast_outer'),
|
|
role: this.isHiding ? null : this.isStatus ? 'status' : 'alert',
|
|
'aria-live': this.isHiding ? null : this.isStatus ? 'polite' : 'assertive',
|
|
'aria-atomic': this.isHiding ? null : 'true'
|
|
})
|
|
}, [h(BVTransition, {
|
|
props: {
|
|
noFade: this.noFade
|
|
},
|
|
on: this.transitionHandlers
|
|
}, [this.localShow ? this.makeToast(h) : h()])])]);
|
|
}
|
|
});
|
|
|
|
var PROP_NAME$2 = '$bvToast';
|
|
var PROP_NAME_PRIV$1 = '_bv__toast'; // Base toast props that are allowed
|
|
// Some may be ignored or overridden on some message boxes
|
|
// Prop ID is allowed, but really only should be used for testing
|
|
// We need to add it in explicitly as it comes from the `idMixin`
|
|
|
|
var BASE_PROPS$1 = ['id'].concat(_toConsumableArray(keys(omit(props$1b, ['static', 'visible'])))); // Map prop names to toast slot names
|
|
|
|
var propsToSlots$1 = {
|
|
toastContent: 'default',
|
|
title: 'toast-title'
|
|
}; // --- Utility methods ---
|
|
// Method to filter only recognized props that are not undefined
|
|
|
|
var filterOptions$1 = function filterOptions(options) {
|
|
return BASE_PROPS$1.reduce(function (memo, key) {
|
|
if (!isUndefined(options[key])) {
|
|
memo[key] = options[key];
|
|
}
|
|
|
|
return memo;
|
|
}, {});
|
|
}; // Method to install `$bvToast` VM injection
|
|
|
|
|
|
var plugin$1 = function plugin(Vue) {
|
|
// Create a private sub-component constructor that
|
|
// extends BToast and self-destructs after hidden
|
|
// @vue/component
|
|
var BVToastPop = Vue.extend({
|
|
name: NAME_TOAST_POP,
|
|
extends: BToast,
|
|
destroyed: function destroyed() {
|
|
// Make sure we not in document any more
|
|
if (this.$el && this.$el.parentNode) {
|
|
this.$el.parentNode.removeChild(this.$el);
|
|
}
|
|
},
|
|
mounted: function mounted() {
|
|
var _this = this;
|
|
|
|
// Self destruct handler
|
|
var handleDestroy = function handleDestroy() {
|
|
// Ensure the toast has been force hidden
|
|
_this.localShow = false;
|
|
_this.doRender = false;
|
|
|
|
_this.$nextTick(function () {
|
|
_this.$nextTick(function () {
|
|
// In a `requestAF()` to release control back to application
|
|
// and to allow the portal-target time to remove the content
|
|
requestAF(function () {
|
|
_this.$destroy();
|
|
});
|
|
});
|
|
});
|
|
}; // Self destruct if parent destroyed
|
|
|
|
|
|
this.$parent.$once('hook:destroyed', handleDestroy); // Self destruct after hidden
|
|
|
|
this.$once('hidden', handleDestroy); // Self destruct when toaster is destroyed
|
|
|
|
this.listenOnRoot('bv::toaster::destroyed', function (toaster) {
|
|
/* istanbul ignore next: hard to test */
|
|
if (toaster === _this.toaster) {
|
|
handleDestroy();
|
|
}
|
|
});
|
|
}
|
|
}); // Private method to generate the on-demand toast
|
|
|
|
var makeToast = function makeToast(props, $parent) {
|
|
if (warnNotClient(PROP_NAME$2)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
} // Create an instance of `BVToastPop` component
|
|
|
|
|
|
var toast = new BVToastPop({
|
|
// We set parent as the local VM so these toasts can emit events on the
|
|
// app `$root`, and it ensures `BToast` is destroyed when parent is destroyed
|
|
parent: $parent,
|
|
propsData: _objectSpread2(_objectSpread2(_objectSpread2({}, filterOptions$1(getComponentConfig(NAME_TOAST))), omit(props, keys(propsToSlots$1))), {}, {
|
|
// Props that can't be overridden
|
|
static: false,
|
|
visible: true
|
|
})
|
|
}); // Convert certain props to slots
|
|
|
|
keys(propsToSlots$1).forEach(function (prop) {
|
|
var value = props[prop];
|
|
|
|
if (!isUndefined(value)) {
|
|
// Can be a string, or array of VNodes
|
|
if (prop === 'title' && isString(value)) {
|
|
// Special case for title if it is a string, we wrap in a <strong>
|
|
value = [$parent.$createElement('strong', {
|
|
class: 'mr-2'
|
|
}, value)];
|
|
}
|
|
|
|
toast.$slots[propsToSlots$1[prop]] = concat(value);
|
|
}
|
|
}); // Create a mount point (a DIV) and mount it (which triggers the show)
|
|
|
|
var div = document.createElement('div');
|
|
document.body.appendChild(div);
|
|
toast.$mount(div);
|
|
}; // Declare BvToast instance property class
|
|
|
|
|
|
var BvToast = /*#__PURE__*/function () {
|
|
function BvToast(vm) {
|
|
_classCallCheck(this, BvToast);
|
|
|
|
// Assign the new properties to this instance
|
|
assign(this, {
|
|
_vm: vm,
|
|
_root: vm.$root
|
|
}); // Set these properties as read-only and non-enumerable
|
|
|
|
defineProperties(this, {
|
|
_vm: readonlyDescriptor(),
|
|
_root: readonlyDescriptor()
|
|
});
|
|
} // --- Public Instance methods ---
|
|
// Opens a user defined toast and returns immediately
|
|
|
|
|
|
_createClass(BvToast, [{
|
|
key: "toast",
|
|
value: function toast(content) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
if (!content || warnNotClient(PROP_NAME$2)) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
makeToast(_objectSpread2(_objectSpread2({}, filterOptions$1(options)), {}, {
|
|
toastContent: content
|
|
}), this._vm);
|
|
} // shows a `<b-toast>` component with the specified ID
|
|
|
|
}, {
|
|
key: "show",
|
|
value: function show(id) {
|
|
if (id) {
|
|
this._root.$emit('bv::show::toast', id);
|
|
}
|
|
} // Hide a toast with specified ID, or if not ID all toasts
|
|
|
|
}, {
|
|
key: "hide",
|
|
value: function hide() {
|
|
var id = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
|
|
this._root.$emit('bv::hide::toast', id);
|
|
}
|
|
}]);
|
|
|
|
return BvToast;
|
|
}(); // Add our instance mixin
|
|
|
|
|
|
Vue.mixin({
|
|
beforeCreate: function beforeCreate() {
|
|
// Because we need access to `$root` for `$emits`, and VM for parenting,
|
|
// we have to create a fresh instance of `BvToast` for each VM
|
|
this[PROP_NAME_PRIV$1] = new BvToast(this);
|
|
}
|
|
}); // Define our read-only `$bvToast` instance property
|
|
// Placed in an if just in case in HMR mode
|
|
|
|
if (!hasOwnProperty(Vue.prototype, PROP_NAME$2)) {
|
|
defineProperty(Vue.prototype, PROP_NAME$2, {
|
|
get: function get() {
|
|
/* istanbul ignore next */
|
|
if (!this || !this[PROP_NAME_PRIV$1]) {
|
|
warn("\"".concat(PROP_NAME$2, "\" must be accessed from a Vue instance \"this\" context."), NAME_TOAST);
|
|
}
|
|
|
|
return this[PROP_NAME_PRIV$1];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
var BVToastPlugin = /*#__PURE__*/pluginFactory({
|
|
plugins: {
|
|
plugin: plugin$1
|
|
}
|
|
});
|
|
|
|
var ToastPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BToast: BToast,
|
|
BToaster: BToaster
|
|
},
|
|
// $bvToast injection
|
|
plugins: {
|
|
BVToastPlugin: BVToastPlugin
|
|
}
|
|
});
|
|
|
|
var BV_TOOLTIP = '__BV_Tooltip__'; // Default trigger
|
|
|
|
var DefaultTrigger$1 = 'hover focus'; // Valid event triggers
|
|
|
|
var validTriggers$1 = {
|
|
focus: true,
|
|
hover: true,
|
|
click: true,
|
|
blur: true,
|
|
manual: true
|
|
}; // Directive modifier test regular expressions. Pre-compile for performance
|
|
|
|
var htmlRE$1 = /^html$/i;
|
|
var noninteractiveRE = /^noninteractive$/i;
|
|
var noFadeRE$1 = /^nofade$/i;
|
|
var placementRE$1 = /^(auto|top(left|right)?|bottom(left|right)?|left(top|bottom)?|right(top|bottom)?)$/i;
|
|
var boundaryRE$1 = /^(window|viewport|scrollParent)$/i;
|
|
var delayRE$1 = /^d\d+$/i;
|
|
var delayShowRE$1 = /^ds\d+$/i;
|
|
var delayHideRE$1 = /^dh\d+$/i;
|
|
var offsetRE$1 = /^o-?\d+$/i;
|
|
var variantRE$1 = /^v-.+$/i;
|
|
var spacesRE$1 = /\s+/; // Build a Tooltip config based on bindings (if any)
|
|
// Arguments and modifiers take precedence over passed value config object
|
|
|
|
var parseBindings$1 = function parseBindings(bindings, vnode)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
// We start out with a basic config
|
|
var config = {
|
|
title: undefined,
|
|
trigger: '',
|
|
// Default set below if needed
|
|
placement: 'top',
|
|
fallbackPlacement: 'flip',
|
|
container: false,
|
|
// Default of body
|
|
animation: true,
|
|
offset: 0,
|
|
id: null,
|
|
html: false,
|
|
interactive: true,
|
|
disabled: false,
|
|
delay: getComponentConfig(NAME_TOOLTIP, 'delay', 50),
|
|
boundary: String(getComponentConfig(NAME_TOOLTIP, 'boundary', 'scrollParent')),
|
|
boundaryPadding: toInteger(getComponentConfig(NAME_TOOLTIP, 'boundaryPadding', 5), 0),
|
|
variant: getComponentConfig(NAME_TOOLTIP, 'variant'),
|
|
customClass: getComponentConfig(NAME_TOOLTIP, 'customClass')
|
|
}; // Process `bindings.value`
|
|
|
|
if (isString(bindings.value) || isNumber(bindings.value)) {
|
|
// Value is tooltip content (HTML optionally supported)
|
|
config.title = bindings.value;
|
|
} else if (isFunction(bindings.value)) {
|
|
// Title generator function
|
|
config.title = bindings.value;
|
|
} else if (isPlainObject(bindings.value)) {
|
|
// Value is config object, so merge
|
|
config = _objectSpread2(_objectSpread2({}, config), bindings.value);
|
|
} // 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)
|
|
};
|
|
} // 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);
|
|
} // Process modifiers
|
|
|
|
|
|
keys(bindings.modifiers).forEach(function (mod) {
|
|
if (htmlRE$1.test(mod)) {
|
|
// Title allows HTML
|
|
config.html = true;
|
|
} else if (noninteractiveRE.test(mod)) {
|
|
// Noninteractive
|
|
config.interactive = false;
|
|
} else if (noFadeRE$1.test(mod)) {
|
|
// No animation
|
|
config.animation = false;
|
|
} else if (placementRE$1.test(mod)) {
|
|
// Placement of tooltip
|
|
config.placement = mod;
|
|
} else if (boundaryRE$1.test(mod)) {
|
|
// Boundary of tooltip
|
|
mod = mod === 'scrollparent' ? 'scrollParent' : mod;
|
|
config.boundary = mod;
|
|
} else if (delayRE$1.test(mod)) {
|
|
// Delay value
|
|
var delay = toInteger(mod.slice(1), 0);
|
|
config.delay.show = delay;
|
|
config.delay.hide = delay;
|
|
} else if (delayShowRE$1.test(mod)) {
|
|
// Delay show value
|
|
config.delay.show = toInteger(mod.slice(2), 0);
|
|
} else if (delayHideRE$1.test(mod)) {
|
|
// Delay hide value
|
|
config.delay.hide = toInteger(mod.slice(2), 0);
|
|
} else if (offsetRE$1.test(mod)) {
|
|
// Offset value, negative allowed
|
|
config.offset = toInteger(mod.slice(1), 0);
|
|
} else if (variantRE$1.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$1).forEach(function (trigger) {
|
|
if (validTriggers$1[trigger]) {
|
|
selectedTriggers[trigger] = true;
|
|
}
|
|
}); // Parse modifiers for triggers
|
|
|
|
keys(bindings.modifiers).forEach(function (mod) {
|
|
mod = mod.toLowerCase();
|
|
|
|
if (validTriggers$1[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$1;
|
|
} // Return the config
|
|
|
|
|
|
return config;
|
|
}; // Add/update Tooltip on our element
|
|
|
|
|
|
var applyTooltip = function applyTooltip(el, bindings, vnode) {
|
|
if (!isBrowser) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
var config = parseBindings$1(bindings, vnode);
|
|
|
|
if (!el[BV_TOOLTIP]) {
|
|
var $parent = vnode.context;
|
|
el[BV_TOOLTIP] = new BVTooltip({
|
|
parent: $parent,
|
|
// Add the parent's scoped style attribute data
|
|
_scopeId: getScopeId($parent, undefined)
|
|
});
|
|
el[BV_TOOLTIP].__bv_prev_data__ = {};
|
|
el[BV_TOOLTIP].$on('show', function ()
|
|
/* istanbul ignore next: for now */
|
|
{
|
|
// Before showing the tooltip, we update the title if it is a function
|
|
if (isFunction(config.title)) {
|
|
el[BV_TOOLTIP].updateData({
|
|
title: config.title(el)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
var data = {
|
|
title: config.title,
|
|
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,
|
|
interactive: config.interactive,
|
|
disabled: config.disabled,
|
|
html: config.html
|
|
};
|
|
var oldData = el[BV_TOOLTIP].__bv_prev_data__;
|
|
el[BV_TOOLTIP].__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 is a function, we execute it here
|
|
newData[prop] = prop === 'title' && isFunction(data[prop]) ? data[prop](el) : data[prop];
|
|
}
|
|
});
|
|
el[BV_TOOLTIP].updateData(newData);
|
|
}
|
|
}; // Remove Tooltip on our element
|
|
|
|
|
|
var removeTooltip = function removeTooltip(el) {
|
|
if (el[BV_TOOLTIP]) {
|
|
el[BV_TOOLTIP].$destroy();
|
|
el[BV_TOOLTIP] = null;
|
|
}
|
|
|
|
delete el[BV_TOOLTIP];
|
|
}; // Export our directive
|
|
|
|
|
|
var VBTooltip = {
|
|
bind: function bind(el, bindings, vnode) {
|
|
applyTooltip(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 render update loops
|
|
vnode.context.$nextTick(function () {
|
|
applyTooltip(el, bindings, vnode);
|
|
});
|
|
},
|
|
unbind: function unbind(el) {
|
|
removeTooltip(el);
|
|
}
|
|
};
|
|
|
|
var VBTooltipPlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBTooltip: VBTooltip
|
|
}
|
|
});
|
|
|
|
var TooltipPlugin = /*#__PURE__*/pluginFactory({
|
|
components: {
|
|
BTooltip: BTooltip
|
|
},
|
|
plugins: {
|
|
VBTooltipPlugin: VBTooltipPlugin
|
|
}
|
|
});
|
|
|
|
var componentsPlugin = /*#__PURE__*/pluginFactory({
|
|
plugins: {
|
|
AlertPlugin: AlertPlugin,
|
|
AspectPlugin: AspectPlugin,
|
|
AvatarPlugin: AvatarPlugin,
|
|
BadgePlugin: BadgePlugin,
|
|
BreadcrumbPlugin: BreadcrumbPlugin,
|
|
ButtonPlugin: ButtonPlugin,
|
|
ButtonGroupPlugin: ButtonGroupPlugin,
|
|
ButtonToolbarPlugin: ButtonToolbarPlugin,
|
|
CalendarPlugin: CalendarPlugin,
|
|
CardPlugin: CardPlugin,
|
|
CarouselPlugin: CarouselPlugin,
|
|
CollapsePlugin: CollapsePlugin,
|
|
DropdownPlugin: DropdownPlugin,
|
|
EmbedPlugin: EmbedPlugin,
|
|
FormPlugin: FormPlugin,
|
|
FormCheckboxPlugin: FormCheckboxPlugin,
|
|
FormDatepickerPlugin: FormDatepickerPlugin,
|
|
FormFilePlugin: FormFilePlugin,
|
|
FormGroupPlugin: FormGroupPlugin,
|
|
FormInputPlugin: FormInputPlugin,
|
|
FormRadioPlugin: FormRadioPlugin,
|
|
FormRatingPlugin: FormRatingPlugin,
|
|
FormSelectPlugin: FormSelectPlugin,
|
|
FormSpinbuttonPlugin: FormSpinbuttonPlugin,
|
|
FormTagsPlugin: FormTagsPlugin,
|
|
FormTextareaPlugin: FormTextareaPlugin,
|
|
FormTimepickerPlugin: FormTimepickerPlugin,
|
|
ImagePlugin: ImagePlugin,
|
|
InputGroupPlugin: InputGroupPlugin,
|
|
JumbotronPlugin: JumbotronPlugin,
|
|
LayoutPlugin: LayoutPlugin,
|
|
LinkPlugin: LinkPlugin,
|
|
ListGroupPlugin: ListGroupPlugin,
|
|
MediaPlugin: MediaPlugin,
|
|
ModalPlugin: ModalPlugin,
|
|
NavPlugin: NavPlugin,
|
|
NavbarPlugin: NavbarPlugin,
|
|
OverlayPlugin: OverlayPlugin,
|
|
PaginationPlugin: PaginationPlugin,
|
|
PaginationNavPlugin: PaginationNavPlugin,
|
|
PopoverPlugin: PopoverPlugin,
|
|
ProgressPlugin: ProgressPlugin,
|
|
SidebarPlugin: SidebarPlugin,
|
|
SkeletonPlugin: SkeletonPlugin,
|
|
SpinnerPlugin: SpinnerPlugin,
|
|
TablePlugin: TablePlugin,
|
|
TabsPlugin: TabsPlugin,
|
|
TimePlugin: TimePlugin,
|
|
ToastPlugin: ToastPlugin,
|
|
TooltipPlugin: TooltipPlugin
|
|
}
|
|
});
|
|
|
|
var VBHoverPlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBHover: VBHover
|
|
}
|
|
});
|
|
|
|
var VBModalPlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBModal: VBModal
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Constants / Defaults
|
|
*/
|
|
|
|
var NAME$1 = 'v-b-scrollspy';
|
|
var ACTIVATE_EVENT = 'bv::scrollspy::activate';
|
|
var CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';
|
|
var CLASS_NAME_ACTIVE = 'active';
|
|
var SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';
|
|
var SELECTOR_NAV_LINKS = '.nav-link';
|
|
var SELECTOR_NAV_ITEMS = '.nav-item';
|
|
var SELECTOR_LIST_ITEMS = '.list-group-item';
|
|
var SELECTOR_DROPDOWN = '.dropdown, .dropup';
|
|
var SELECTOR_DROPDOWN_ITEMS = '.dropdown-item';
|
|
var SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
|
|
var METHOD_OFFSET = 'offset';
|
|
var METHOD_POSITION = 'position';
|
|
var Default = {
|
|
element: 'body',
|
|
offset: 10,
|
|
method: 'auto',
|
|
throttle: 75
|
|
};
|
|
var DefaultType = {
|
|
element: '(string|element|component)',
|
|
offset: 'number',
|
|
method: 'string',
|
|
throttle: 'number'
|
|
}; // Transition Events
|
|
|
|
var TransitionEndEvents$1 = ['webkitTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd'];
|
|
/*
|
|
* Utility Methods
|
|
*/
|
|
// Better var type detection
|
|
|
|
var toType$1 = function toType(obj)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
return toString(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
|
|
}; // Check config properties for expected types
|
|
|
|
/* istanbul ignore next */
|
|
|
|
|
|
var typeCheckConfig = function typeCheckConfig(componentName, config, configTypes)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
for (var property in configTypes) {
|
|
if (hasOwnProperty(configTypes, property)) {
|
|
var expectedTypes = configTypes[property];
|
|
var value = config[property];
|
|
var valueType = value && isElement(value) ? 'element' : toType$1(value); // handle Vue instances
|
|
|
|
valueType = value && value._isVue ? 'component' : valueType;
|
|
|
|
if (!new RegExp(expectedTypes).test(valueType)) {
|
|
/* istanbul ignore next */
|
|
warn("".concat(componentName, ": Option \"").concat(property, "\" provided type \"").concat(valueType, "\" but expected type \"").concat(expectedTypes, "\""));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/*
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
|
|
|
|
var ScrollSpy
|
|
/* istanbul ignore next: not easy to test */
|
|
= /*#__PURE__*/function () {
|
|
function ScrollSpy(element, config, $root) {
|
|
_classCallCheck(this, ScrollSpy);
|
|
|
|
// The element we activate links in
|
|
this.$el = element;
|
|
this.$scroller = null;
|
|
this.$selector = [SELECTOR_NAV_LINKS, SELECTOR_LIST_ITEMS, SELECTOR_DROPDOWN_ITEMS].join(',');
|
|
this.$offsets = [];
|
|
this.$targets = [];
|
|
this.$activeTarget = null;
|
|
this.$scrollHeight = 0;
|
|
this.$resizeTimeout = null;
|
|
this.$scrollerObserver = null;
|
|
this.$targetsObserver = null;
|
|
this.$root = $root || null;
|
|
this.$config = null;
|
|
this.updateConfig(config);
|
|
}
|
|
|
|
_createClass(ScrollSpy, [{
|
|
key: "updateConfig",
|
|
value: function updateConfig(config, $root) {
|
|
if (this.$scroller) {
|
|
// Just in case out scroll element has changed
|
|
this.unlisten();
|
|
this.$scroller = null;
|
|
}
|
|
|
|
var cfg = _objectSpread2(_objectSpread2({}, this.constructor.Default), config);
|
|
|
|
if ($root) {
|
|
this.$root = $root;
|
|
}
|
|
|
|
typeCheckConfig(this.constructor.Name, cfg, this.constructor.DefaultType);
|
|
this.$config = cfg;
|
|
|
|
if (this.$root) {
|
|
var self = this;
|
|
this.$root.$nextTick(function () {
|
|
self.listen();
|
|
});
|
|
} else {
|
|
this.listen();
|
|
}
|
|
}
|
|
}, {
|
|
key: "dispose",
|
|
value: function dispose() {
|
|
this.unlisten();
|
|
clearTimeout(this.$resizeTimeout);
|
|
this.$resizeTimeout = null;
|
|
this.$el = null;
|
|
this.$config = null;
|
|
this.$scroller = null;
|
|
this.$selector = null;
|
|
this.$offsets = null;
|
|
this.$targets = null;
|
|
this.$activeTarget = null;
|
|
this.$scrollHeight = null;
|
|
}
|
|
}, {
|
|
key: "listen",
|
|
value: function listen() {
|
|
var _this = this;
|
|
|
|
var scroller = this.getScroller();
|
|
|
|
if (scroller && scroller.tagName !== 'BODY') {
|
|
eventOn(scroller, 'scroll', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
|
|
eventOn(window, 'scroll', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOn(window, 'resize', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOn(window, 'orientationchange', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
TransitionEndEvents$1.forEach(function (evtName) {
|
|
eventOn(window, evtName, _this, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
this.setObservers(true); // Schedule a refresh
|
|
|
|
this.handleEvent('refresh');
|
|
}
|
|
}, {
|
|
key: "unlisten",
|
|
value: function unlisten() {
|
|
var _this2 = this;
|
|
|
|
var scroller = this.getScroller();
|
|
this.setObservers(false);
|
|
|
|
if (scroller && scroller.tagName !== 'BODY') {
|
|
eventOff(scroller, 'scroll', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
}
|
|
|
|
eventOff(window, 'scroll', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOff(window, 'resize', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
eventOff(window, 'orientationchange', this, EVENT_OPTIONS_NO_CAPTURE);
|
|
TransitionEndEvents$1.forEach(function (evtName) {
|
|
eventOff(window, evtName, _this2, EVENT_OPTIONS_NO_CAPTURE);
|
|
});
|
|
}
|
|
}, {
|
|
key: "setObservers",
|
|
value: function setObservers(on) {
|
|
var _this3 = this;
|
|
|
|
// We observe both the scroller for content changes, and the target links
|
|
this.$scrollerObserver && this.$scrollerObserver.disconnect();
|
|
this.$targetsObserver && this.$targetsObserver.disconnect();
|
|
this.$scrollerObserver = null;
|
|
this.$targetsObserver = null;
|
|
|
|
if (on) {
|
|
this.$targetsObserver = observeDom(this.$el, function () {
|
|
_this3.handleEvent('mutation');
|
|
}, {
|
|
subtree: true,
|
|
childList: true,
|
|
attributes: true,
|
|
attributeFilter: ['href']
|
|
});
|
|
this.$scrollerObserver = observeDom(this.getScroller(), function () {
|
|
_this3.handleEvent('mutation');
|
|
}, {
|
|
subtree: true,
|
|
childList: true,
|
|
characterData: true,
|
|
attributes: true,
|
|
attributeFilter: ['id', 'style', 'class']
|
|
});
|
|
}
|
|
} // General event handler
|
|
|
|
}, {
|
|
key: "handleEvent",
|
|
value: function handleEvent(evt) {
|
|
var type = isString(evt) ? evt : evt.type;
|
|
var self = this;
|
|
|
|
var resizeThrottle = function resizeThrottle() {
|
|
if (!self.$resizeTimeout) {
|
|
self.$resizeTimeout = setTimeout(function () {
|
|
self.refresh();
|
|
self.process();
|
|
self.$resizeTimeout = null;
|
|
}, self.$config.throttle);
|
|
}
|
|
};
|
|
|
|
if (type === 'scroll') {
|
|
if (!this.$scrollerObserver) {
|
|
// Just in case we are added to the DOM before the scroll target is
|
|
// We re-instantiate our listeners, just in case
|
|
this.listen();
|
|
}
|
|
|
|
this.process();
|
|
} else if (/(resize|orientationchange|mutation|refresh)/.test(type)) {
|
|
// Postpone these events by throttle time
|
|
resizeThrottle();
|
|
}
|
|
} // Refresh the list of target links on the element we are applied to
|
|
|
|
}, {
|
|
key: "refresh",
|
|
value: function refresh() {
|
|
var _this4 = this;
|
|
|
|
var scroller = this.getScroller();
|
|
|
|
if (!scroller) {
|
|
return;
|
|
}
|
|
|
|
var autoMethod = scroller !== scroller.window ? METHOD_POSITION : METHOD_OFFSET;
|
|
var method = this.$config.method === 'auto' ? autoMethod : this.$config.method;
|
|
var methodFn = method === METHOD_POSITION ? position : offset;
|
|
var offsetBase = method === METHOD_POSITION ? this.getScrollTop() : 0;
|
|
this.$offsets = [];
|
|
this.$targets = [];
|
|
this.$scrollHeight = this.getScrollHeight(); // Find all the unique link HREFs that we will control
|
|
|
|
selectAll(this.$selector, this.$el) // Get HREF value
|
|
.map(function (link) {
|
|
return getAttr(link, 'href');
|
|
}) // Filter out HREFs that do not match our RegExp
|
|
.filter(function (href) {
|
|
return href && RX_HREF.test(href || '');
|
|
}) // Find all elements with ID that match HREF hash
|
|
.map(function (href) {
|
|
// Convert HREF into an ID (including # at beginning)
|
|
var id = href.replace(RX_HREF, '$1').trim();
|
|
|
|
if (!id) {
|
|
return null;
|
|
} // Find the element with the ID specified by id
|
|
|
|
|
|
var el = select(id, scroller);
|
|
|
|
if (el && isVisible(el)) {
|
|
return {
|
|
offset: toInteger(methodFn(el).top, 0) + offsetBase,
|
|
target: id
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}).filter(Boolean) // Sort them by their offsets (smallest first)
|
|
.sort(function (a, b) {
|
|
return a.offset - b.offset;
|
|
}) // record only unique targets/offsets
|
|
.reduce(function (memo, item) {
|
|
if (!memo[item.target]) {
|
|
_this4.$offsets.push(item.offset);
|
|
|
|
_this4.$targets.push(item.target);
|
|
|
|
memo[item.target] = true;
|
|
}
|
|
|
|
return memo;
|
|
}, {}); // Return this for easy chaining
|
|
|
|
return this;
|
|
} // Handle activating/clearing
|
|
|
|
}, {
|
|
key: "process",
|
|
value: function process() {
|
|
var scrollTop = this.getScrollTop() + this.$config.offset;
|
|
var scrollHeight = this.getScrollHeight();
|
|
var maxScroll = this.$config.offset + scrollHeight - this.getOffsetHeight();
|
|
|
|
if (this.$scrollHeight !== scrollHeight) {
|
|
this.refresh();
|
|
}
|
|
|
|
if (scrollTop >= maxScroll) {
|
|
var target = this.$targets[this.$targets.length - 1];
|
|
|
|
if (this.$activeTarget !== target) {
|
|
this.activate(target);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.$activeTarget && scrollTop < this.$offsets[0] && this.$offsets[0] > 0) {
|
|
this.$activeTarget = null;
|
|
this.clear();
|
|
return;
|
|
}
|
|
|
|
for (var i = this.$offsets.length; i--;) {
|
|
var isActiveTarget = this.$activeTarget !== this.$targets[i] && scrollTop >= this.$offsets[i] && (isUndefined(this.$offsets[i + 1]) || scrollTop < this.$offsets[i + 1]);
|
|
|
|
if (isActiveTarget) {
|
|
this.activate(this.$targets[i]);
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: "getScroller",
|
|
value: function getScroller() {
|
|
if (this.$scroller) {
|
|
return this.$scroller;
|
|
}
|
|
|
|
var scroller = this.$config.element;
|
|
|
|
if (!scroller) {
|
|
return null;
|
|
} else if (isElement(scroller.$el)) {
|
|
scroller = scroller.$el;
|
|
} else if (isString(scroller)) {
|
|
scroller = select(scroller);
|
|
}
|
|
|
|
if (!scroller) {
|
|
return null;
|
|
}
|
|
|
|
this.$scroller = scroller.tagName === 'BODY' ? window : scroller;
|
|
return this.$scroller;
|
|
}
|
|
}, {
|
|
key: "getScrollTop",
|
|
value: function getScrollTop() {
|
|
var scroller = this.getScroller();
|
|
return scroller === window ? scroller.pageYOffset : scroller.scrollTop;
|
|
}
|
|
}, {
|
|
key: "getScrollHeight",
|
|
value: function getScrollHeight() {
|
|
return this.getScroller().scrollHeight || mathMax(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
}
|
|
}, {
|
|
key: "getOffsetHeight",
|
|
value: function getOffsetHeight() {
|
|
var scroller = this.getScroller();
|
|
return scroller === window ? window.innerHeight : getBCR(scroller).height;
|
|
}
|
|
}, {
|
|
key: "activate",
|
|
value: function activate(target) {
|
|
var _this5 = this;
|
|
|
|
this.$activeTarget = target;
|
|
this.clear(); // Grab the list of target links (<a href="{$target}">)
|
|
|
|
var links = selectAll(this.$selector // Split out the base selectors
|
|
.split(',') // Map to a selector that matches links with HREF ending in the ID (including '#')
|
|
.map(function (selector) {
|
|
return "".concat(selector, "[href$=\"").concat(target, "\"]");
|
|
}) // Join back into a single selector string
|
|
.join(','), this.$el);
|
|
links.forEach(function (link) {
|
|
if (hasClass(link, CLASS_NAME_DROPDOWN_ITEM)) {
|
|
// This is a dropdown item, so find the .dropdown-toggle and set its state
|
|
var dropdown = closest(SELECTOR_DROPDOWN, link);
|
|
|
|
if (dropdown) {
|
|
_this5.setActiveState(select(SELECTOR_DROPDOWN_TOGGLE, dropdown), true);
|
|
} // Also set this link's state
|
|
|
|
|
|
_this5.setActiveState(link, true);
|
|
} else {
|
|
// Set triggered link as active
|
|
_this5.setActiveState(link, true);
|
|
|
|
if (matches(link.parentElement, SELECTOR_NAV_ITEMS)) {
|
|
// Handle nav-link inside nav-item, and set nav-item active
|
|
_this5.setActiveState(link.parentElement, true);
|
|
} // Set triggered links parents as active
|
|
// With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
|
|
|
|
|
|
var el = link;
|
|
|
|
while (el) {
|
|
el = closest(SELECTOR_NAV_LIST_GROUP, el);
|
|
var sibling = el ? el.previousElementSibling : null;
|
|
|
|
if (sibling && matches(sibling, "".concat(SELECTOR_NAV_LINKS, ", ").concat(SELECTOR_LIST_ITEMS))) {
|
|
_this5.setActiveState(sibling, true);
|
|
} // Handle special case where nav-link is inside a nav-item
|
|
|
|
|
|
if (sibling && matches(sibling, SELECTOR_NAV_ITEMS)) {
|
|
_this5.setActiveState(select(SELECTOR_NAV_LINKS, sibling), true); // Add active state to nav-item as well
|
|
|
|
|
|
_this5.setActiveState(sibling, true);
|
|
}
|
|
}
|
|
}
|
|
}); // Signal event to via $root, passing ID of activated target and reference to array of links
|
|
|
|
if (links && links.length > 0 && this.$root) {
|
|
this.$root.$emit(ACTIVATE_EVENT, target, links);
|
|
}
|
|
}
|
|
}, {
|
|
key: "clear",
|
|
value: function clear() {
|
|
var _this6 = this;
|
|
|
|
selectAll("".concat(this.$selector, ", ").concat(SELECTOR_NAV_ITEMS), this.$el).filter(function (el) {
|
|
return hasClass(el, CLASS_NAME_ACTIVE);
|
|
}).forEach(function (el) {
|
|
return _this6.setActiveState(el, false);
|
|
});
|
|
}
|
|
}, {
|
|
key: "setActiveState",
|
|
value: function setActiveState(el, active) {
|
|
if (!el) {
|
|
return;
|
|
}
|
|
|
|
if (active) {
|
|
addClass(el, CLASS_NAME_ACTIVE);
|
|
} else {
|
|
removeClass(el, CLASS_NAME_ACTIVE);
|
|
}
|
|
}
|
|
}], [{
|
|
key: "Name",
|
|
get: function get() {
|
|
return NAME$1;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}, {
|
|
key: "DefaultType",
|
|
get: function get() {
|
|
return DefaultType;
|
|
}
|
|
}]);
|
|
|
|
return ScrollSpy;
|
|
}();
|
|
|
|
var BV_SCROLLSPY = '__BV_ScrollSpy__'; // Pre-compiled regular expressions
|
|
|
|
var onlyDigitsRE = /^\d+$/;
|
|
var offsetRE$2 = /^(auto|position|offset)$/; // Build a ScrollSpy config based on bindings (if any)
|
|
// Arguments and modifiers take precedence over passed value config object
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
|
|
var parseBindings$2 = function parseBindings(bindings)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
var config = {}; // If argument, assume element ID
|
|
|
|
if (bindings.arg) {
|
|
// Element ID specified as arg
|
|
// We must prepend '#' to become a CSS selector
|
|
config.element = "#".concat(bindings.arg);
|
|
} // Process modifiers
|
|
|
|
|
|
keys(bindings.modifiers).forEach(function (mod) {
|
|
if (onlyDigitsRE.test(mod)) {
|
|
// Offset value
|
|
config.offset = toInteger(mod, 0);
|
|
} else if (offsetRE$2.test(mod)) {
|
|
// Offset method
|
|
config.method = mod;
|
|
}
|
|
}); // Process value
|
|
|
|
if (isString(bindings.value)) {
|
|
// Value is a CSS ID or selector
|
|
config.element = bindings.value;
|
|
} else if (isNumber(bindings.value)) {
|
|
// Value is offset
|
|
config.offset = mathRound(bindings.value);
|
|
} else if (isObject(bindings.value)) {
|
|
// Value is config object
|
|
// Filter the object based on our supported config options
|
|
keys(bindings.value).filter(function (k) {
|
|
return !!ScrollSpy.DefaultType[k];
|
|
}).forEach(function (k) {
|
|
config[k] = bindings.value[k];
|
|
});
|
|
}
|
|
|
|
return config;
|
|
}; // Add or update ScrollSpy on our element
|
|
|
|
|
|
var applyScrollspy = function applyScrollspy(el, bindings, vnode)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
if (!isBrowser) {
|
|
/* istanbul ignore next */
|
|
return;
|
|
}
|
|
|
|
var config = parseBindings$2(bindings);
|
|
|
|
if (el[BV_SCROLLSPY]) {
|
|
el[BV_SCROLLSPY].updateConfig(config, vnode.context.$root);
|
|
} else {
|
|
el[BV_SCROLLSPY] = new ScrollSpy(el, config, vnode.context.$root);
|
|
}
|
|
}; // Remove ScrollSpy on our element
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
|
|
|
|
var removeScrollspy = function removeScrollspy(el)
|
|
/* istanbul ignore next: not easy to test */
|
|
{
|
|
if (el[BV_SCROLLSPY]) {
|
|
el[BV_SCROLLSPY].dispose();
|
|
el[BV_SCROLLSPY] = null;
|
|
delete el[BV_SCROLLSPY];
|
|
}
|
|
};
|
|
/*
|
|
* Export our directive
|
|
*/
|
|
|
|
|
|
var VBScrollspy = {
|
|
/* istanbul ignore next: not easy to test */
|
|
bind: function bind(el, bindings, vnode) {
|
|
applyScrollspy(el, bindings, vnode);
|
|
},
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
inserted: function inserted(el, bindings, vnode) {
|
|
applyScrollspy(el, bindings, vnode);
|
|
},
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
update: function update(el, bindings, vnode) {
|
|
if (bindings.value !== bindings.oldValue) {
|
|
applyScrollspy(el, bindings, vnode);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
componentUpdated: function componentUpdated(el, bindings, vnode) {
|
|
if (bindings.value !== bindings.oldValue) {
|
|
applyScrollspy(el, bindings, vnode);
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next: not easy to test */
|
|
unbind: function unbind(el) {
|
|
removeScrollspy(el);
|
|
}
|
|
};
|
|
|
|
var VBScrollspyPlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBScrollspy: VBScrollspy
|
|
}
|
|
});
|
|
|
|
var VBVisiblePlugin = /*#__PURE__*/pluginFactory({
|
|
directives: {
|
|
VBVisible: VBVisible
|
|
}
|
|
});
|
|
|
|
var directivesPlugin = /*#__PURE__*/pluginFactory({
|
|
plugins: {
|
|
VBHoverPlugin: VBHoverPlugin,
|
|
VBModalPlugin: VBModalPlugin,
|
|
VBPopoverPlugin: VBPopoverPlugin,
|
|
VBScrollspyPlugin: VBScrollspyPlugin,
|
|
VBTogglePlugin: VBTogglePlugin,
|
|
VBTooltipPlugin: VBTooltipPlugin,
|
|
VBVisiblePlugin: VBVisiblePlugin
|
|
}
|
|
});
|
|
|
|
var NAME$2 = 'BootstrapVue'; // --- BootstrapVue installer ---
|
|
|
|
var install = /*#__PURE__*/installFactory({
|
|
plugins: {
|
|
componentsPlugin: componentsPlugin,
|
|
directivesPlugin: directivesPlugin
|
|
}
|
|
}); // --- BootstrapVue plugin ---
|
|
|
|
var BootstrapVue = /*#__PURE__*/{
|
|
install: install,
|
|
NAME: NAME$2
|
|
}; // --- Named exports for BvConfigPlugin ---
|
|
|
|
// Main entry point for the browser build
|
|
|
|
vueUse(BootstrapVue);
|
|
|
|
return BootstrapVue;
|
|
|
|
})));
|
|
//# sourceMappingURL=bootstrap-vue.js.map
|