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
499 lines (447 loc) • 13.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; }
import Vue from '../../utils/vue';
import KeyCodes from '../../utils/key-codes';
import BVTransition from '../../utils/bv-transition';
import { contains, getTabables } from '../../utils/dom';
import { getComponentConfig } from '../../utils/config';
import { toString } from '../../utils/string';
import idMixin from '../../mixins/id';
import listenOnRootMixin from '../../mixins/listen-on-root';
import normalizeSlotMixin from '../../mixins/normalize-slot';
import { EVENT_TOGGLE, EVENT_STATE, EVENT_STATE_REQUEST, EVENT_STATE_SYNC } from '../../directives/toggle/toggle';
import { BButtonClose } from '../button/button-close';
import { BIconX } from '../../icons/icons'; // --- Constants ---
var NAME = 'BSidebar';
var CLASS_NAME = 'b-sidebar'; // --- Render methods ---
var renderHeaderTitle = function renderHeaderTitle(h, ctx) {
var title = ctx.normalizeSlot('title', ctx.slotScope) || toString(ctx.title) || null; // Render a empty `<span>` when to title was provided
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, "-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, "-body"),
class: ctx.bodyClass
}, [ctx.normalizeSlot('default', ctx.slotScope)]);
};
var renderFooter = function renderFooter(h, ctx) {
var $footer = ctx.normalizeSlot('footer', ctx.slotScope);
if (!$footer) {
return h();
}
return h('footer', {
key: 'footer',
staticClass: "".concat(CLASS_NAME, "-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();
}
return h('div', {
directives: [{
name: 'show',
value: ctx.localShow
}],
staticClass: 'b-sidebar-backdrop',
on: {
click: ctx.onBackdropClick
}
});
}; // --- Main component ---
// @vue/component
export var BSidebar = /*#__PURE__*/Vue.extend({
name: NAME,
mixins: [idMixin, listenOnRootMixin, normalizeSlotMixin],
inheritAttrs: false,
model: {
prop: 'visible',
event: 'change'
},
props: {
title: {
type: String // default: null
},
right: {
type: Boolean,
default: false
},
bgVariant: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'bgVariant');
}
},
textVariant: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'textVariant');
}
},
shadow: {
type: [Boolean, String],
default: function _default() {
return getComponentConfig(NAME, 'shadow');
}
},
width: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'width');
}
},
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: function _default() {
return getComponentConfig(NAME, 'tag');
}
},
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
},
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
},
lazy: {
type: Boolean,
default: false
},
visible: {
type: Boolean,
default: false
}
},
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
};
}
},
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()
/* istanbul ignore next: pain to mock */
{
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()
/* istanbul ignore next */
{
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 === KeyCodes.ESC && this.localShow) {
this.hide();
}
},
onBackdropClick: function onBackdropClick() {
if (this.localShow && !this.noCloseOnBackdrop) {
this.hide();
}
},
/* istanbul ignore next */
onTopTrapFocus: function onTopTrapFocus()
/* istanbul ignore next */
{
var tabables = getTabables(this.$refs.content);
try {
tabables.reverse()[0].focus();
} catch (_unused) {}
},
/* istanbul ignore next */
onBottomTrapFocus: function onBottomTrapFocus()
/* istanbul ignore next */
{
var tabables = getTabables(this.$refs.content);
try {
tabables[0].focus();
} catch (_unused2) {}
},
onBeforeEnter: function onBeforeEnter() {
this.$_returnFocusEl = null;
try {
this.$_returnFocusEl = document.activeElement || null;
} catch (_unused3) {} // Trigger lazy render
this.isOpen = true;
},
onAfterEnter: function onAfterEnter(el) {
try {
if (!contains(el, document.activeElement)) {
el.focus();
}
} catch (_unused4) {}
this.$emit('shown');
},
onAfterLeave: function onAfterLeave() {
try {
this.$_returnFocusEl.focus();
} catch (_unused5) {}
this.$_returnFocusEl = null; // Trigger lazy render
this.isOpen = false;
this.$emit('hidden');
}
},
render: function render(h) {
var _ref;
var localShow = this.localShow;
var shadow = this.shadow === '' ? true : this.shadow;
var title = this.normalizeSlot('title', this.slotScope) || toString(this.title) || null;
var titleId = title ? this.safeId('__title__') : null;
var ariaLabel = this.ariaLabel || null; // `ariaLabel` takes precedence over `ariaLabelledby`
var ariaLabelledby = this.ariaLabelledby || titleId || null;
var $sidebar = h(this.tag, {
ref: 'content',
directives: [{
name: 'show',
value: localShow
}],
staticClass: CLASS_NAME,
class: [(_ref = {
shadow: shadow === true
}, _defineProperty(_ref, "shadow-".concat(shadow), shadow && shadow !== true), _defineProperty(_ref, "".concat(CLASS_NAME, "-right"), this.right), _defineProperty(_ref, "bg-".concat(this.bgVariant), !!this.bgVariant), _defineProperty(_ref, "text-".concat(this.textVariant), !!this.textVariant), _ref), this.sidebarClass],
attrs: _objectSpread(_objectSpread({}, this.$attrs), {}, {
id: this.safeId(),
tabindex: '-1',
role: 'dialog',
'aria-modal': this.backdrop ? 'true' : 'false',
'aria-hidden': localShow ? null : 'true',
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby
}),
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]);
}
});