bootstrap-vue
Version:
BootstrapVue, with more than 85 custom components, over 45 plugins, several custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated W
728 lines (664 loc) • 23.6 kB
JavaScript
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
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."); }
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 _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 _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
// BTime control (not form input control)
import Vue from '../../utils/vue'; // Utilities
import identity from '../../utils/identity';
import KeyCodes from '../../utils/key-codes';
import looseEqual from '../../utils/loose-equal';
import { concat } from '../../utils/array';
import { getComponentConfig } from '../../utils/config';
import { createDate, createDateFormatter } from '../../utils/date';
import { contains, requestAF } from '../../utils/dom';
import { isNull, isUndefinedOrNull } from '../../utils/inspect';
import { isLocaleRTL } from '../../utils/locale';
import { toInteger } from '../../utils/number';
import { toString } from '../../utils/string'; // Mixins
import idMixin from '../../mixins/id';
import normalizeSlotMixin from '../../mixins/normalize-slot'; // Sub components used
import { BFormSpinbutton } from '../form-spinbutton/form-spinbutton';
import { BIconCircleFill, BIconChevronUp } from '../../icons/icons'; // --- Constants ---
var NAME = 'BTime';
var NUMERIC = 'numeric';
var LEFT = KeyCodes.LEFT,
RIGHT = KeyCodes.RIGHT; // Time string RegExpr (optional seconds)
var RE_TIME = /^([0-1]?[0-9]|2[0-3]):[0-5]?[0-9](:[0-5]?[0-9])?$/; // --- Helpers ---
// Fallback to BFormSpinbutton prop if no value found
var getConfigFallback = function getConfigFallback(prop) {
return getComponentConfig(NAME, prop) || getComponentConfig('BFormSpinbutton', prop);
};
var padLeftZeros = function padLeftZeros(num) {
return "00".concat(num || '').slice(-2);
};
var parseHMS = function parseHMS(hms) {
hms = toString(hms);
var hh = null,
mm = null,
ss = null;
if (RE_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(':');
}; // @vue/component
export var BTime = /*#__PURE__*/Vue.extend({
name: NAME,
mixins: [idMixin, normalizeSlotMixin],
model: {
prop: 'value',
event: 'input'
},
props: {
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: function _default() {
return getComponentConfig(NAME, 'labelNoTimeSelected');
}
},
labelSelected: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelSelected');
}
},
labelHours: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelHours');
}
},
labelMinutes: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelMinutes');
}
},
labelSeconds: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelSeconds');
}
},
labelAmpm: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelAmpm');
}
},
labelAm: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelAm');
}
},
labelPm: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'labelPm');
}
},
// Passed to the spin buttons
labelIncrement: {
type: String,
// Falls back to BFormSpinbutton label
default: function _default() {
return getConfigFallback('labelIncrement');
}
},
labelDecrement: {
type: String,
// Falls back to BFormSpinbutton label
default: function _default() {
return getConfigFallback('labelDecrement');
}
},
hidden: {
type: Boolean,
default: false
}
},
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()
/* istanbul ignore next */
{
this.setLive(true);
},
/* istanbul ignore next */
deactivated: function deactivated()
/* istanbul ignore next */
{
this.setLive(false);
},
beforeDestroy: function beforeDestroy() {
this.setLive(false);
},
methods: {
// Public methods
focus: function focus() {
if (!this.disabled) {
try {
// We focus the first spin button
this.$refs.spinners[0].focus();
} catch (_unused) {}
}
},
blur: function blur() {
if (!this.disabled) {
try {
if (contains(this.$el, document.activeElement)) {
document.activeElement.blur();
}
} catch (_unused2) {}
}
},
// 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 === LEFT || keyCode === RIGHT)) {
evt.preventDefault();
evt.stopPropagation();
var spinners = this.$refs.spinners || [];
var index = spinners.map(function (cmp) {
return !!cmp.hasFocus;
}).indexOf(true);
index = index + (keyCode === LEFT ? -1 : 1);
index = index >= spinners.length ? 0 : index < 0 ? spinners.length - 1 : index;
try {
spinners[index].focus();
} catch (_unused3) {}
}
},
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: _objectSpread({
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('default');
$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]);
}
});