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
625 lines (593 loc) • 18.4 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; }
import Vue from '../../vue';
import { NAME_CALENDAR, NAME_FORM_DATEPICKER } from '../../constants/components';
import { CALENDAR_LONG, CALENDAR_NARROW, CALENDAR_SHORT, DATE_FORMAT_NUMERIC } from '../../constants/date';
import { arrayIncludes } from '../../utils/array';
import { BVFormBtnLabelControl, dropdownProps } from '../../utils/bv-form-btn-label-control';
import { getComponentConfig } from '../../utils/config';
import { createDate, constrainDate, formatYMD, parseYMD } from '../../utils/date';
import { attemptBlur, attemptFocus } from '../../utils/dom';
import { isUndefinedOrNull } from '../../utils/inspect';
import { pick } from '../../utils/object';
import idMixin from '../../mixins/id';
import { BButton } from '../button/button';
import { BCalendar } from '../calendar/calendar';
import { BIconCalendar, BIconCalendarFill } from '../../icons/icons'; // Fallback to BCalendar prop if no value found
var getConfigFallback = function getConfigFallback(prop) {
return getComponentConfig(NAME_FORM_DATEPICKER, prop) || getComponentConfig(NAME_CALENDAR, prop);
}; // We create our props as a mixin so that we can control
// where they appear in the props listing reference section
var propsMixin = {
props: _objectSpread({
value: {
type: [String, Date],
default: null
},
valueAsDate: {
type: Boolean,
default: false
},
resetValue: {
type: [String, Date] // default: null
},
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`)
// Passed directly to <b-calendar>
type: [String, Date] // default: null
},
placeholder: {
type: String // Defaults to `labelNoDateSelected` from calendar context
// default: null
},
size: {
type: String // default: null
},
min: {
type: [String, Date] // default: null
},
max: {
type: [String, Date] // default: null
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
required: {
// If true adds the `aria-required` attribute
type: Boolean,
default: false
},
name: {
type: String // default: null
},
form: {
type: String // default: null
},
state: {
// Tri-state prop: `true`, `false` or `null`
type: Boolean,
default: null
},
dateDisabledFn: {
type: Function // default: null
},
noCloseOnSelect: {
type: Boolean,
default: false
},
hideHeader: {
type: Boolean,
default: false
},
showDecadeNav: {
// When `true` enables the decade navigation buttons
type: Boolean,
default: false
},
locale: {
type: [String, Array] // default: null
},
startWeekday: {
// `0` (Sunday), `1` (Monday), ... `6` (Saturday)
// Day of week to start calendar on
type: [Number, String],
default: 0
},
direction: {
type: String // default: null
},
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'
},
selectedVariant: {
// Variant color to use for the selected date
type: String,
default: function _default() {
return getConfigFallback('selectedVariant');
}
},
todayVariant: {
// Variant color to use for today's date (defaults to `selectedVariant`)
type: String,
default: function _default() {
return getConfigFallback('todayVariant');
}
},
navButtonVariant: {
// Variant color to use for the navigation buttons
type: String,
default: function _default() {
return getConfigFallback('navButtonVariant');
}
},
noHighlightToday: {
// Disable highlighting today's date
type: Boolean,
default: false
},
todayButton: {
type: Boolean,
default: false
},
labelTodayButton: {
type: String,
default: function _default() {
return getComponentConfig(NAME_FORM_DATEPICKER, 'labelTodayButton');
}
},
todayButtonVariant: {
type: String,
default: 'outline-primary'
},
resetButton: {
type: Boolean,
default: false
},
labelResetButton: {
type: String,
default: function _default() {
return getComponentConfig(NAME_FORM_DATEPICKER, 'labelResetButton');
}
},
resetButtonVariant: {
type: String,
default: 'outline-danger'
},
closeButton: {
type: Boolean,
default: false
},
labelCloseButton: {
type: String,
default: function _default() {
return getComponentConfig(NAME_FORM_DATEPICKER, 'labelCloseButton');
}
},
closeButtonVariant: {
type: String,
default: 'outline-secondary'
},
dateInfoFn: {
// Passed through to b-calendar
type: Function // default: undefined
},
// Labels for buttons and keyboard shortcuts
// These pick BCalendar global config if no BFormDate global config
labelPrevDecade: {
type: String,
default: function _default() {
return getConfigFallback('labelPrevDecade');
}
},
labelPrevYear: {
type: String,
default: function _default() {
return getConfigFallback('labelPrevYear');
}
},
labelPrevMonth: {
type: String,
default: function _default() {
return getConfigFallback('labelPrevMonth');
}
},
labelCurrentMonth: {
type: String,
default: function _default() {
return getConfigFallback('labelCurrentMonth');
}
},
labelNextMonth: {
type: String,
default: function _default() {
return getConfigFallback('labelNextMonth');
}
},
labelNextYear: {
type: String,
default: function _default() {
return getConfigFallback('labelNextYear');
}
},
labelNextDecade: {
type: String,
default: function _default() {
return getConfigFallback('labelNextDecade');
}
},
labelToday: {
type: String,
default: function _default() {
return getConfigFallback('labelToday');
}
},
labelSelected: {
type: String,
default: function _default() {
return getConfigFallback('labelSelected');
}
},
labelNoDateSelected: {
type: String,
default: function _default() {
return getConfigFallback('labelNoDateSelected');
}
},
labelCalendar: {
type: String,
default: function _default() {
return getConfigFallback('labelCalendar');
}
},
labelNav: {
type: String,
default: function _default() {
return getConfigFallback('labelNav');
}
},
labelHelp: {
type: String,
default: function _default() {
return getConfigFallback('labelHelp');
}
},
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);
}
},
// Dark mode
dark: {
type: Boolean,
default: false
},
// extra dropdown stuff
menuClass: {
type: [String, Array, Object] // default: null
}
}, dropdownProps)
}; // --- BFormDate component ---
// @vue/component
export var BFormDatepicker = /*#__PURE__*/Vue.extend({
name: NAME_FORM_DATEPICKER,
// The mixins order determines the order of appearance in the props reference section
mixins: [idMixin, propsMixin],
model: {
prop: 'value',
event: 'input'
},
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);
},
calendarProps: function calendarProps() {
// Use self for better minification, as `this` won't
// minimize and we reference it many times below
var self = this;
return {
hidden: !self.isVisible,
value: self.localYMD,
min: self.min,
max: self.max,
initialDate: self.initialDate,
readonly: self.readonly,
disabled: self.disabled,
locale: self.locale,
startWeekday: self.startWeekday,
direction: self.direction,
width: self.calendarWidth,
dateDisabledFn: self.dateDisabledFn,
selectedVariant: self.selectedVariant,
todayVariant: self.todayVariant,
navButtonVariant: self.navButtonVariant,
dateInfoFn: self.dateInfoFn,
hideHeader: self.hideHeader,
showDecadeNav: self.showDecadeNav,
noHighlightToday: self.noHighlightToday,
labelPrevDecade: self.labelPrevDecade,
labelPrevYear: self.labelPrevYear,
labelPrevMonth: self.labelPrevMonth,
labelCurrentMonth: self.labelCurrentMonth,
labelNextMonth: self.labelNextMonth,
labelNextYear: self.labelNextYear,
labelNextDecade: self.labelNextDecade,
labelToday: self.labelToday,
labelSelected: self.labelSelected,
labelNoDateSelected: self.labelNoDateSelected,
labelCalendar: self.labelCalendar,
labelNav: self.labelNav,
labelHelp: self.labelHelp,
dateFormatOptions: self.dateFormatOptions,
weekdayHeaderFormat: self.weekdayHeaderFormat
};
},
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)
/* istanbul ignore next */
{
// 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 $scopedSlots = this.$scopedSlots;
var localYMD = this.localYMD;
var disabled = this.disabled;
var readonly = this.readonly;
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: this.calendarProps,
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: _objectSpread(_objectSpread({}, this.$props), {}, {
// Overridden / computed props
id: this.safeId(),
rtl: this.isRTL,
lang: this.computedLang,
value: localYMD || '',
formattedValue: localYMD ? this.formattedValue : '',
placeholder: placeholder || '',
menuClass: [{
'bg-dark': !!this.dark,
'text-light': !!this.dark
}, this.menuClass]
}),
on: {
show: this.onShow,
shown: this.onShown,
hidden: this.onHidden
},
scopedSlots: {
'button-content': $scopedSlots['button-content'] || this.defaultButtonFn
}
}, [$calendar]);
}
});