bootstrap-vue
Version:
With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens
717 lines (657 loc) • 23.5 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 '../../vue';
import { NAME_FORM_SPINBUTTON, NAME_TIME } from '../../constants/components';
import { CODE_LEFT, CODE_RIGHT } from '../../constants/key-codes';
import { RX_TIME } from '../../constants/regex';
import identity from '../../utils/identity';
import looseEqual from '../../utils/loose-equal';
import { concat } from '../../utils/array';
import { getComponentConfig } from '../../utils/config';
import { createDate, createDateFormatter } from '../../utils/date';
import { attemptBlur, attemptFocus, contains, getActiveElement, requestAF } from '../../utils/dom';
import { stopEvent } from '../../utils/events';
import { isNull, isUndefinedOrNull } from '../../utils/inspect';
import { isLocaleRTL } from '../../utils/locale';
import { toInteger } from '../../utils/number';
import { toString } from '../../utils/string';
import idMixin from '../../mixins/id';
import normalizeSlotMixin from '../../mixins/normalize-slot';
import { BFormSpinbutton } from '../form-spinbutton/form-spinbutton';
import { BIconCircleFill, BIconChevronUp } from '../../icons/icons'; // --- Constants ---
var NUMERIC = 'numeric'; // --- Helpers ---
// Fallback to BFormSpinbutton prop if no value found
var getConfigFallback = function getConfigFallback(prop) {
return getComponentConfig(NAME_TIME, prop) || getComponentConfig(NAME_FORM_SPINBUTTON, 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 (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(':');
}; // @vue/component
export var BTime = /*#__PURE__*/Vue.extend({
name: NAME_TIME,
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_TIME, 'labelNoTimeSelected');
}
},
labelSelected: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelSelected');
}
},
labelHours: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelHours');
}
},
labelMinutes: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelMinutes');
}
},
labelSeconds: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelSeconds');
}
},
labelAmpm: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelAmpm');
}
},
labelAm: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, 'labelAm');
}
},
labelPm: {
type: String,
default: function _default() {
return getComponentConfig(NAME_TIME, '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) {
// 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: _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();
$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]);
}
});