bootstrap-vue
Version:
BootstrapVue provides one of the most comprehensive implementations of Bootstrap 4 components and grid system for Vue.js and with extensive and automated WAI-ARIA accessibility markup.
363 lines (347 loc) • 10.2 kB
JavaScript
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 KeyCodes from '../../utils/key-codes';
import observeDom from '../../utils/observe-dom';
import idMixin from '../../mixins/id';
// Helper component
var bTabButtonHelper = {
name: 'bTabButtonHelper',
props: {
content: { type: [String, Array], default: '' },
href: { type: String, default: '#' },
posInSet: { type: Number, default: null },
setSize: { type: Number, default: null },
controls: { type: String, default: null },
id: { type: String, default: null },
active: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
linkClass: { default: null },
itemClass: { default: null }
},
render: function render(h) {
var t = this;
var link = h('a', {
class: ['nav-link', { active: t.active, disabled: t.disabled }, t.linkClass],
attrs: {
role: 'tab',
tabindex: '-1',
href: t.href,
id: t.id,
disabled: t.disabled,
'aria-selected': t.active ? 'true' : 'false',
'aria-setsize': t.setSize,
'aria-posinset': t.posInSet,
'aria-controls': t.controls
},
on: {
click: t.handleClick,
keydown: t.handleClick
}
}, t.content);
return h('li', { class: ['nav-item', t.itemClass], attrs: { role: 'presentation' } }, [link]);
},
methods: {
handleClick: function handleClick(evt) {
function stop() {
evt.preventDefault();
evt.stopPropagation();
}
if (this.disabled) {
stop();
return;
}
if (evt.type === 'click' || evt.keyCode === KeyCodes.ENTER || evt.keyCode === KeyCodes.SPACE) {
stop();
this.$emit('click', evt);
}
}
}
};
export default {
mixins: [idMixin],
render: function render(h) {
var _ref;
var t = this;
var tabs = this.tabs;
// Navigation 'buttons'
var buttons = tabs.map(function (tab, index) {
return h(bTabButtonHelper, {
key: index,
props: {
content: tab.$slots.title || tab.title,
href: tab.href,
id: tab.controlledBy || t.safeId('_BV_tab_' + (index + 1) + '_'),
active: tab.localActive,
disabled: tab.disabled,
setSize: tabs.length,
posInSet: index + 1,
controls: t.safeId('_BV_tab_container_'),
linkClass: tab.titleLinkClass,
itemClass: tab.titleItemClass
},
on: {
click: function click(evt) {
t.setTab(index);
}
}
});
});
// Nav 'button' wrapper
var navs = h('ul', {
class: ['nav', 'nav-' + t.navStyle, (_ref = {}, _defineProperty(_ref, 'card-header-' + t.navStyle, t.card && !t.vertical), _defineProperty(_ref, 'card-header', t.card && t.vertical), _defineProperty(_ref, 'h-100', t.card && t.vertical), _defineProperty(_ref, 'flex-column', t.vertical), _defineProperty(_ref, 'border-bottom-0', t.vertical), _defineProperty(_ref, 'rounded-0', t.vertical), _defineProperty(_ref, 'small', t.small), _ref), t.navClass],
attrs: {
role: 'tablist',
tabindex: '0',
id: t.safeId('_BV_tab_controls_')
},
on: { keydown: t.onKeynav }
}, [buttons, t.$slots.tabs]);
navs = h('div', {
class: [{
'card-header': t.card && !t.vertical && !(t.end || t.bottom),
'card-footer': t.card && !t.vertical && (t.end || t.bottom),
'col-auto': t.vertical
}, t.navWrapperClass]
}, [navs]);
var empty = void 0;
if (tabs && tabs.length) {
empty = h(false);
} else {
empty = h('div', { class: ['tab-pane', 'active', { 'card-body': t.card }] }, t.$slots.empty);
}
// Main content section
var content = h('div', {
ref: 'tabsContainer',
class: ['tab-content', { col: t.vertical }, t.contentClass],
attrs: { id: t.safeId('_BV_tab_container_') }
}, [t.$slots.default, empty]);
// Render final output
return h(t.tag, {
class: ['tabs', { row: t.vertical, 'no-gutters': t.vertical && t.card }],
attrs: { id: t.safeId() }
}, [t.end || t.bottom ? content : h(false), [navs], t.end || t.bottom ? h(false) : content]);
},
data: function data() {
return {
currentTab: this.value,
tabs: []
};
},
props: {
tag: {
type: String,
default: 'div'
},
card: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
value: {
type: Number,
default: null
},
pills: {
type: Boolean,
default: false
},
vertical: {
type: Boolean,
default: false
},
bottom: {
type: Boolean,
default: false
},
end: {
// Synonym for 'bottom'
type: Boolean,
default: false
},
noFade: {
type: Boolean,
default: false
},
lazy: {
// This prop is sniffed by the 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
}
},
watch: {
currentTab: function currentTab(val, old) {
if (val === old) {
return;
}
this.$root.$emit('changed::tab', this, val, this.tabs[val]);
this.$emit('input', val);
this.tabs[val].$emit('click');
},
value: function value(val, old) {
if (val === old) {
return;
}
if (typeof old !== 'number') {
old = 0;
}
// Moving left or right?
var direction = val < old ? -1 : 1;
this.setTab(val, false, direction);
}
},
computed: {
fade: function fade() {
// This computed prop is sniffed by the tab child
return !this.noFade;
},
navStyle: function navStyle() {
return this.pills ? 'pills' : 'tabs';
}
},
methods: {
/**
* Util: Return the sign of a number (as -1, 0, or 1)
*/
sign: function sign(x) {
return x === 0 ? 0 : x > 0 ? 1 : -1;
},
/*
* handle keyboard navigation
*/
onKeynav: function onKeynav(evt) {
var key = evt.keyCode;
var shift = evt.shiftKey;
function stop() {
evt.preventDefault();
evt.stopPropagation();
}
if (key === KeyCodes.UP || key === KeyCodes.LEFT) {
stop();
if (shift) {
this.setTab(0, false, 1);
} else {
this.previousTab();
}
} else if (key === KeyCodes.DWON || key === KeyCodes.RIGHT) {
stop();
if (shift) {
this.setTab(this.tabs.length - 1, false, -1);
} else {
this.nextTab();
}
}
},
/**
* Move to next tab
*/
nextTab: function nextTab() {
this.setTab(this.currentTab + 1, false, 1);
},
/**
* Move to previous tab
*/
previousTab: function previousTab() {
this.setTab(this.currentTab - 1, false, -1);
},
/**
* Set active tab on the tabs collection and the child 'tab' component
* Index is the tab we want to activate. Direction is the direction we are moving
* so if the tab we requested is disabled, we can skip over it.
* Force is used by updateTabs to ensure we have cleared any previous active tabs.
*/
setTab: function setTab(index, force, direction) {
var _this = this;
direction = this.sign(direction || 0);
index = index || 0;
// Prevent setting same tab and infinite loops!
if (!force && index === this.currentTab) {
return;
}
var tab = this.tabs[index];
// Don't go beyond indexes!
if (!tab) {
// Reset the v-model to the current Tab
this.$emit('input', this.currentTab);
return;
}
// Ignore or Skip disabled
if (tab.disabled) {
if (direction) {
// Skip to next non disabled tab in specified direction (recursive)
this.setTab(index + direction, force, direction);
}
return;
}
// Activate requested current tab, and deactivte any old tabs
this.tabs.forEach(function (t) {
if (t === tab) {
// Set new tab as active
_this.$set(t, 'localActive', true);
} else {
// Ensure non current tabs are not active
_this.$set(t, 'localActive', false);
}
});
// Update currentTab
this.currentTab = index;
},
/**
* Dynamically update tabs list
*/
updateTabs: function updateTabs() {
// Probe tabs
this.tabs = this.$children.filter(function (child) {
return child._isTab;
});
// Set initial active tab
var tabIndex = null;
// Find *last* active non-dsabled tab in current tabs
// We trust tab state over currentTab
this.tabs.forEach(function (tab, index) {
if (tab.localActive && !tab.disabled) {
tabIndex = index;
}
});
// Else try setting to currentTab
if (tabIndex === null) {
if (this.currentTab >= this.tabs.length) {
// Handle last tab being removed
this.setTab(this.tabs.length - 1, true, -1);
return;
} else if (this.tabs[this.currentTab] && !this.tabs[this.currentTab].disabled) {
tabIndex = this.currentTab;
}
}
// Else find *first* non-disabled tab in current tabs
if (tabIndex === null) {
this.tabs.forEach(function (tab, index) {
if (!tab.disabled && tabIndex === null) {
tabIndex = index;
}
});
}
this.setTab(tabIndex || 0, true, 0);
}
},
mounted: function mounted() {
this.updateTabs();
// Observe Child changes so we can notify tabs change
observeDom(this.$refs.tabsContainer, this.updateTabs.bind(this), {
subtree: false
});
}
};