first commit

This commit is contained in:
DanielRamirezGe
2020-01-14 20:43:08 -06:00
parent 3557894f7f
commit 5eecfbf6cd
26326 changed files with 2434803 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
// Constants used by table helpers
// Object of item keys that should be ignored for headers and
// stringification and filter events
export var IGNORED_FIELD_KEYS = {
_rowVariant: true,
_cellVariants: true,
_showDetails: true
}; // Filter CSS selector for click/dblclick/etc. events
// If any of these selectors match the clicked element, we ignore the event
export var EVENT_FILTER = ['a', 'a *', // Include content inside links
'button', 'button *', // Include content inside buttons
'input:not(.disabled):not([disabled])', 'select:not(.disabled):not([disabled])', 'textarea:not(.disabled):not([disabled])', '[role="link"]', '[role="link"] *', '[role="button"]', '[role="button"] *', '[tabindex]:not(.disabled):not([disabled])'].join(',');
@@ -0,0 +1,40 @@
import get from '../../../utils/get';
import { isDate, isFunction, isNumber, isUndefinedOrNull } from '../../../utils/inspect';
import stringifyObjectValues from './stringify-object-values'; // Default sort compare routine
//
// TODO: Add option to sort by multiple columns (tri-state per column,
// plus order of columns in sort) where sortBy could be an array
// of objects `[ {key: 'foo', sortDir: 'asc'}, {key:'bar', sortDir: 'desc'} ...]`
// or an array of arrays `[ ['foo','asc'], ['bar','desc'] ]`
// Multisort will most likely be handled in mixin-sort.js by
// calling this method for each sortBy
var defaultSortCompare = function defaultSortCompare(a, b, sortBy, sortDesc, formatter, localeOpts, locale, nullLast) {
var aa = get(a, sortBy, null);
var bb = get(b, sortBy, null);
if (isFunction(formatter)) {
aa = formatter(aa, sortBy, a);
bb = formatter(bb, sortBy, b);
}
aa = isUndefinedOrNull(aa) ? '' : aa;
bb = isUndefinedOrNull(bb) ? '' : bb;
if (isDate(aa) && isDate(bb) || isNumber(aa) && isNumber(bb)) {
// Special case for comparing dates and numbers
// Internally dates are compared via their epoch number values
return aa < bb ? -1 : aa > bb ? 1 : 0;
} else if (nullLast && aa === '' && bb !== '') {
// Special case when sorting null/undefined/empty string last
return 1;
} else if (nullLast && aa !== '' && bb === '') {
// Special case when sorting null/undefined/empty string last
return -1;
} // Do localized string comparison
return stringifyObjectValues(aa).localeCompare(stringifyObjectValues(bb), locale, localeOpts);
};
export default defaultSortCompare;
@@ -0,0 +1,43 @@
import { closest, getAttr, getById, matches, select } from '../../../utils/dom';
import { EVENT_FILTER } from './constants';
var TABLE_TAG_NAMES = ['TD', 'TH', 'TR']; // Returns `true` if we should ignore the click/double-click/keypress event
// Avoids having the user need to use `@click.stop` on the form control
var filterEvent = function filterEvent(evt) {
// Exit early when we don't have a target element
if (!evt || !evt.target) {
/* istanbul ignore next */
return false;
}
var el = evt.target; // Exit early when element is disabled or a table element
if (el.disabled || TABLE_TAG_NAMES.indexOf(el.tagName) !== -1) {
return false;
} // Ignore the click when it was inside a dropdown menu
if (closest('.dropdown-menu', el)) {
return true;
}
var label = el.tagName === 'LABEL' ? el : closest('label', el); // If the label's form control is not disabled then we don't propagate event
// Modern browsers have `label.control` that references the associated input, but IE11
// does not have this property on the label element, so we resort to DOM lookups
if (label) {
var labelFor = getAttr(label, 'for');
var input = labelFor ? getById(labelFor) : select('input, select, textarea', label);
if (input && !input.disabled) {
return true;
}
} // Otherwise check if the event target matches one of the selectors in the
// event filter (i.e. anchors, non disabled inputs, etc.)
// Return `true` if we should ignore the event
return matches(el, EVENT_FILTER);
};
export default filterEvent;
@@ -0,0 +1,25 @@
import { isFunction } from '../../../utils/inspect';
import { BTr } from '../tr';
var slotName = 'bottom-row';
export default {
methods: {
renderBottomRow: function renderBottomRow() {
var h = this.$createElement; // Static bottom row slot (hidden in visibly stacked mode as we can't control the data-label)
// If in *always* stacked mode, we don't bother rendering the row
if (!this.hasNormalizedSlot(slotName) || this.stacked === true || this.stacked === '') {
return h();
}
var fields = this.computedFields;
return h(BTr, {
key: 'b-bottom-row',
staticClass: 'b-table-bottom-row',
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(null, 'row-bottom') : this.tbodyTrClass]
}, this.normalizeSlot(slotName, {
columns: fields.length,
fields: fields
}));
}
}
};
+63
View File
@@ -0,0 +1,63 @@
import { isFunction } from '../../../utils/inspect';
import { BTr } from '../tr';
import { BTd } from '../td';
var busySlotName = 'table-busy';
export default {
props: {
busy: {
type: Boolean,
default: false
}
},
data: function data() {
return {
localBusy: false
};
},
computed: {
computedBusy: function computedBusy() {
return this.busy || this.localBusy;
}
},
watch: {
localBusy: function localBusy(newVal, oldVal) {
if (newVal !== oldVal) {
this.$emit('update:busy', newVal);
}
}
},
methods: {
// Event handler helper
stopIfBusy: function stopIfBusy(evt) {
if (this.computedBusy) {
// If table is busy (via provider) then don't propagate
evt.preventDefault();
evt.stopPropagation();
return true;
}
return false;
},
// Render the busy indicator or return `null` if not busy
renderBusy: function renderBusy() {
var h = this.$createElement; // Return a busy indicator row, or `null` if not busy
if (this.computedBusy && this.hasNormalizedSlot(busySlotName)) {
// Show the busy slot
return h(BTr, {
key: 'table-busy-slot',
staticClass: 'b-table-busy-slot',
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(null, busySlotName) : this.tbodyTrClass]
}, [h(BTd, {
props: {
colspan: this.computedFields.length || null
}
}, [this.normalizeSlot(busySlotName)])]);
} else {
// We return `null` here so that we can determine if we need to
// render the table items rows or not
return null;
}
}
}
};
@@ -0,0 +1,49 @@
import { htmlOrText } from '../../../utils/html';
export default {
props: {
// `caption-top` is part of table-redere mixin (styling)
// captionTop: {
// type: Boolean,
// default: false
// },
caption: {
type: String,
default: null
},
captionHtml: {
type: String
}
},
computed: {
captionId: function captionId() {
// Even though `this.safeId` looks like a method, it is a computed prop
// that returns a new function if the underlying ID changes
return this.isStacked ? this.safeId('_caption_') : null;
}
},
methods: {
renderCaption: function renderCaption() {
var h = this.$createElement; // Build the caption
var $captionSlot = this.normalizeSlot('table-caption');
var $caption = h();
if ($captionSlot || this.caption || this.captionHtml) {
var data = {
key: 'caption',
attrs: {
id: this.captionId
}
};
if (!$captionSlot) {
data.domProps = htmlOrText(this.captionHtml, this.caption);
}
$caption = h('caption', data, [$captionSlot]);
}
return $caption;
}
}
};
@@ -0,0 +1,20 @@
export default {
methods: {
renderColgroup: function renderColgroup() {
var h = this.$createElement;
var fields = this.computedFields;
var $colgroup = h();
if (this.hasNormalizedSlot('table-colgroup')) {
$colgroup = h('colgroup', {
key: 'colgroup'
}, [this.normalizeSlot('table-colgroup', {
columns: fields.length,
fields: fields
})]);
}
return $colgroup;
}
}
};
+70
View File
@@ -0,0 +1,70 @@
import { htmlOrText } from '../../../utils/html';
import { isFunction } from '../../../utils/inspect';
import { BTr } from '../tr';
import { BTd } from '../td';
export default {
props: {
showEmpty: {
type: Boolean,
default: false
},
emptyText: {
type: String,
default: 'There are no records to show'
},
emptyHtml: {
type: String
},
emptyFilteredText: {
type: String,
default: 'There are no records matching your request'
},
emptyFilteredHtml: {
type: String
}
},
methods: {
renderEmpty: function renderEmpty() {
var h = this.$createElement;
var items = this.computedItems;
var $empty;
if (this.showEmpty && (!items || items.length === 0) && !(this.computedBusy && this.hasNormalizedSlot('table-busy'))) {
$empty = this.normalizeSlot(this.isFiltered ? 'emptyfiltered' : 'empty', {
emptyFilteredHtml: this.emptyFilteredHtml,
emptyFilteredText: this.emptyFilteredText,
emptyHtml: this.emptyHtml,
emptyText: this.emptyText,
fields: this.computedFields,
// Not sure why this is included, as it will always be an empty array
items: this.computedItems
});
if (!$empty) {
$empty = h('div', {
class: ['text-center', 'my-2'],
domProps: this.isFiltered ? htmlOrText(this.emptyFilteredHtml, this.emptyFilteredText) : htmlOrText(this.emptyHtml, this.emptyText)
});
}
$empty = h(BTd, {
props: {
colspan: this.computedFields.length || null
}
}, [h('div', {
attrs: {
role: 'alert',
'aria-live': 'polite'
}
}, [$empty])]);
$empty = h(BTr, {
key: this.isFiltered ? 'b-empty-filtered-row' : 'b-empty-row',
staticClass: 'b-table-empty-row',
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(null, 'row-empty') : this.tbodyTrClass]
}, [$empty]);
}
return $empty || h();
}
}
};
@@ -0,0 +1,257 @@
import cloneDeep from '../../../utils/clone-deep';
import looseEqual from '../../../utils/loose-equal';
import { concat } from '../../../utils/array';
import { isFunction, isString, isRegExp } from '../../../utils/inspect';
import { warn } from '../../../utils/warn';
import stringifyRecordValues from './stringify-record-values';
var DEPRECATED_DEBOUNCE = 'b-table: Prop "filter-debounce" is deprecated. Use the debounce feature of <b-form-input> instead';
export default {
props: {
filter: {
type: [String, RegExp, Object, Array],
default: null
},
filterFunction: {
type: Function,
default: null
},
filterIgnoredFields: {
type: Array // default: undefined
},
filterIncludedFields: {
type: Array // default: undefined
},
filterDebounce: {
type: [Number, String],
deprecated: DEPRECATED_DEBOUNCE,
default: 0,
validator: function validator(val) {
return /^\d+/.test(String(val));
}
}
},
data: function data() {
return {
// Flag for displaying which empty slot to show and some event triggering
isFiltered: false,
// Where we store the copy of the filter criteria after debouncing
// We pre-set it with the sanitized filter value
localFilter: this.filterSanitize(this.filter)
};
},
computed: {
computedFilterIgnored: function computedFilterIgnored() {
return this.filterIgnoredFields ? concat(this.filterIgnoredFields).filter(Boolean) : null;
},
computedFilterIncluded: function computedFilterIncluded() {
return this.filterIncludedFields ? concat(this.filterIncludedFields).filter(Boolean) : null;
},
computedFilterDebounce: function computedFilterDebounce() {
var ms = parseInt(this.filterDebounce, 10) || 0;
/* istanbul ignore next */
if (ms > 0) {
warn(DEPRECATED_DEBOUNCE);
}
return ms;
},
localFiltering: function localFiltering() {
return this.hasProvider ? !!this.noProviderFiltering : true;
},
// For watching changes to `filteredItems` vs `localItems`
filteredCheck: function filteredCheck() {
return {
filteredItems: this.filteredItems,
localItems: this.localItems,
localFilter: this.localFilter
};
},
// Sanitized/normalize filter-function prop
localFilterFn: function localFilterFn() {
// Return `null` to signal to use internal filter function
return isFunction(this.filterFunction) ? this.filterFunction : null;
},
// Returns the records in `localItems` that match the filter criteria
// Returns the original `localItems` array if not sorting
filteredItems: function filteredItems() {
var items = this.localItems || []; // Note the criteria is debounced and sanitized
var criteria = this.localFilter; // Resolve the filtering function, when requested
// We prefer the provided filtering function and fallback to the internal one
// When no filtering criteria is specified the filtering factories will return `null`
var filterFn = this.localFiltering ? this.filterFnFactory(this.localFilterFn, criteria) || this.defaultFilterFnFactory(criteria) : null; // We only do local filtering when requested and there are records to filter
return filterFn && items.length > 0 ? items.filter(filterFn) : items;
}
},
watch: {
// Watch for debounce being set to 0
computedFilterDebounce: function computedFilterDebounce(newVal, oldVal) {
if (!newVal && this.$_filterTimer) {
clearTimeout(this.$_filterTimer);
this.$_filterTimer = null;
this.localFilter = this.filterSanitize(this.filter);
}
},
// Watch for changes to the filter criteria, and debounce if necessary
filter: {
// We need a deep watcher in case the user passes
// an object when using `filter-function`
deep: true,
handler: function handler(newCriteria, oldCriteria) {
var _this = this;
var timeout = this.computedFilterDebounce;
clearTimeout(this.$_filterTimer);
this.$_filterTimer = null;
if (timeout && timeout > 0) {
// If we have a debounce time, delay the update of `localFilter`
this.$_filterTimer = setTimeout(function () {
_this.localFilter = _this.filterSanitize(newCriteria);
}, timeout);
} else {
// Otherwise, immediately update `localFilter` with `newFilter` value
this.localFilter = this.filterSanitize(newCriteria);
}
}
},
// Watch for changes to the filter criteria and filtered items vs `localItems`
// Set visual state and emit events as required
filteredCheck: function filteredCheck(_ref) {
var filteredItems = _ref.filteredItems,
localItems = _ref.localItems,
localFilter = _ref.localFilter;
// Determine if the dataset is filtered or not
var isFiltered = false;
if (!localFilter) {
// If filter criteria is falsey
isFiltered = false;
} else if (looseEqual(localFilter, []) || looseEqual(localFilter, {})) {
// If filter criteria is an empty array or object
isFiltered = false;
} else if (localFilter) {
// If filter criteria is truthy
isFiltered = true;
}
if (isFiltered) {
this.$emit('filtered', filteredItems, filteredItems.length);
}
this.isFiltered = isFiltered;
},
isFiltered: function isFiltered(newVal, oldVal) {
if (newVal === false && oldVal === true) {
// We need to emit a filtered event if isFiltered transitions from true to
// false so that users can update their pagination controls.
this.$emit('filtered', this.localItems, this.localItems.length);
}
}
},
created: function created() {
var _this2 = this;
// Create non-reactive prop where we store the debounce timer id
this.$_filterTimer = null; // If filter is "pre-set", set the criteria
// This will trigger any watchers/dependents
// this.localFilter = this.filterSanitize(this.filter)
// Set the initial filtered state in a `$nextTick()` so that
// we trigger a filtered event if needed
this.$nextTick(function () {
_this2.isFiltered = Boolean(_this2.localFilter);
});
},
beforeDestroy: function beforeDestroy()
/* istanbul ignore next */
{
clearTimeout(this.$_filterTimer);
this.$_filterTimer = null;
},
methods: {
filterSanitize: function filterSanitize(criteria) {
// Sanitizes filter criteria based on internal or external filtering
if (this.localFiltering && !this.localFilterFn && !(isString(criteria) || isRegExp(criteria))) {
// If using internal filter function, which only accepts string or RegExp,
// return '' to signify no filter
return '';
} // Could be a string, object or array, as needed by external filter function
// We use `cloneDeep` to ensure we have a new copy of an object or array
// without Vue's reactive observers
return cloneDeep(criteria);
},
// Filter Function factories
filterFnFactory: function filterFnFactory(filterFn, criteria) {
// Wrapper factory for external filter functions
// Wrap the provided filter-function and return a new function
// Returns `null` if no filter-function defined or if criteria is falsey
// Rather than directly grabbing `this.computedLocalFilterFn` or `this.filterFunction`
// we have it passed, so that the caller computed prop will be reactive to changes
// in the original filter-function (as this routine is a method)
if (!filterFn || !isFunction(filterFn) || !criteria || looseEqual(criteria, []) || looseEqual(criteria, {})) {
return null;
} // Build the wrapped filter test function, passing the criteria to the provided function
var fn = function fn(item) {
// Generated function returns true if the criteria matches part
// of the serialized data, otherwise false
return filterFn(item, criteria);
}; // Return the wrapped function
return fn;
},
defaultFilterFnFactory: function defaultFilterFnFactory(criteria) {
var _this3 = this;
// Generates the default filter function, using the given filter criteria
// Returns `null` if no criteria or criteria format not supported
if (!criteria || !(isString(criteria) || isRegExp(criteria))) {
// Built in filter can only support strings or RegExp criteria (at the moment)
return null;
} // Build the regexp needed for filtering
var regexp = criteria;
if (isString(regexp)) {
// Escape special `RegExp` characters in the string and convert contiguous
// whitespace to `\s+` matches
var pattern = criteria.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&').replace(/[\s\uFEFF\xA0]+/g, '\\s+'); // Build the `RegExp` (no need for global flag, as we only need
// to find the value once in the string)
regexp = new RegExp(".*".concat(pattern, ".*"), 'i');
} // Generate the wrapped filter test function to use
var fn = function fn(item) {
// This searches all row values (and sub property values) in the entire (excluding
// special `_` prefixed keys), because we convert the record to a space-separated
// string containing all the value properties (recursively), even ones that are
// not visible (not specified in this.fields)
// Users can ignore filtering on specific fields, or on only certain fields,
// and can optionall specify searching results of fields with formatter
//
// TODO: Enable searching on scoped slots (optional, as it will be SLOW)
//
// Generated function returns true if the criteria matches part of
// the serialized data, otherwise false
// We set `lastIndex = 0` on the `RegExp` in case someone specifies the `/g` global flag
regexp.lastIndex = 0;
return regexp.test(stringifyRecordValues(item, _this3.computedFilterIgnored, _this3.computedFilterIncluded, _this3.computedFieldsObj));
}; // Return the generated function
return fn;
}
}
};
+127
View File
@@ -0,0 +1,127 @@
import looseEqual from '../../../utils/loose-equal';
import { isArray, isFunction, isNull, isString, isUndefined } from '../../../utils/inspect';
import { clone } from '../../../utils/object';
import normalizeFields from './normalize-fields';
export default {
props: {
items: {
// Provider mixin adds in `Function` type
type: Array,
default: function _default()
/* istanbul ignore next */
{
return [];
}
},
fields: {
type: Array,
default: null
},
primaryKey: {
// Primary key for record
// If provided the value in each row must be unique!
type: String,
default: null
},
value: {
// `v-model` for retrieving the current displayed rows
type: Array,
default: function _default() {
return [];
}
}
},
data: function data() {
return {
// Our local copy of the items
// Must be an array
localItems: isArray(this.items) ? this.items.slice() : []
};
},
computed: {
computedFields: function computedFields() {
// We normalize fields into an array of objects
// `[ { key:..., label:..., ...}, {...}, ..., {..}]`
return normalizeFields(this.fields, this.localItems);
},
computedFieldsObj: function computedFieldsObj() {
// Fields as a simple lookup hash object
// Mainly for formatter lookup and use in `scopedSlots` for convenience
// If the field has a formatter, it normalizes formatter to a
// function ref or `undefined` if no formatter
var parent = this.$parent;
return this.computedFields.reduce(function (obj, f) {
// We use object spread here so we don't mutate the original field object
obj[f.key] = clone(f);
if (f.formatter) {
// Normalize formatter to a function ref or `undefined`
var formatter = f.formatter;
if (isString(formatter) && isFunction(parent[formatter])) {
formatter = parent[formatter];
} else if (!isFunction(formatter)) {
/* istanbul ignore next */
formatter = undefined;
} // Return formatter function or `undefined` if none
obj[f.key].formatter = formatter;
}
return obj;
}, {});
},
computedItems: function computedItems() {
// Fallback if various mixins not provided
return (this.paginatedItems || this.sortedItems || this.filteredItems || this.localItems || []).slice();
},
context: function context() {
// Current state of sorting, filtering and pagination props/values
return {
filter: this.localFilter,
sortBy: this.localSortBy,
sortDesc: this.localSortDesc,
perPage: parseInt(this.perPage, 10) || 0,
currentPage: parseInt(this.currentPage, 10) || 1,
apiUrl: this.apiUrl
};
}
},
watch: {
items: function items(newItems) {
/* istanbul ignore else */
if (isArray(newItems)) {
// Set `localItems`/`filteredItems` to a copy of the provided array
this.localItems = newItems.slice();
} else if (isUndefined(newItems) || isNull(newItems)) {
/* istanbul ignore next */
this.localItems = [];
}
},
// Watch for changes on `computedItems` and update the `v-model`
computedItems: function computedItems(newVal) {
this.$emit('input', newVal);
},
// Watch for context changes
context: function context(newVal, oldVal) {
// Emit context information for external paging/filtering/sorting handling
if (!looseEqual(newVal, oldVal)) {
this.$emit('context-changed', newVal);
}
}
},
mounted: function mounted() {
// Initially update the `v-model` of displayed items
this.$emit('input', this.computedItems);
},
methods: {
// Method to get the formatter method for a given field key
getFieldFormatter: function getFieldFormatter(key) {
var field = this.computedFieldsObj[key]; // `this.computedFieldsObj` has pre-normalized the formatter to a
// function ref if present, otherwise `undefined`
return field ? field.formatter : undefined;
}
}
};
@@ -0,0 +1,30 @@
export default {
props: {
perPage: {
type: [Number, String],
default: 0
},
currentPage: {
type: [Number, String],
default: 1
}
},
computed: {
localPaging: function localPaging() {
return this.hasProvider ? !!this.noProviderPaging : true;
},
paginatedItems: function paginatedItems() {
var items = this.sortedItems || this.filteredItems || this.localItems || [];
var currentPage = Math.max(parseInt(this.currentPage, 10) || 1, 1);
var perPage = Math.max(parseInt(this.perPage, 10) || 0, 0); // Apply local pagination
if (this.localPaging && !!perPage) {
// Grab the current page of data (which may be past filtered items limit)
items = items.slice((currentPage - 1) * perPage, currentPage * perPage);
} // Return the items to display in the table
return items;
}
}
};
@@ -0,0 +1,195 @@
import looseEqual from '../../../utils/loose-equal';
import warn from '../../../utils/warn';
import { isArray, isFunction, isPromise } from '../../../utils/inspect';
import { clone } from '../../../utils/object';
import listenOnRootMixin from '../../../mixins/listen-on-root';
export default {
mixins: [listenOnRootMixin],
props: {
// Prop override(s)
items: {
// Adds in 'Function' support
type: [Array, Function],
default: function _default()
/* istanbul ignore next */
{
return [];
}
},
// Additional props
noProviderPaging: {
type: Boolean,
default: false
},
noProviderSorting: {
type: Boolean,
default: false
},
noProviderFiltering: {
type: Boolean,
default: false
},
apiUrl: {
// Passthrough prop. Passed to the context object. Not used by b-table directly
type: String,
default: ''
}
},
computed: {
hasProvider: function hasProvider() {
return isFunction(this.items);
},
providerTriggerContext: function providerTriggerContext() {
// Used to trigger the provider function via a watcher. Only the fields that
// are needed for triggering a provider update are included. Note that the
// regular this.context is sent to the provider during fetches though, as they
// may need all the prop info.
var ctx = {
apiUrl: this.apiUrl,
filter: null,
sortBy: null,
sortDesc: null,
perPage: null,
currentPage: null
};
if (!this.noProviderFiltering) {
// Either a string, or could be an object or array.
ctx.filter = this.localFilter;
}
if (!this.noProviderSorting) {
ctx.sortBy = this.localSortBy;
ctx.sortDesc = this.localSortDesc;
}
if (!this.noProviderPaging) {
ctx.perPage = this.perPage;
ctx.currentPage = this.currentPage;
}
return clone(ctx);
}
},
watch: {
// Provider update triggering
items: function items(newVal, oldVal) {
// If a new provider has been specified, trigger an update
if (this.hasProvider || isFunction(newVal)) {
this.$nextTick(this._providerUpdate);
}
},
providerTriggerContext: function providerTriggerContext(newVal, oldVal) {
// Trigger the provider to update as the relevant context values have changed.
if (!looseEqual(newVal, oldVal)) {
this.$nextTick(this._providerUpdate);
}
}
},
mounted: function mounted() {
var _this = this;
// Call the items provider if necessary
if (this.hasProvider && (!this.localItems || this.localItems.length === 0)) {
// Fetch on mount if localItems is empty
this._providerUpdate();
} // Listen for global messages to tell us to force refresh the table
this.listenOnRoot('bv::refresh::table', function (id) {
if (id === _this.id || id === _this) {
_this.refresh();
}
});
},
methods: {
refresh: function refresh() {
// Public Method: Force a refresh of the provider function
this.$off('refreshed', this.refresh);
if (this.computedBusy) {
// Can't force an update when forced busy by user (busy prop === true)
if (this.localBusy && this.hasProvider) {
// But if provider running (localBusy), re-schedule refresh once `refreshed` emitted
this.$on('refreshed', this.refresh);
}
} else {
this.clearSelected();
if (this.hasProvider) {
this.$nextTick(this._providerUpdate);
} else {
/* istanbul ignore next */
this.localItems = isArray(this.items) ? this.items.slice() : [];
}
}
},
// Provider related methods
_providerSetLocal: function _providerSetLocal(items) {
this.localItems = isArray(items) ? items.slice() : [];
this.localBusy = false;
this.$emit('refreshed'); // New root emit
if (this.id) {
this.emitOnRoot('bv::table::refreshed', this.id);
}
},
_providerUpdate: function _providerUpdate() {
var _this2 = this;
// Refresh the provider function items.
if (!this.hasProvider) {
// Do nothing if no provider
return;
} // If table is busy, wait until refreshed before calling again
if (this.computedBusy) {
// Schedule a new refresh once `refreshed` is emitted
this.$nextTick(this.refresh);
return;
} // Set internal busy state
this.localBusy = true; // Call provider function with context and optional callback after DOM is fully updated
this.$nextTick(function () {
try {
// Call provider function passing it the context and optional callback
var data = _this2.items(_this2.context, _this2._providerSetLocal);
if (isPromise(data)) {
// Provider returned Promise
data.then(function (items) {
// Provider resolved with items
_this2._providerSetLocal(items);
});
} else if (isArray(data)) {
// Provider returned Array data
_this2._providerSetLocal(data);
} else {
/* istanbul ignore if */
if (_this2.items.length !== 2) {
// Check number of arguments provider function requested
// Provider not using callback (didn't request second argument), so we clear
// busy state as most likely there was an error in the provider function
/* istanbul ignore next */
warn("b-table provider function didn't request callback and did not return a promise or data");
_this2.localBusy = false;
}
}
} catch (e)
/* istanbul ignore next */
{
// Provider function borked on us, so we spew out a warning
// and clear the busy state
warn("b-table provider function error [".concat(e.name, "] ").concat(e.message));
_this2.localBusy = false;
_this2.$off('refreshed', _this2.refresh);
}
});
}
}
};
@@ -0,0 +1,224 @@
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 looseEqual from '../../../utils/loose-equal';
import range from '../../../utils/range';
import { isArray, arrayIncludes } from '../../../utils/array';
import { getComponentConfig } from '../../../utils/config';
import { isNumber } from '../../../utils/inspect';
import sanitizeRow from './sanitize-row';
export default {
props: {
selectable: {
type: Boolean,
default: false
},
selectMode: {
type: String,
default: 'multi',
validator: function validator(val) {
return arrayIncludes(['range', 'multi', 'single'], val);
}
},
selectedVariant: {
type: String,
default: function _default() {
return getComponentConfig('BTable', 'selectedVariant');
}
},
noSelectOnClick: {
// Disable use of click handlers for row selection
type: Boolean,
default: false
}
},
data: function data() {
return {
selectedRows: [],
selectedLastRow: -1
};
},
computed: {
isSelectable: function isSelectable() {
return this.selectable && this.selectMode;
},
hasSelectableRowClick: function hasSelectableRowClick() {
return this.isSelectable && !this.noSelectOnClick;
},
supportsSelectableRows: function supportsSelectableRows() {
return true;
},
selectableHasSelection: function selectableHasSelection() {
return this.isSelectable && this.selectedRows && this.selectedRows.length > 0 && this.selectedRows.some(Boolean);
},
selectableIsMultiSelect: function selectableIsMultiSelect() {
return this.isSelectable && arrayIncludes(['range', 'multi'], this.selectMode);
},
selectableTableClasses: function selectableTableClasses() {
var _ref;
return _ref = {
'b-table-selectable': this.isSelectable
}, _defineProperty(_ref, "b-table-select-".concat(this.selectMode), this.isSelectable), _defineProperty(_ref, 'b-table-selecting', this.selectableHasSelection), _defineProperty(_ref, 'b-table-selectable-no-click', this.isSelectable && !this.hasSelectableRowClick), _ref;
},
selectableTableAttrs: function selectableTableAttrs() {
return {
// TODO:
// Should this attribute not be included when no-select-on-click is set
// since this attribute implies keyboard navigation?
'aria-multiselectable': !this.isSelectable ? null : this.selectableIsMultiSelect ? 'true' : 'false'
};
}
},
watch: {
computedItems: function computedItems(newVal, oldVal) {
// Reset for selectable
var equal = false;
if (this.isSelectable && this.selectedRows.length > 0) {
// Quick check against array length
equal = isArray(newVal) && isArray(oldVal) && newVal.length === oldVal.length;
for (var i = 0; equal && i < newVal.length; i++) {
// Look for the first non-loosely equal row, after ignoring reserved fields
equal = looseEqual(sanitizeRow(newVal[i]), sanitizeRow(oldVal[i]));
}
}
if (!equal) {
this.clearSelected();
}
},
selectable: function selectable(newVal, oldVal) {
this.clearSelected();
this.setSelectionHandlers(newVal);
},
selectMode: function selectMode(newVal, oldVal) {
this.clearSelected();
},
hasSelectableRowClick: function hasSelectableRowClick(newVal, oldVal) {
this.clearSelected();
this.setSelectionHandlers(!newVal);
},
selectedRows: function selectedRows(_selectedRows, oldVal) {
var _this = this;
if (this.isSelectable && !looseEqual(_selectedRows, oldVal)) {
var items = []; // `.forEach()` skips over non-existent indices (on sparse arrays)
_selectedRows.forEach(function (v, idx) {
if (v) {
items.push(_this.computedItems[idx]);
}
});
this.$emit('row-selected', items);
}
}
},
beforeMount: function beforeMount() {
// Set up handlers if needed
if (this.isSelectable) {
this.setSelectionHandlers(true);
}
},
methods: {
// Public methods
selectRow: function selectRow(index) {
// Select a particular row (indexed based on computedItems)
if (this.isSelectable && isNumber(index) && index >= 0 && index < this.computedItems.length && !this.isRowSelected(index)) {
var selectedRows = this.selectableIsMultiSelect ? this.selectedRows.slice() : [];
selectedRows[index] = true;
this.selectedLastClicked = -1;
this.selectedRows = selectedRows;
}
},
unselectRow: function unselectRow(index) {
// Un-select a particular row (indexed based on `computedItems`)
if (this.isSelectable && isNumber(index) && this.isRowSelected(index)) {
var selectedRows = this.selectedRows.slice();
selectedRows[index] = false;
this.selectedLastClicked = -1;
this.selectedRows = selectedRows;
}
},
selectAllRows: function selectAllRows() {
var length = this.computedItems.length;
if (this.isSelectable && length > 0) {
this.selectedLastClicked = -1;
this.selectedRows = this.selectableIsMultiSelect ? range(length).map(function (i) {
return true;
}) : [true];
}
},
isRowSelected: function isRowSelected(index) {
// Determine if a row is selected (indexed based on `computedItems`)
return Boolean(isNumber(index) && this.selectedRows[index]);
},
clearSelected: function clearSelected() {
// Clear any active selected row(s)
this.selectedLastClicked = -1;
this.selectedRows = [];
},
// Internal private methods
selectableRowClasses: function selectableRowClasses(index) {
if (this.isSelectable && this.isRowSelected(index)) {
var variant = this.selectedVariant;
return _defineProperty({
'b-table-row-selected': true
}, "".concat(this.dark ? 'bg' : 'table', "-").concat(variant), variant);
} else {
return {};
}
},
selectableRowAttrs: function selectableRowAttrs(index) {
return {
'aria-selected': !this.isSelectable ? null : this.isRowSelected(index) ? 'true' : 'false'
};
},
setSelectionHandlers: function setSelectionHandlers(on) {
var method = on && !this.noSelectOnClick ? '$on' : '$off'; // Handle row-clicked event
this[method]('row-clicked', this.selectionHandler); // Clear selection on filter, pagination, and sort changes
this[method]('filtered', this.clearSelected);
this[method]('context-changed', this.clearSelected);
},
selectionHandler: function selectionHandler(item, index, evt) {
/* istanbul ignore if: should never happen */
if (!this.isSelectable || this.noSelectOnClick) {
// Don't do anything if table is not in selectable mode
this.clearSelected();
return;
}
var selectMode = this.selectMode;
var selectedRows = this.selectedRows.slice();
var selected = !selectedRows[index]; // Note 'multi' mode needs no special event handling
if (selectMode === 'single') {
selectedRows = [];
} else if (selectMode === 'range') {
if (this.selectedLastRow > -1 && evt.shiftKey) {
// range
for (var idx = Math.min(this.selectedLastRow, index); idx <= Math.max(this.selectedLastRow, index); idx++) {
selectedRows[idx] = true;
}
selected = true;
} else {
if (!(evt.ctrlKey || evt.metaKey)) {
// Clear range selection if any
selectedRows = [];
selected = true;
}
this.selectedLastRow = selected ? index : -1;
}
}
selectedRows[index] = selected;
this.selectedRows = selectedRows;
}
}
};
@@ -0,0 +1,314 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 stableSort from '../../../utils/stable-sort';
import startCase from '../../../utils/startcase';
import { arrayIncludes } from '../../../utils/array';
import { isFunction, isUndefinedOrNull } from '../../../utils/inspect';
import defaultSortCompare from './default-sort-compare';
export default {
props: {
sortBy: {
type: String,
default: ''
},
sortDesc: {
// TODO: Make this tri-state: true, false, null
type: Boolean,
default: false
},
sortDirection: {
// This prop is named incorrectly
// It should be `initialSortDirection` as it is a bit misleading
// (not to mention it screws up the ARIA label on the headers)
type: String,
default: 'asc',
validator: function validator(direction) {
return arrayIncludes(['asc', 'desc', 'last'], direction);
}
},
sortCompare: {
type: Function,
default: null
},
sortCompareOptions: {
// Supported localCompare options, see `options` section of:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
type: Object,
default: function _default() {
return {
numeric: true
};
}
},
sortCompareLocale: {
// String: locale code
// Array: array of Locale strings
type: [String, Array] // default: undefined
},
sortNullLast: {
// Sort null and undefined to appear last
type: Boolean,
default: false
},
noSortReset: {
// Another prop that should have had a better name.
// It should be noSortClear (on non-sortable headers).
// We will need to make sure the documentation is clear on what
// this prop does (as well as in the code for future reference)
type: Boolean,
default: false
},
labelSortAsc: {
type: String,
default: 'Click to sort Ascending'
},
labelSortDesc: {
type: String,
default: 'Click to sort Descending'
},
labelSortClear: {
type: String,
default: 'Click to clear sorting'
},
noLocalSorting: {
type: Boolean,
default: false
},
noFooterSorting: {
type: Boolean,
default: false
},
sortIconLeft: {
// Place the sorting icon on the left of the header cells
type: Boolean,
default: false
}
},
data: function data() {
return {
localSortBy: this.sortBy || '',
localSortDesc: this.sortDesc || false
};
},
computed: {
localSorting: function localSorting() {
return this.hasProvider ? !!this.noProviderSorting : !this.noLocalSorting;
},
isSortable: function isSortable() {
return this.computedFields.some(function (f) {
return f.sortable;
});
},
sortedItems: function sortedItems() {
// Sorts the filtered items and returns a new array of the sorted items
// or the original items array if not sorted.
var items = (this.filteredItems || this.localItems || []).slice();
var sortBy = this.localSortBy;
var sortDesc = this.localSortDesc;
var sortCompare = this.sortCompare;
var localSorting = this.localSorting;
var sortOptions = _objectSpread({}, this.sortCompareOptions, {
usage: 'sort'
});
var sortLocale = this.sortCompareLocale || undefined;
var nullLast = this.sortNullLast;
if (sortBy && localSorting) {
var field = this.computedFieldsObj[sortBy] || {};
var sortByFormatted = field.sortByFormatted;
var formatter = isFunction(sortByFormatted) ? sortByFormatted : sortByFormatted ? this.getFieldFormatter(sortBy) : undefined; // `stableSort` returns a new array, and leaves the original array intact
return stableSort(items, function (a, b) {
var result = null;
if (isFunction(sortCompare)) {
// Call user provided sortCompare routine
result = sortCompare(a, b, sortBy, sortDesc, formatter, sortOptions, sortLocale);
}
if (isUndefinedOrNull(result) || result === false) {
// Fallback to built-in defaultSortCompare if sortCompare
// is not defined or returns null/false
result = defaultSortCompare(a, b, sortBy, sortDesc, formatter, sortOptions, sortLocale, nullLast);
} // Negate result if sorting in descending order
return (result || 0) * (sortDesc ? -1 : 1);
});
}
return items;
}
},
watch: {
isSortable: function isSortable(newVal, oldVal)
/* istanbul ignore next: pain in the butt to test */
{
if (newVal) {
if (this.isSortable) {
this.$on('head-clicked', this.handleSort);
}
} else {
this.$off('head-clicked', this.handleSort);
}
},
sortDesc: function sortDesc(newVal, oldVal) {
if (newVal === this.localSortDesc) {
/* istanbul ignore next */
return;
}
this.localSortDesc = newVal || false;
},
sortBy: function sortBy(newVal, oldVal) {
if (newVal === this.localSortBy) {
/* istanbul ignore next */
return;
}
this.localSortBy = newVal || '';
},
// Update .sync props
localSortDesc: function localSortDesc(newVal, oldVal) {
// Emit update to sort-desc.sync
if (newVal !== oldVal) {
this.$emit('update:sortDesc', newVal);
}
},
localSortBy: function localSortBy(newVal, oldVal) {
if (newVal !== oldVal) {
this.$emit('update:sortBy', newVal);
}
}
},
created: function created() {
if (this.isSortable) {
this.$on('head-clicked', this.handleSort);
}
},
methods: {
// Handlers
// Need to move from thead-mixin
handleSort: function handleSort(key, field, evt, isFoot) {
var _this = this;
if (!this.isSortable) {
/* istanbul ignore next */
return;
}
if (isFoot && this.noFooterSorting) {
return;
} // TODO: make this tri-state sorting
// cycle desc => asc => none => desc => ...
var sortChanged = false;
var toggleLocalSortDesc = function toggleLocalSortDesc() {
var sortDirection = field.sortDirection || _this.sortDirection;
if (sortDirection === 'asc') {
_this.localSortDesc = false;
} else if (sortDirection === 'desc') {
_this.localSortDesc = true;
} else {// sortDirection === 'last'
// Leave at last sort direction from previous column
}
};
if (field.sortable) {
if (key === this.localSortBy) {
// Change sorting direction on current column
this.localSortDesc = !this.localSortDesc;
} else {
// Start sorting this column ascending
this.localSortBy = key; // this.localSortDesc = false
toggleLocalSortDesc();
}
sortChanged = true;
} else if (this.localSortBy && !this.noSortReset) {
this.localSortBy = '';
toggleLocalSortDesc();
sortChanged = true;
}
if (sortChanged) {
// Sorting parameters changed
this.$emit('sort-changed', this.context);
}
},
// methods to compute classes and attrs for thead>th cells
sortTheadThClasses: function sortTheadThClasses(key, field, isFoot) {
return {
// If sortable and sortIconLeft are true, then place sort icon on the left
'b-table-sort-icon-left': field.sortable && this.sortIconLeft && !(isFoot && this.noFooterSorting)
};
},
sortTheadThAttrs: function sortTheadThAttrs(key, field, isFoot) {
if (!this.isSortable || isFoot && this.noFooterSorting) {
// No attributes if not a sortable table
return {};
}
var sortable = field.sortable;
var ariaLabel = '';
if ((!field.label || !field.label.trim()) && !field.headerTitle) {
// In case field's label and title are empty/blank, we need to
// add a hint about what the column is about for non-sighted users.
// This is duplicated code from tbody-row mixin, but we need it
// here as well, since we overwrite the original aria-label.
/* istanbul ignore next */
ariaLabel = startCase(key);
} // The correctness of these labels is very important for screen-reader users.
var ariaLabelSorting = '';
if (sortable) {
if (this.localSortBy === key) {
// currently sorted sortable column.
ariaLabelSorting = this.localSortDesc ? this.labelSortAsc : this.labelSortDesc;
} else {
// Not currently sorted sortable column.
// Not using nested ternary's here for clarity/readability
// Default for ariaLabel
ariaLabelSorting = this.localSortDesc ? this.labelSortDesc : this.labelSortAsc; // Handle sortDirection setting
var sortDirection = this.sortDirection || field.sortDirection;
if (sortDirection === 'asc') {
ariaLabelSorting = this.labelSortAsc;
} else if (sortDirection === 'desc') {
ariaLabelSorting = this.labelSortDesc;
}
}
} else if (!this.noSortReset) {
// Non sortable column
ariaLabelSorting = this.localSortBy ? this.labelSortClear : '';
} // Assemble the aria-label attribute value
ariaLabel = [ariaLabel.trim(), ariaLabelSorting.trim()].filter(Boolean).join(': '); // Assemble the aria-sort attribute value
var ariaSort = sortable && this.localSortBy === key ? this.localSortDesc ? 'descending' : 'ascending' : sortable ? 'none' : null; // Return the attributes
// (All the above just to get these two values)
return {
'aria-label': ariaLabel || null,
'aria-sort': ariaSort
};
}
}
};
@@ -0,0 +1,25 @@
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; }
// Mixin for providing stacked tables
export default {
props: {
stacked: {
type: [Boolean, String],
default: false
}
},
computed: {
isStacked: function isStacked() {
// `true` when always stacked, or returns breakpoint specified
return this.stacked === '' ? true : this.stacked;
},
isStackedAlways: function isStackedAlways() {
return this.isStacked === true;
},
stackedTableClasses: function stackedTableClasses() {
return _defineProperty({
'b-table-stacked': this.isStackedAlways
}, "b-table-stacked-".concat(this.stacked), !this.isStackedAlways && this.isStacked);
}
}
};
@@ -0,0 +1,172 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 { isBoolean } from '../../../utils/inspect'; // Main `<table>` render mixin
// Includes all main table styling options
export default {
// Don't place attributes on root element automatically,
// as table could be wrapped in responsive `<div>`
inheritAttrs: false,
provide: function provide() {
return {
bvTable: this
};
},
props: {
striped: {
type: Boolean,
default: false
},
bordered: {
type: Boolean,
default: false
},
borderless: {
type: Boolean,
default: false
},
outlined: {
type: Boolean,
default: false
},
dark: {
type: Boolean,
default: false
},
hover: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
fixed: {
type: Boolean,
default: false
},
responsive: {
type: [Boolean, String],
default: false
},
stickyHeader: {
// If a string, it is assumed to be the table `max-height` value
type: [Boolean, String],
default: false
},
noBorderCollapse: {
type: Boolean,
default: false
},
captionTop: {
type: Boolean,
default: false
},
tableVariant: {
type: String,
default: null
},
tableClass: {
type: [String, Array, Object],
default: null
}
},
computed: {
// Layout related computed props
isResponsive: function isResponsive() {
var responsive = this.responsive === '' ? true : this.responsive;
return this.isStacked ? false : responsive;
},
isStickyHeader: function isStickyHeader() {
var stickyHeader = this.stickyHeader === '' ? true : this.stickyHeader;
return this.isStacked ? false : stickyHeader;
},
wrapperClasses: function wrapperClasses() {
return [this.isStickyHeader ? 'b-table-sticky-header' : '', this.isResponsive === true ? 'table-responsive' : this.isResponsive ? "table-responsive-".concat(this.responsive) : ''].filter(Boolean);
},
wrapperStyles: function wrapperStyles() {
return this.isStickyHeader && !isBoolean(this.isStickyHeader) ? {
maxHeight: this.isStickyHeader
} : {};
},
tableClasses: function tableClasses() {
var hover = this.isTableSimple ? this.hover : this.hover && this.computedItems.length > 0 && !this.computedBusy;
return [// User supplied classes
this.tableClass, // Styling classes
{
'table-striped': this.striped,
'table-hover': hover,
'table-dark': this.dark,
'table-bordered': this.bordered,
'table-borderless': this.borderless,
'table-sm': this.small,
// The following are b-table custom styles
border: this.outlined,
'b-table-fixed': this.fixed,
'b-table-caption-top': this.captionTop,
'b-table-no-border-collapse': this.noBorderCollapse
}, this.tableVariant ? "".concat(this.dark ? 'bg' : 'table', "-").concat(this.tableVariant) : '', // Stacked table classes
this.stackedTableClasses, // Selectable classes
this.selectableTableClasses];
},
tableAttrs: function tableAttrs() {
// Preserve user supplied aria-describedby, if provided in `$attrs`
var adb = [(this.$attrs || {})['aria-describedby'], this.captionId].filter(Boolean).join(' ') || null;
var items = this.computedItems;
var filteredItems = this.filteredItems;
var fields = this.computedFields;
var selectableAttrs = this.selectableTableAttrs || {};
var ariaAttrs = this.isTableSimple ? {} : {
'aria-busy': this.computedBusy ? 'true' : 'false',
'aria-colcount': String(fields.length),
'aria-describedby': adb
};
var rowCount = items && filteredItems && filteredItems.length > items.length ? String(filteredItems.length) : null;
return _objectSpread({
// We set `aria-rowcount` before merging in `$attrs`,
// in case user has supplied their own
'aria-rowcount': rowCount
}, this.$attrs, {
// Now we can override any `$attrs` here
id: this.safeId(),
role: 'table'
}, ariaAttrs, {}, selectableAttrs);
}
},
render: function render(h) {
var $content = [];
if (this.isTableSimple) {
$content.push(this.normalizeSlot('default', {}));
} else {
// Build the `<caption>` (from caption mixin)
$content.push(this.renderCaption ? this.renderCaption() : null); // Build the `<colgroup>`
$content.push(this.renderColgroup ? this.renderColgroup() : null); // Build the `<thead>`
$content.push(this.renderThead ? this.renderThead() : null); // Build the `<tbody>`
$content.push(this.renderTbody ? this.renderTbody() : null); // Build the `<tfoot>`
$content.push(this.renderTfoot ? this.renderTfoot() : null);
} // Assemble `<table>`
var $table = h('table', {
key: 'b-table',
staticClass: 'table b-table',
class: this.tableClasses,
attrs: this.tableAttrs
}, $content.filter(Boolean)); // Add responsive/sticky wrapper if needed and return table
return this.wrapperClasses.length > 0 ? h('div', {
key: 'wrap',
class: this.wrapperClasses,
style: this.wrapperStyles
}, [$table]) : $table;
}
};
@@ -0,0 +1,323 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 get from '../../../utils/get';
import toString from '../../../utils/to-string';
import { isFunction, isString, isUndefinedOrNull } from '../../../utils/inspect';
import { BTr } from '../tr';
import { BTd } from '../td';
import { BTh } from '../th';
var detailsSlotName = 'row-details';
export default {
props: {
tbodyTrClass: {
type: [String, Array, Object, Function],
default: null
},
detailsTdClass: {
type: [String, Array, Object],
default: null
}
},
methods: {
// Methods for computing classes, attributes and styles for table cells
getTdValues: function getTdValues(item, key, tdValue, defValue) {
var parent = this.$parent;
if (tdValue) {
var value = get(item, key, '');
if (isFunction(tdValue)) {
return tdValue(value, key, item);
} else if (isString(tdValue) && isFunction(parent[tdValue])) {
return parent[tdValue](value, key, item);
}
return tdValue;
}
return defValue;
},
getThValues: function getThValues(item, key, thValue, type, defValue) {
var parent = this.$parent;
if (thValue) {
var value = get(item, key, '');
if (isFunction(thValue)) {
return thValue(value, key, item, type);
} else if (isString(thValue) && isFunction(parent[thValue])) {
return parent[thValue](value, key, item, type);
}
return thValue;
}
return defValue;
},
// Method to get the value for a field
getFormattedValue: function getFormattedValue(item, field) {
var key = field.key;
var formatter = this.getFieldFormatter(key);
var value = get(item, key, null);
if (isFunction(formatter)) {
value = formatter(value, key, item);
}
return isUndefinedOrNull(value) ? '' : value;
},
// Factory function methods
toggleDetailsFactory: function toggleDetailsFactory(hasDetailsSlot, item) {
var _this = this;
// Returns a function to toggle a row's details slot
return function () {
if (hasDetailsSlot) {
_this.$set(item, '_showDetails', !item._showDetails);
}
};
},
// Row event handlers
rowHovered: function rowHovered(evt) {
// `mouseenter` handler (non-bubbling)
// `this.tbodyRowEvtStopped` from tbody mixin
if (!this.tbodyRowEvtStopped(evt)) {
// `this.emitTbodyRowEvent` from tbody mixin
this.emitTbodyRowEvent('row-hovered', evt);
}
},
rowUnhovered: function rowUnhovered(evt) {
// `mouseleave` handler (non-bubbling)
// `this.tbodyRowEvtStopped` from tbody mixin
if (!this.tbodyRowEvtStopped(evt)) {
// `this.emitTbodyRowEvent` from tbody mixin
this.emitTbodyRowEvent('row-unhovered', evt);
}
},
// Render helpers
renderTbodyRowCell: function renderTbodyRowCell(field, colIndex, item, rowIndex) {
var _this2 = this;
// Renders a TD or TH for a row's field
var h = this.$createElement;
var hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName);
var formatted = this.getFormattedValue(item, field);
var key = field.key;
var stickyColumn = !this.isStacked && (this.isResponsive || this.stickyHeader) && field.stickyColumn; // We only uses the helper components for sticky columns to
// improve performance of BTable/BTableLite by reducing the
// total number of vue instances created during render
var cellTag = stickyColumn ? field.isRowHeader ? BTh : BTd : field.isRowHeader ? 'th' : 'td';
var cellVariant = item._cellVariants && item._cellVariants[key] ? item._cellVariants[key] : field.variant || null;
var data = {
// For the Vue key, we concatenate the column index and
// field key (as field keys could be duplicated)
// TODO: Although we do prevent duplicate field keys...
// So we could change this to: `row-${rowIndex}-cell-${key}`
key: "row-".concat(rowIndex, "-cell-").concat(colIndex, "-").concat(key),
class: [field.class ? field.class : '', this.getTdValues(item, key, field.tdClass, '')],
props: {},
attrs: _objectSpread({
'aria-colindex': String(colIndex + 1)
}, field.isRowHeader ? this.getThValues(item, key, field.thAttr, 'row', {}) : this.getTdValues(item, key, field.tdAttr, {}))
};
if (stickyColumn) {
// We are using the helper BTd or BTh
data.props = {
stackedHeading: this.isStacked ? field.label : null,
stickyColumn: true,
variant: cellVariant
};
} else {
// Using native TD or TH element, so we need to
// add in the attributes and variant class
data.attrs['data-label'] = this.isStacked && !isUndefinedOrNull(field.label) ? toString(field.label) : null;
data.attrs.role = field.isRowHeader ? 'rowheader' : 'cell';
data.attrs.scope = field.isRowHeader ? 'row' : null; // Add in the variant class
if (cellVariant) {
data.class.push("".concat(this.dark ? 'bg' : 'table', "-").concat(cellVariant));
}
}
var slotScope = {
item: item,
index: rowIndex,
field: field,
unformatted: get(item, key, ''),
value: formatted,
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item),
detailsShowing: Boolean(item._showDetails)
}; // If table supports selectable mode, then add in the following scope
// this.supportsSelectableRows will be undefined if mixin isn't loaded
if (this.supportsSelectableRows) {
slotScope.rowSelected = this.isRowSelected(rowIndex);
slotScope.selectRow = function () {
return _this2.selectRow(rowIndex);
};
slotScope.unselectRow = function () {
return _this2.unselectRow(rowIndex);
};
} // The new `v-slot` syntax doesn't like a slot name starting with
// a square bracket and if using in-document HTML templates, the
// v-slot attributes are lower-cased by the browser.
// Switched to round bracket syntax to prevent confusion with
// dynamic slot name syntax.
// We look for slots in this order: `cell(${key})`, `cell(${key.toLowerCase()})`, 'cell()'
// Slot names are now cached by mixin tbody in `this.$_bodyFieldSlotNameCache`
// Will be `null` if no slot (or fallback slot) exists
var slotName = this.$_bodyFieldSlotNameCache[key];
var $childNodes = slotName ? this.normalizeSlot(slotName, slotScope) : toString(formatted);
if (this.isStacked) {
// We wrap in a DIV to ensure rendered as a single cell when visually stacked!
$childNodes = [h('div', {}, [$childNodes])];
} // Render either a td or th cell
return h(cellTag, data, [$childNodes]);
},
renderTbodyRow: function renderTbodyRow(item, rowIndex) {
var _this3 = this;
// Renders an item's row (or rows if details supported)
var h = this.$createElement;
var fields = this.computedFields;
var tableStriped = this.striped;
var hasDetailsSlot = this.hasNormalizedSlot(detailsSlotName);
var rowShowDetails = Boolean(item._showDetails && hasDetailsSlot);
var hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick; // We can return more than one TR if rowDetails enabled
var $rows = []; // Details ID needed for `aria-details` when details showing
// We set it to `null` when not showing so that attribute
// does not appear on the element
var detailsId = rowShowDetails ? this.safeId("_details_".concat(rowIndex, "_")) : null; // For each item data field in row
var $tds = fields.map(function (field, colIndex) {
return _this3.renderTbodyRowCell(field, colIndex, item, rowIndex);
}); // Calculate the row number in the dataset (indexed from 1)
var ariaRowIndex = null;
if (this.currentPage && this.perPage && this.perPage > 0) {
ariaRowIndex = String((this.currentPage - 1) * this.perPage + rowIndex + 1);
} // Create a unique :key to help ensure that sub components are re-rendered rather than
// re-used, which can cause issues. If a primary key is not provided we use the rendered
// rows index within the tbody.
// See: https://github.com/bootstrap-vue/bootstrap-vue/issues/2410
var primaryKey = this.primaryKey;
var primaryKeyValue = toString(get(item, primaryKey)) || null;
var rowKey = primaryKeyValue || String(rowIndex); // If primary key is provided, use it to generate a unique ID on each tbody > tr
// In the format of '{tableId}__row_{primaryKeyValue}'
var rowId = primaryKeyValue ? this.safeId("_row_".concat(primaryKeyValue)) : null; // Selectable classes and attributes
var selectableClasses = this.selectableRowClasses ? this.selectableRowClasses(rowIndex) : {};
var selectableAttrs = this.selectableRowAttrs ? this.selectableRowAttrs(rowIndex) : {}; // Add the item row
$rows.push(h(BTr, {
key: "__b-table-row-".concat(rowKey, "__"),
ref: 'itemRows',
refInFor: true,
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(item, 'row') : this.tbodyTrClass, selectableClasses, rowShowDetails ? 'b-table-has-details' : ''],
props: {
variant: item._rowVariant || null
},
attrs: _objectSpread({
id: rowId,
tabindex: hasRowClickHandler ? '0' : null,
'data-pk': primaryKeyValue || null,
'aria-details': detailsId,
'aria-owns': detailsId,
'aria-rowindex': ariaRowIndex
}, selectableAttrs),
on: {
// Note: These events are not A11Y friendly!
mouseenter: this.rowHovered,
mouseleave: this.rowUnhovered
}
}, $tds)); // Row Details slot
if (rowShowDetails) {
var detailsScope = {
item: item,
index: rowIndex,
fields: fields,
toggleDetails: this.toggleDetailsFactory(hasDetailsSlot, item)
}; // If table supports selectable mode, then add in the following scope
// this.supportsSelectableRows will be undefined if mixin isn't loaded
if (this.supportsSelectableRows) {
detailsScope.rowSelected = this.isRowSelected(rowIndex);
detailsScope.selectRow = function () {
return _this3.selectRow(rowIndex);
};
detailsScope.unselectRow = function () {
return _this3.unselectRow(rowIndex);
};
} // Render the details slot in a TD
var $details = h(BTd, {
props: {
colspan: fields.length
},
class: this.detailsTdClass
}, [this.normalizeSlot(detailsSlotName, detailsScope)]); // Add a hidden row to keep table row striping consistent when details showing
// Only added if the table is striped
if (tableStriped) {
$rows.push( // We don't use `BTr` here as we don't need the extra functionality
h('tr', {
key: "__b-table-details-stripe__".concat(rowKey),
staticClass: 'd-none',
attrs: {
'aria-hidden': 'true',
role: 'presentation'
}
}));
} // Add the actual details row
$rows.push(h(BTr, {
key: "__b-table-details__".concat(rowKey),
staticClass: 'b-table-details',
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(item, detailsSlotName) : this.tbodyTrClass],
props: {
variant: item._rowVariant || null
},
attrs: {
id: detailsId,
tabindex: '-1'
}
}, [$details]));
} else if (hasDetailsSlot) {
// Only add the placeholder if a the table has a row-details slot defined (but not shown)
$rows.push(h());
if (tableStriped) {
// Add extra placeholder if table is striped
$rows.push(h());
}
} // Return the row(s)
return $rows;
}
}
};
+227
View File
@@ -0,0 +1,227 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 KeyCodes from '../../../utils/key-codes';
import { arrayIncludes, from as arrayFrom } from '../../../utils/array';
import { closest, isElement } from '../../../utils/dom';
import { props as tbodyProps, BTbody } from '../tbody';
import filterEvent from './filter-event';
import textSelectionActive from './text-selection-active';
import tbodyRowMixin from './mixin-tbody-row';
var props = _objectSpread({}, tbodyProps, {
tbodyClass: {
type: [String, Array, Object] // default: undefined
}
});
export default {
mixins: [tbodyRowMixin],
props: props,
methods: {
// Helper methods
getTbodyTrs: function getTbodyTrs() {
// Returns all the item TR elements (excludes detail and spacer rows)
// `this.$refs.itemRows` is an array of item TR components/elements
// Rows should all be B-TR components, but we map to TR elements
// Also note that `this.$refs.itemRows` may not always be in document order
var tbody = this.$refs.tbody.$el || this.$refs.tbody;
var trs = (this.$refs.itemRows || []).map(function (tr) {
return tr.$el || tr;
}); // TODO: This may take time for tables many rows, so we may want to cache
// the result of this during each render cycle on a non-reactive
// property. We clear out the cache as each render starts, and
// populate it on first access of this method if null
return arrayFrom(tbody.children).filter(function (tr) {
return arrayIncludes(trs, tr);
});
},
getTbodyTrIndex: function getTbodyTrIndex(el) {
// Returns index of a particular TBODY item TR
// We set `true` on closest to include self in result
/* istanbul ignore next: should not normally happen */
if (!isElement(el)) {
return -1;
}
var tr = el.tagName === 'TR' ? el : closest('tr', el, true);
return tr ? this.getTbodyTrs().indexOf(tr) : -1;
},
emitTbodyRowEvent: function emitTbodyRowEvent(type, evt) {
// Emits a row event, with the item object, row index and original event
if (type && evt && evt.target) {
var rowIndex = this.getTbodyTrIndex(evt.target);
if (rowIndex > -1) {
// The array of TRs correlate to the `computedItems` array
var item = this.computedItems[rowIndex];
this.$emit(type, item, rowIndex, evt);
}
}
},
tbodyRowEvtStopped: function tbodyRowEvtStopped(evt) {
return this.stopIfBusy && this.stopIfBusy(evt);
},
// Delegated row event handlers
onTbodyRowKeydown: function onTbodyRowKeydown(evt) {
// Keyboard navigation and row click emulation
var target = evt.target;
if (this.tbodyRowEvtStopped(evt) || target.tagName !== 'TR' || target !== document.activeElement || target.tabIndex !== 0) {
// Early exit if not an item row TR
return;
}
var keyCode = evt.keyCode;
if (arrayIncludes([KeyCodes.ENTER, KeyCodes.SPACE], keyCode)) {
// Emulated click for keyboard users, transfer to click handler
evt.stopPropagation();
evt.preventDefault();
this.onTBodyRowClicked(evt);
} else if (arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode)) {
// Keyboard navigation
var rowIndex = this.getTbodyTrIndex(target);
if (rowIndex > -1) {
evt.stopPropagation();
evt.preventDefault();
var trs = this.getTbodyTrs();
var shift = evt.shiftKey;
if (keyCode === KeyCodes.HOME || shift && keyCode === KeyCodes.UP) {
// Focus first row
trs[0].focus();
} else if (keyCode === KeyCodes.END || shift && keyCode === KeyCodes.DOWN) {
// Focus last row
trs[trs.length - 1].focus();
} else if (keyCode === KeyCodes.UP && rowIndex > 0) {
// Focus previous row
trs[rowIndex - 1].focus();
} else if (keyCode === KeyCodes.DOWN && rowIndex < trs.length - 1) {
// Focus next row
trs[rowIndex + 1].focus();
}
}
}
},
onTBodyRowClicked: function onTBodyRowClicked(evt) {
if (this.tbodyRowEvtStopped(evt)) {
// If table is busy, then don't propagate
return;
} else if (filterEvent(evt) || textSelectionActive(this.$el)) {
// Clicked on a non-disabled control so ignore
// Or user is selecting text, so ignore
return;
}
this.emitTbodyRowEvent('row-clicked', evt);
},
onTbodyRowMiddleMouseRowClicked: function onTbodyRowMiddleMouseRowClicked(evt) {
if (!this.tbodyRowEvtStopped(evt) && evt.which === 2) {
this.emitTbodyRowEvent('row-middle-clicked', evt);
}
},
onTbodyRowContextmenu: function onTbodyRowContextmenu(evt) {
if (!this.tbodyRowEvtStopped(evt)) {
this.emitTbodyRowEvent('row-contextmenu', evt);
}
},
onTbodyRowDblClicked: function onTbodyRowDblClicked(evt) {
if (!this.tbodyRowEvtStopped(evt) && !filterEvent(evt)) {
this.emitTbodyRowEvent('row-dblclicked', evt);
}
},
// Note: Row hover handlers are handled by the tbody-row mixin
// As mouseenter/mouseleave events do not bubble
//
// Render Helper
renderTbody: function renderTbody() {
var _this = this;
// Render the tbody element and children
var items = this.computedItems; // Shortcut to `createElement` (could use `this._c()` instead)
var h = this.$createElement;
var hasRowClickHandler = this.$listeners['row-clicked'] || this.hasSelectableRowClick; // Prepare the tbody rows
var $rows = []; // Add the item data rows or the busy slot
var $busy = this.renderBusy ? this.renderBusy() : null;
if ($busy) {
// If table is busy and a busy slot, then return only the busy "row" indicator
$rows.push($busy);
} else {
// Table isn't busy, or we don't have a busy slot
// Create a slot cache for improved performance when looking up cell slot names
// Values will be keyed by the field's `key` and will store the slot's name
// Slots could be dynamic (i.e. `v-if`), so we must compute on each render
// Used by tbody-row mixin render helper
var cache = {};
var defaultSlotName = this.hasNormalizedSlot('cell()') ? 'cell()' : null;
this.computedFields.forEach(function (field) {
var key = field.key;
var fullName = "cell(".concat(key, ")");
var lowerName = "cell(".concat(key.toLowerCase(), ")");
cache[key] = _this.hasNormalizedSlot(fullName) ? fullName : _this.hasNormalizedSlot(lowerName) ? lowerName : defaultSlotName;
}); // Created as a non-reactive property so to not trigger component updates
// Must be a fresh object each render
this.$_bodyFieldSlotNameCache = cache; // Add static top row slot (hidden in visibly stacked mode
// as we can't control `data-label` attr)
$rows.push(this.renderTopRow ? this.renderTopRow() : h()); // Render the rows
items.forEach(function (item, rowIndex) {
// Render the individual item row (rows if details slot)
$rows.push(_this.renderTbodyRow(item, rowIndex));
}); // Empty items / empty filtered row slot (only shows if `items.length < 1`)
$rows.push(this.renderEmpty ? this.renderEmpty() : h()); // Static bottom row slot (hidden in visibly stacked mode
// as we can't control `data-label` attr)
$rows.push(this.renderBottomRow ? this.renderBottomRow() : h());
}
var handlers = {
// TODO: We may want to to only instantiate these handlers
// if there is an event listener registered
auxclick: this.onTbodyRowMiddleMouseRowClicked,
// TODO: Perhaps we do want to automatically prevent the
// default context menu from showing if there is
// a `row-contextmenu` listener registered.
contextmenu: this.onTbodyRowContextmenu,
// The following event(s) is not considered A11Y friendly
dblclick: this.onTbodyRowDblClicked // hover events (mouseenter/mouseleave) ad handled by tbody-row mixin
};
if (hasRowClickHandler) {
handlers.click = this.onTBodyRowClicked;
handlers.keydown = this.onTbodyRowKeydown;
} // Assemble rows into the tbody
var $tbody = h(BTbody, {
ref: 'tbody',
class: this.tbodyClass || null,
props: {
tbodyTransitionProps: this.tbodyTransitionProps,
tbodyTransitionHandlers: this.tbodyTransitionHandlers
},
// BTbody transfers all native event listeners to the root element
// TODO: Only set the handlers if the table is not busy
on: handlers
}, $rows); // Return the assembled tbody
return $tbody;
}
}
};
+55
View File
@@ -0,0 +1,55 @@
import { getComponentConfig } from '../../../utils/config';
import { BTfoot } from '../tfoot';
export default {
props: {
footClone: {
type: Boolean,
default: false
},
footVariant: {
type: String,
// 'dark', 'light', or `null` (or custom)
default: function _default() {
return getComponentConfig('BTable', 'footVariant');
}
},
footRowVariant: {
type: String,
// Any Bootstrap theme variant (or custom). Falls back to `headRowVariant`
default: null
},
tfootClass: {
type: [String, Array, Object],
default: null
},
tfootTrClass: {
type: [String, Array, Object],
default: null
}
},
methods: {
renderTFootCustom: function renderTFootCustom() {
var h = this.$createElement;
if (this.hasNormalizedSlot('custom-foot')) {
return h(BTfoot, {
key: 'bv-tfoot-custom',
class: this.tfootClass || null,
props: {
footVariant: this.footVariant || this.headVariant || null
}
}, this.normalizeSlot('custom-foot', {
items: this.computedItems.slice(),
fields: this.computedFields.slice(),
columns: this.computedFields.length
}));
} else {
return h();
}
},
renderTfoot: function renderTfoot() {
// Passing true to renderThead will make it render a tfoot
return this.footClone ? this.renderThead(true) : this.renderTFootCustom();
}
}
};
+213
View File
@@ -0,0 +1,213 @@
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 KeyCodes from '../../../utils/key-codes';
import startCase from '../../../utils/startcase';
import { getComponentConfig } from '../../../utils/config';
import { htmlOrText } from '../../../utils/html';
import { isUndefinedOrNull } from '../../../utils/inspect';
import filterEvent from './filter-event';
import textSelectionActive from './text-selection-active';
import { BThead } from '../thead';
import { BTfoot } from '../tfoot';
import { BTr } from '../tr';
import { BTh } from '../th';
export default {
props: {
headVariant: {
type: String,
// 'light', 'dark' or `null` (or custom)
default: function _default() {
return getComponentConfig('BTable', 'headVariant');
}
},
headRowVariant: {
type: String,
// Any Bootstrap theme variant (or custom)
default: null
},
theadClass: {
type: [String, Array, Object] // default: undefined
},
theadTrClass: {
type: [String, Array, Object] // default: undefined
}
},
methods: {
fieldClasses: function fieldClasses(field) {
// Header field (<th>) classes
return [field.class ? field.class : '', field.thClass ? field.thClass : ''];
},
headClicked: function headClicked(evt, field, isFoot) {
if (this.stopIfBusy && this.stopIfBusy(evt)) {
// If table is busy (via provider) then don't propagate
return;
} else if (filterEvent(evt)) {
// Clicked on a non-disabled control so ignore
return;
} else if (textSelectionActive(this.$el)) {
// User is selecting text, so ignore
/* istanbul ignore next: JSDOM doesn't support getSelection() */
return;
}
evt.stopPropagation();
evt.preventDefault();
this.$emit('head-clicked', field.key, field, evt, isFoot);
},
renderThead: function renderThead() {
var _this = this;
var isFoot = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var h = this.$createElement;
var fields = this.computedFields || [];
if (this.isStackedAlways || fields.length === 0) {
// In always stacked mode, we don't bother rendering the head/foot
// Or if no field headings (empty table)
return h();
} // Reference to `selectAllRows` and `clearSelected()`, if table is selectable
var selectAllRows = this.isSelectable ? this.selectAllRows : function () {};
var clearSelected = this.isSelectable ? this.clearSelected : function () {}; // Helper function to generate a field <th> cell
var makeCell = function makeCell(field, colIndex) {
var ariaLabel = null;
if (!field.label.trim() && !field.headerTitle) {
// In case field's label and title are empty/blank
// We need to add a hint about what the column is about for non-sighted users
/* istanbul ignore next */
ariaLabel = startCase(field.key);
}
var hasHeadClickListener = _this.$listeners['head-clicked'] || _this.isSortable;
var handlers = {};
if (hasHeadClickListener) {
handlers.click = function (evt) {
_this.headClicked(evt, field, isFoot);
};
handlers.keydown = function (evt) {
var keyCode = evt.keyCode;
if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) {
_this.headClicked(evt, field, isFoot);
}
};
}
var sortAttrs = _this.isSortable ? _this.sortTheadThAttrs(field.key, field, isFoot) : {};
var sortClass = _this.isSortable ? _this.sortTheadThClasses(field.key, field, isFoot) : null;
var data = {
key: field.key,
class: [_this.fieldClasses(field), sortClass],
props: {
variant: field.variant,
stickyColumn: field.stickyColumn
},
style: field.thStyle || {},
attrs: _objectSpread({
// We only add a tabindex of 0 if there is a head-clicked listener
tabindex: hasHeadClickListener ? '0' : null,
abbr: field.headerAbbr || null,
title: field.headerTitle || null,
'aria-colindex': String(colIndex + 1),
'aria-label': ariaLabel
}, _this.getThValues(null, field.key, field.thAttr, isFoot ? 'foot' : 'head', {}), {}, sortAttrs),
on: handlers
}; // Handle edge case where in-document templates are used with new
// `v-slot:name` syntax where the browser lower-cases the v-slot's
// name (attributes become lower cased when parsed by the browser)
// We have replaced the square bracket syntax with round brackets
// to prevent confusion with dynamic slot names
var slotNames = ["head(".concat(field.key, ")"), "head(".concat(field.key.toLowerCase(), ")"), 'head()'];
if (isFoot) {
// Footer will fallback to header slot names
slotNames = ["foot(".concat(field.key, ")"), "foot(".concat(field.key.toLowerCase(), ")"), 'foot()'].concat(_toConsumableArray(slotNames));
}
var hasSlot = _this.hasNormalizedSlot(slotNames);
var slot = field.label;
if (hasSlot) {
slot = _this.normalizeSlot(slotNames, {
label: field.label,
column: field.key,
field: field,
isFoot: isFoot,
// Add in row select methods
selectAllRows: selectAllRows,
clearSelected: clearSelected
});
} else {
data.domProps = htmlOrText(field.labelHtml);
}
return h(BTh, data, slot);
}; // Generate the array of <th> cells
var $cells = fields.map(makeCell).filter(function (th) {
return th;
}); // Genrate the row(s)
var $trs = [];
if (isFoot) {
var trProps = {
variant: isUndefinedOrNull(this.footRowVariant) ? this.headRowVariant : this.footRowVariant
};
$trs.push(h(BTr, {
class: this.tfootTrClass,
props: trProps
}, $cells));
} else {
var scope = {
columns: fields.length,
fields: fields,
// Add in row select methods
selectAllRows: selectAllRows,
clearSelected: clearSelected
};
$trs.push(this.normalizeSlot('thead-top', scope) || h());
$trs.push(h(BTr, {
class: this.theadTrClass,
props: {
variant: this.headRowVariant
}
}, $cells));
}
return h(isFoot ? BTfoot : BThead, {
key: isFoot ? 'bv-tfoot' : 'bv-thead',
class: (isFoot ? this.tfootClass : this.theadClass) || null,
props: isFoot ? {
footVariant: this.footVariant || this.headVariant || null
} : {
headVariant: this.headVariant || null
}
}, $trs);
}
}
};
@@ -0,0 +1,25 @@
import { isFunction } from '../../../utils/inspect';
import { BTr } from '../tr';
var slotName = 'top-row';
export default {
methods: {
renderTopRow: function renderTopRow() {
var h = this.$createElement; // Add static Top Row slot (hidden in visibly stacked mode as we can't control the data-label)
// If in *always* stacked mode, we don't bother rendering the row
if (!this.hasNormalizedSlot(slotName) || this.stacked === true || this.stacked === '') {
return h();
}
var fields = this.computedFields;
return h(BTr, {
key: 'b-top-row',
staticClass: 'b-table-top-row',
class: [isFunction(this.tbodyTrClass) ? this.tbodyTrClass(null, 'row-top') : this.tbodyTrClass]
}, [this.normalizeSlot(slotName, {
columns: fields.length,
fields: fields
})]);
}
}
};
@@ -0,0 +1,92 @@
import startCase from '../../../utils/startcase';
import { isArray, isFunction, isObject, isString } from '../../../utils/inspect';
import { clone, keys } from '../../../utils/object';
import { IGNORED_FIELD_KEYS } from './constants'; // Private function to massage field entry into common object format
var processField = function processField(key, value) {
var field = null;
if (isString(value)) {
// Label shortcut
field = {
key: key,
label: value
};
} else if (isFunction(value)) {
// Formatter shortcut
field = {
key: key,
formatter: value
};
} else if (isObject(value)) {
field = clone(value);
field.key = field.key || key;
} else if (value !== false) {
// Fallback to just key
/* istanbul ignore next */
field = {
key: key
};
}
return field;
}; // We normalize fields into an array of objects
// [ { key:..., label:..., ...}, {...}, ..., {..}]
var normalizeFields = function normalizeFields(origFields, items) {
var fields = [];
if (isArray(origFields)) {
// Normalize array Form
origFields.filter(function (f) {
return f;
}).forEach(function (f) {
if (isString(f)) {
fields.push({
key: f,
label: startCase(f)
});
} else if (isObject(f) && f.key && isString(f.key)) {
// Full object definition. We use assign so that we don't mutate the original
fields.push(clone(f));
} else if (isObject(f) && keys(f).length === 1) {
// Shortcut object (i.e. { 'foo_bar': 'This is Foo Bar' }
var key = keys(f)[0];
var field = processField(key, f[key]);
if (field) {
fields.push(field);
}
}
});
} // If no field provided, take a sample from first record (if exits)
if (fields.length === 0 && isArray(items) && items.length > 0) {
var sample = items[0];
keys(sample).forEach(function (k) {
if (!IGNORED_FIELD_KEYS[k]) {
fields.push({
key: k,
label: startCase(k)
});
}
});
} // Ensure we have a unique array of fields and that they have String labels
var memo = {};
return fields.filter(function (f) {
if (!memo[f.key]) {
memo[f.key] = true;
f.label = isString(f.label) ? f.label : startCase(f.key);
return true;
}
return false;
});
};
export default normalizeFields;
@@ -0,0 +1,26 @@
import { keys } from '../../../utils/object';
import { arrayIncludes } from '../../../utils/array';
import { isFunction } from '../../../utils/inspect';
import { IGNORED_FIELD_KEYS } from './constants'; // Return a copy of a row after all reserved fields have been filtered out
var sanitizeRow = function sanitizeRow(row, ignoreFields, includeFields) {
var fieldsObj = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
return keys(row).reduce(function (obj, key) {
// Ignore special fields that start with `_`
// Ignore fields in the `ignoreFields` array
// Include only fields in the `includeFields` array
if (!IGNORED_FIELD_KEYS[key] && !(ignoreFields && ignoreFields.length > 0 && arrayIncludes(ignoreFields, key)) && !(includeFields && includeFields.length > 0 && !arrayIncludes(includeFields, key))) {
var f = fieldsObj[key] || {};
var val = row[key]; // `f.filterByFormatted` will either be a function or boolean
// `f.formater` will have already been noramlized into a function ref
var filterByFormatted = f.filterByFormatted;
var formatter = isFunction(filterByFormatted) ? filterByFormatted : filterByFormatted ? f.formatter : null;
obj[key] = isFunction(formatter) ? formatter(val, key, row) : val;
}
return obj;
}, {});
};
export default sanitizeRow;
@@ -0,0 +1,35 @@
import { keys } from '../../../utils/object';
import { isDate, isObject, isUndefinedOrNull } from '../../../utils/inspect'; // Recursively stringifies the values of an object, space separated, in an
// SSR safe deterministic way (keys are sorted before stringification)
//
// ex:
// { b: 3, c: { z: 'zzz', d: null, e: 2 }, d: [10, 12, 11], a: 'one' }
// becomes
// 'one 3 2 zzz 10 12 11'
//
// Primitives (numbers/strings) are returned as-is
// Null and undefined values are filtered out
// Dates are converted to their native string format
var stringifyObjectValues = function stringifyObjectValues(val) {
if (isUndefinedOrNull(val)) {
/* istanbul ignore next */
return '';
} // Arrays are also object, and keys just returns the array indexes
// Date objects we convert to strings
if (isObject(val) && !isDate(val)) {
return keys(val).sort() // Sort to prevent SSR issues on pre-rendered sorted tables
.filter(function (v) {
return !isUndefinedOrNull(v);
}) // Ignore undefined/null values
.map(function (k) {
return stringifyObjectValues(val[k]);
}).join(' ');
}
return String(val);
};
export default stringifyObjectValues;
@@ -0,0 +1,10 @@
import { isObject } from '../../../utils/inspect';
import sanitizeRow from './sanitize-row';
import stringifyObjectValues from './stringify-object-values'; // Stringifies the values of a record, ignoring any special top level field keys
// TODO: Add option to stringify `scopedSlot` items
var stringifyRecordValues = function stringifyRecordValues(row, ignoreFields, includeFields, fieldsObj) {
return isObject(row) ? stringifyObjectValues(sanitizeRow(row, ignoreFields, includeFields, fieldsObj)) : '';
};
export default stringifyRecordValues;
@@ -0,0 +1,13 @@
import { getSel, isElement } from '../../../utils/dom'; // Helper to determine if a there is an active text selection on the document page
// Used to filter out click events caused by the mouse up at end of selection
//
// Accepts an element as only argument to test to see if selection overlaps or is
// contained within the element
var textSelectionActive = function textSelectionActive() {
var el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
var sel = getSel();
return sel && sel.toString().trim() !== '' && sel.containsNode && isElement(el) ? sel.containsNode(el, true) : false;
};
export default textSelectionActive;
+236
View File
@@ -0,0 +1,236 @@
//
// Table
//
import Vue, { VNode } from 'vue'
import { BvPlugin, BvComponent } from '../../'
// Table Plugins
export declare const TablePlugin: BvPlugin
export declare const TableLitePlugin: BvPlugin
export declare const TableSimplePlugin: BvPlugin
// Component: b-table
export declare class BTable extends BvComponent {
// Public methods
refresh: () => void
clearSelected: () => void
selectAllRows: () => void
isRowSelected: (index: number) => boolean
selectRow: (index: number) => void
unselectRow: (index: number) => void
// Props
id?: string
items: Array<any> | BvTableProviderCallback
fields?: BvTableFieldArray
primaryKey?: string
sortBy?: string | null
sortDesc?: boolean
sortDirection?: BvTableSortDirection
sortCompare?: BvTableSortCompareCallback
sortCompareLocale?: string | Array<string>
sortCompareOptions?: BvTableLocaleCompareOptions
perPage?: number | string
currentPage?: number | string
filter?: string | Array<any> | RegExp | object | any
filterFunction?: BvTableFilterCallback
filterIgnoredFields?: Array<string>
filterIncludedFields?: Array<string>
busy?: boolean
tbodyTrClass?: string | Array<any> | object | BvTableTbodyTrClassCallback
tabelVariant?: BvTableVariant | string
headVariant?: BvTableHeadFootVariant | string
footVariant?: BvTableHeadFootVariant | string
tbodyTransitionProps?: BvTableTbodyTransitionProps
tbodyTransitionHandlers?: BvTableTbodyTransitionHandlers
responsive?: boolean | string
stacked?: boolean | string
stickyHeader?: boolean
}
// Component: b-table-lite
export declare class BTableLite extends BvComponent {
// Props
id?: string
items: Array<any> | BvTableProviderCallback
fields?: BvTableFieldArray
primaryKey?: string
tbodyTrClass?: string | Array<any> | object | BvTableTbodyTrClassCallback
tableClass?: string
tableVariant?: BvTableVariant | string
headVariant?: BvTableHeadFootVariant | string
footVariant?: BvTableHeadFootVariant | string
tbodyTransitionProps?: BvTableTbodyTransitionProps
tbodyTransitionHandlers?: BvTableTbodyTransitionHandlers
responsive?: boolean | string
stacked?: boolean | string
stickyHeader?: boolean
}
// Component: b-table-simple
export declare class BTableSimple extends BvComponent {
// Props
id?: string
tableClass?: string
tabelVariant?: BvTableVariant | string
responsive?: boolean | string
stacked?: boolean | string
stickyHeader?: boolean
}
// Component: b-tbody
export declare class BTbody extends BvComponent {
tbodyTransitionProps?: BvTableTbodyTransitionProps
tbodyTransitionHandlers?: BvTableTbodyTransitionHandlers
}
// Component: b-thead
export declare class BThead extends BvComponent {
headVariant?: BvTableHeadFootVariant | string
}
// Component: b-tfoot
export declare class BTfoot extends BvComponent {
footVariant?: BvTableHeadFootVariant | string
}
// Component: b-tr
export declare class BTr extends BvComponent {
variant?: BvTableVariant | string
}
// Component: b-th
export declare class BTh extends BvComponent {
variant?: BvTableVariant | string
colspan?: number | string
rowspan?: number | string
stackedHeading?: number | string
stickyColumn?: boolean
}
// Component: b-td
export declare class BTd extends BvComponent {
variant?: BvTableVariant | string
colspan?: number | string
rowspan?: number | string
stackedHeading?: number | string
stickyColumn?: boolean
}
export type BvTableVariant =
| 'active'
| 'success'
| 'info'
| 'warning'
| 'danger'
| 'primary'
| 'secondary'
| 'light'
| 'dark'
export type BvTableHeadFootVariant = 'light' | 'dark'
export type BvTableSortDirection = 'asc' | 'desc' | 'last'
export type BvTableFormatterCallback = ((value: any, key: string, item: any) => any)
export type BvTableTbodyTrClassCallback = ((item: any, type: string) => any)
export type BvTableFilterCallback = ((item: any, filter: any) => boolean)
export type BvTableLocaleCompareOptionLocaleMatcher = 'lookup' | 'best fit'
export type BvTableLocaleCompareOptionSensitivity = 'base' | 'accent' | 'case' | 'variant'
export type BvTableLocaleCompareOptionCaseFirst = 'upper' | 'lower' | 'false'
export type BvTableLocaleCompareOptionUsage = 'sort'
export interface BvTableTbodyTransitionProps {
name?: string
appear?: boolean
css?: boolean
type?: string
duration?: number
moveClass?: string
enterClass?: string
leaveClass?: string
appearClass?: string
enterToClass?: string
leaveToClass?: string
appearToClass?: string
enterActiveClass?: string
leaveActiveClass?: string
appearActiveClass?: string
}
export interface BvTableTbodyTransitionHandlers {
beforeEnter?: (el: any) => void
beforeLeave?: (el: any) => void
beforeAppear?: (el: any) => void
enter?: (el: any, done: () => void) => void
leave?: (el: any, done: () => void) => void
appear?: (el: any, done: () => void) => void
afterEnter?: (el: any) => void
afterLeave?: (el: any) => void
afterAppear?: (el: any) => void
enterCancelled?: (el: any) => void
leaveCancelled?: (el: any) => void
appearCancelled?: (el: any) => void
}
export interface BvTableLocaleCompareOptions {
ignorePunctuation?: boolean
numeric?: boolean
localeMatcher?: BvTableLocaleCompareOptionLocaleMatcher
sensitivity?: BvTableLocaleCompareOptionSensitivity
caseFirst?: BvTableLocaleCompareOptionCaseFirst
usage?: BvTableLocaleCompareOptionUsage
}
export type BvTableSortCompareCallback = ((
a: any,
b: any,
field: string,
sortDesc?: boolean,
formatter?: BvTableFormatterCallback | undefined | null,
localeOptions?: BvTableLocaleCompareOptions,
locale?: string | Array<string> | undefined | null
) => number | boolean | null | undefined)
export interface BvTableCtxObject {
currentPage: number
perPage: number
filter: string | RegExp | BvTableFilterCallback | null
sortBy: string | null
sortDesc: boolean
apiUrl: string | null
[key: string]: any
}
export type BvTableProviderPromiseResult = Array<any> | null
export interface BvTableProviderCallback {
(ctx: BvTableCtxObject): Array<any> | Promise<BvTableProviderPromiseResult> | any
(ctx: BvTableCtxObject, callback: () => Array<any>): null
}
export interface BvTableField {
label?: string
headerTitle?: string
headerAbbr?: string
class?: string | string[]
formatter?: string | BvTableFormatterCallback
sortable?: boolean
sortDirection?: BvTableSortDirection
sortByFormatted?: boolean | BvTableFormatterCallback
filterByFormatted?: boolean | BvTableFormatterCallback
tdClass?: string | string[] | ((value: any, key: string, item: any) => any)
thClass?: string | string[]
thStyle?: any
variant?: BvTableVariant | string
tdAttr?: any | ((value: any, key: string, item: any) => any)
thAttr?: any | ((value: any, key: string, item: any, type: string) => any)
isRowHeader?: boolean
}
export type BvTableFieldArray = Array<string | ({ key: string } & BvTableField)>
+45
View File
@@ -0,0 +1,45 @@
import { BTable } from './table';
import { BTableLite } from './table-lite';
import { BTableSimple } from './table-simple';
import { BTbody } from './tbody';
import { BThead } from './thead';
import { BTfoot } from './tfoot';
import { BTr } from './tr';
import { BTd } from './td';
import { BTh } from './th';
import { pluginFactory } from '../../utils/plugins';
var TableLitePlugin =
/*#__PURE__*/
pluginFactory({
components: {
BTableLite: BTableLite
}
});
var TableSimplePlugin =
/*#__PURE__*/
pluginFactory({
components: {
BTableSimple: BTableSimple,
BTbody: BTbody,
BThead: BThead,
BTfoot: BTfoot,
BTr: BTr,
BTd: BTd,
BTh: BTh
}
});
var TablePlugin =
/*#__PURE__*/
pluginFactory({
components: {
BTable: BTable
},
plugins: {
TableLitePlugin: TableLitePlugin,
TableSimplePlugin: TableSimplePlugin
}
});
export { // Plugins
TablePlugin, TableLitePlugin, TableSimplePlugin // Table components
, BTable, BTableLite, BTableSimple // Helper components
, BTbody, BThead, BTfoot, BTr, BTd, BTh };
+28
View File
@@ -0,0 +1,28 @@
import Vue from '../../utils/vue'; // Mixins
import idMixin from '../../mixins/id';
import normalizeSlotMixin from '../../mixins/normalize-slot'; // Table helper Mixins
import itemsMixin from './helpers/mixin-items';
import captionMixin from './helpers/mixin-caption';
import colgroupMixin from './helpers/mixin-colgroup';
import stackedMixin from './helpers/mixin-stacked';
import theadMixin from './helpers/mixin-thead';
import tfootMixin from './helpers/mixin-tfoot';
import tbodyMixin from './helpers/mixin-tbody'; // Main table renderer mixin
import tableRendererMixin from './helpers/mixin-table-renderer'; // b-table-lite component definition
// @vue/component
export var BTableLite =
/*#__PURE__*/
Vue.extend({
name: 'BTableLite',
// Order of mixins is important!
// They are merged from first to last, followed by this component.
mixins: [// Required mixins
idMixin, normalizeSlotMixin, itemsMixin, tableRendererMixin, stackedMixin, theadMixin, tfootMixin, tbodyMixin, // Features Mixins
// These are pretty lightweight, and are useful for lightweight tables
captionMixin, colgroupMixin] // render function provided by table-renderer mixin
});
+28
View File
@@ -0,0 +1,28 @@
import Vue from '../../utils/vue'; // Mixins
import idMixin from '../../mixins/id';
import normalizeSlotMixin from '../../mixins/normalize-slot'; // Main table renderer mixin
import tableRendererMixin from './helpers/mixin-table-renderer'; // Feature miins
import stackedMixin from './helpers/mixin-stacked'; // b-table-simple component definition
// @vue/component
export var BTableSimple =
/*#__PURE__*/
Vue.extend({
name: 'BTableSimple',
// Order of mixins is important!
// They are merged from first to last, followed by this component.
mixins: [// Required mixins
idMixin, normalizeSlotMixin, tableRendererMixin, // feature mixin
// Stacked requires extra handling by users via
// the table cell `stacked-heading` prop
stackedMixin],
computed: {
isTableSimple: function isTableSimple() {
return true;
}
} // render function provided by table-renderer mixin
});
+36
View File
@@ -0,0 +1,36 @@
import Vue from '../../utils/vue'; // Mixins
import idMixin from '../../mixins/id';
import normalizeSlotMixin from '../../mixins/normalize-slot'; // Table helper Mixins
import itemsMixin from './helpers/mixin-items';
import stackedMixin from './helpers/mixin-stacked';
import filteringMixin from './helpers/mixin-filtering';
import sortingMixin from './helpers/mixin-sorting';
import paginationMixin from './helpers/mixin-pagination';
import captionMixin from './helpers/mixin-caption';
import colgroupMixin from './helpers/mixin-colgroup';
import theadMixin from './helpers/mixin-thead';
import tfootMixin from './helpers/mixin-tfoot';
import tbodyMixin from './helpers/mixin-tbody';
import emptyMixin from './helpers/mixin-empty';
import topRowMixin from './helpers/mixin-top-row';
import bottomRowMixin from './helpers/mixin-bottom-row';
import busyMixin from './helpers/mixin-busy';
import selectableMixin from './helpers/mixin-selectable';
import providerMixin from './helpers/mixin-provider'; // Main table renderer mixin
import tableRendererMixin from './helpers/mixin-table-renderer'; // b-table component definition
// @vue/component
export var BTable =
/*#__PURE__*/
Vue.extend({
name: 'BTable',
// Order of mixins is important!
// They are merged from first to last, followed by this component.
mixins: [// Required Mixins
idMixin, normalizeSlotMixin, itemsMixin, tableRendererMixin, stackedMixin, theadMixin, tfootMixin, tbodyMixin, // Features Mixins
stackedMixin, filteringMixin, sortingMixin, paginationMixin, captionMixin, colgroupMixin, selectableMixin, emptyMixin, topRowMixin, bottomRowMixin, busyMixin, providerMixin] // render function provided by table-renderer mixin
});
+108
View File
@@ -0,0 +1,108 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 normalizeSlotMixin from '../../mixins/normalize-slot';
export var props = {
tbodyTransitionProps: {
type: Object // default: undefined
},
tbodyTransitionHandlers: {
type: Object // default: undefined
}
}; // @vue/component
export var BTbody =
/*#__PURE__*/
Vue.extend({
name: 'BTbody',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
provide: function provide() {
return {
bvTableRowGroup: this
};
},
inject: {
bvTable: {
// Sniffed by <b-tr> / <b-td> / <b-th>
default: function _default()
/* istanbul ignore next */
{
return {};
}
}
},
props: props,
computed: {
isTbody: function isTbody() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return true;
},
isDark: function isDark() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.dark;
},
isStacked: function isStacked() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isStacked;
},
isResponsive: function isResponsive() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isResponsive;
},
isStickyHeader: function isStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Sticky headers are only supported in thead
return false;
},
hasStickyHeader: function hasStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
return !this.isStacked && this.bvTable.stickyHeader;
},
tableVariant: function tableVariant()
/* istanbul ignore next: Not currently sniffed in tests */
{
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.tableVariant;
},
isTransitionGroup: function isTransitionGroup() {
return this.tbodyTransitionProps || this.tbodyTransitionHandlers;
},
tbodyAttrs: function tbodyAttrs() {
return _objectSpread({
role: 'rowgroup'
}, this.$attrs);
},
tbodyProps: function tbodyProps() {
return this.tbodyTransitionProps ? _objectSpread({}, this.tbodyTransitionProps, {
tag: 'tbody'
}) : {};
}
},
render: function render(h) {
var data = {
props: this.tbodyProps,
attrs: this.tbodyAttrs
};
if (this.isTransitionGroup) {
// We use native listeners if a transition group
// for any delegated events
data.on = this.tbodyTransitionHandlers || {};
data.nativeOn = this.$listeners || {};
} else {
// Otherwise we place any listeners on the tbody element
data.on = this.$listeners || {};
}
return h(this.isTransitionGroup ? 'transition-group' : 'tbody', data, this.normalizeSlot('default', {}));
}
});
+188
View File
@@ -0,0 +1,188 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 toString from '../../utils/to-string';
import { isUndefinedOrNull } from '../../utils/inspect';
import normalizeSlotMixin from '../../mixins/normalize-slot';
var digitsRx = /^\d+$/; // Parse a rowspan or colspan into a digit (or null if < 1 or NaN)
var parseSpan = function parseSpan(val) {
val = parseInt(val, 10);
return digitsRx.test(String(val)) && val > 0 ? val : null;
};
/* istanbul ignore next */
var spanValidator = function spanValidator(val) {
return isUndefinedOrNull(val) || parseSpan(val) > 0;
};
export var props = {
variant: {
type: String,
default: null
},
colspan: {
type: [Number, String],
default: null,
validator: spanValidator
},
rowspan: {
type: [Number, String],
default: null,
validator: spanValidator
},
stackedHeading: {
type: String,
default: null
},
stickyColumn: {
type: Boolean,
default: false
}
}; // @vue/component
export var BTd =
/*#__PURE__*/
Vue.extend({
name: 'BTableCell',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
inject: {
bvTableTr: {
default: function _default()
/* istanbul ignore next */
{
return {};
}
}
},
props: props,
computed: {
tag: function tag() {
// Overridden by <b-th>
return 'td';
},
inTbody: function inTbody() {
return this.bvTableTr.inTbody;
},
inThead: function inThead() {
return this.bvTableTr.inThead;
},
inTfoot: function inTfoot() {
return this.bvTableTr.inTfoot;
},
isDark: function isDark() {
return this.bvTableTr.isDark;
},
isStacked: function isStacked() {
return this.bvTableTr.isStacked;
},
isStackedCell: function isStackedCell() {
// We only support stacked-heading in tbody in stacked mode
return this.inTbody && this.isStacked;
},
isResponsive: function isResponsive() {
return this.bvTableTr.isResponsive;
},
isStickyHeader: function isStickyHeader() {
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
// Sticky headers only apply to cells in table `thead`
return this.bvTableTr.isStickyHeader;
},
hasStickyHeader: function hasStickyHeader() {
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
return this.bvTableTr.hasStickyHeader;
},
isStickyColumn: function isStickyColumn() {
// Needed to handle background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
// Sticky column cells are only available in responsive
// mode (horizontal scrolling) or when sticky header mode
// Applies to cells in `thead`, `tbody` and `tfoot`
return !this.isStacked && (this.isResponsive || this.hasStickyHeader) && this.stickyColumn;
},
rowVariant: function rowVariant() {
return this.bvTableTr.variant;
},
headVariant: function headVariant() {
return this.bvTableTr.headVariant;
},
footVariant: function footVariant()
/* istanbul ignore next: need to add in tests for footer variant */
{
return this.bvTableTr.footVariant;
},
tableVariant: function tableVariant() {
return this.bvTableTr.tableVariant;
},
computedColspan: function computedColspan() {
return parseSpan(this.colspan);
},
computedRowspan: function computedRowspan() {
return parseSpan(this.rowspan);
},
cellClasses: function cellClasses() {
// We use computed props here for improved performance by caching
// the results of the string interpolation
// TODO: need to add handling for footVariant
var variant = this.variant;
if (!variant && this.isStickyHeader && !this.headVariant || !variant && this.isStickyColumn) {
// Needed for sticky-header mode as Bootstrap v4 table cells do
// not inherit parent's background-color. Boo!
variant = this.rowVariant || this.tableVariant || 'b-table-default';
}
return [variant ? "".concat(this.isDark ? 'bg' : 'table', "-").concat(variant) : null, this.isStickyColumn ? 'b-table-sticky-column' : null];
},
cellAttrs: function cellAttrs() {
// We use computed props here for improved performance by caching
// the results of the object spread (Object.assign)
var headOrFoot = this.inThead || this.inTfoot; // Make sure col/rowspan's are > 0 or null
var colspan = this.computedColspan;
var rowspan = this.computedRowspan; // Default role and scope
var role = 'cell';
var scope = null; // Compute role and scope
// We only add scopes with an explicit span of 1 or greater
if (headOrFoot) {
// Header or footer cells
role = 'columnheader';
scope = colspan > 0 ? 'colspan' : 'col';
} else if (this.tag === 'th') {
// th's in tbody
role = 'rowheader';
scope = rowspan > 0 ? 'rowgroup' : 'row';
}
return _objectSpread({
colspan: colspan,
rowspan: rowspan,
role: role,
scope: scope
}, this.$attrs, {
// Add in the stacked cell label data-attribute if in
// stacked mode (if a stacked heading label is provided)
'data-label': this.isStackedCell && !isUndefinedOrNull(this.stackedHeading) ? toString(this.stackedHeading) : null
});
}
},
render: function render(h) {
var content = [this.normalizeSlot('default')];
return h(this.tag, {
class: this.cellClasses,
attrs: this.cellAttrs,
// Transfer any native listeners
on: this.$listeners
}, [this.isStackedCell ? h('div', [content]) : content]);
}
});
+92
View File
@@ -0,0 +1,92 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 normalizeSlotMixin from '../../mixins/normalize-slot';
export var props = {
footVariant: {
type: String,
// supported values: 'lite', 'dark', or null
default: null
}
}; // @vue/component
export var BTfoot =
/*#__PURE__*/
Vue.extend({
name: 'BTfoot',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
provide: function provide() {
return {
bvTableRowGroup: this
};
},
inject: {
bvTable: {
// Sniffed by <b-tr> / <b-td> / <b-th>
default: function _default()
/* istanbul ignore next */
{
return {};
}
}
},
props: props,
computed: {
isTfoot: function isTfoot() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return true;
},
isDark: function isDark()
/* istanbul ignore next: Not currently sniffed in tests */
{
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.dark;
},
isStacked: function isStacked() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isStacked;
},
isResponsive: function isResponsive() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isResponsive;
},
isStickyHeader: function isStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Sticky headers are only supported in thead
return false;
},
hasStickyHeader: function hasStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
return !this.isStacked && this.bvTable.stickyHeader;
},
tableVariant: function tableVariant()
/* istanbul ignore next: Not currently sniffed in tests */
{
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.tableVariant;
},
tfootClasses: function tfootClasses() {
return [this.footVariant ? "thead-".concat(this.footVariant) : null];
},
tfootAttrs: function tfootAttrs() {
return _objectSpread({
role: 'rowgroup'
}, this.$attrs);
}
},
render: function render(h) {
return h('tfoot', {
class: this.tfootClasses,
attrs: this.tfootAttrs,
// Pass down any native listeners
on: this.$listeners
}, this.normalizeSlot('default', {}));
}
});
+14
View File
@@ -0,0 +1,14 @@
import Vue from '../../utils/vue';
import { BTd } from './td'; // @vue/component
export var BTh =
/*#__PURE__*/
Vue.extend({
name: 'BTh',
extends: BTd,
computed: {
tag: function tag() {
return 'th';
}
}
});
+91
View File
@@ -0,0 +1,91 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 normalizeSlotMixin from '../../mixins/normalize-slot';
export var props = {
headVariant: {
// Also sniffed by <b-tr> / <b-td> / <b-th>
type: String,
// supported values: 'lite', 'dark', or null
default: null
}
}; // @vue/component
export var BThead =
/*#__PURE__*/
Vue.extend({
name: 'BThead',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
provide: function provide() {
return {
bvTableRowGroup: this
};
},
inject: {
bvTable: {
// Sniffed by <b-tr> / <b-td> / <b-th>
default: function _default()
/* istanbul ignore next */
{
return {};
}
}
},
props: props,
computed: {
isThead: function isThead() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return true;
},
isDark: function isDark() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.dark;
},
isStacked: function isStacked() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isStacked;
},
isResponsive: function isResponsive() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.isResponsive;
},
isStickyHeader: function isStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
// Sticky headers only apply to cells in table `thead`
return !this.isStacked && this.bvTable.stickyHeader;
},
hasStickyHeader: function hasStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
return !this.isStacked && this.bvTable.stickyHeader;
},
tableVariant: function tableVariant() {
// Sniffed by <b-tr> / <b-td> / <b-th>
return this.bvTable.tableVariant;
},
theadClasses: function theadClasses() {
return [this.headVariant ? "thead-".concat(this.headVariant) : null];
},
theadAttrs: function theadAttrs() {
return _objectSpread({
role: 'rowgroup'
}, this.$attrs);
}
},
render: function render(h) {
return h('thead', {
class: this.theadClasses,
attrs: this.theadAttrs,
// Pass down any native listeners
on: this.$listeners
}, this.normalizeSlot('default', {}));
}
});
+107
View File
@@ -0,0 +1,107 @@
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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 normalizeSlotMixin from '../../mixins/normalize-slot';
export var props = {
variant: {
type: String,
default: null
}
};
var LIGHT = 'light';
var DARK = 'dark'; // @vue/component
export var BTr =
/*#__PURE__*/
Vue.extend({
name: 'BTr',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
provide: function provide() {
return {
bvTableTr: this
};
},
inject: {
bvTableRowGroup: {
defaut: function defaut()
/* istanbul ignore next */
{
return {};
}
}
},
props: props,
computed: {
inTbody: function inTbody() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isTbody;
},
inThead: function inThead() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isThead;
},
inTfoot: function inTfoot() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isTfoot;
},
isDark: function isDark() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isDark;
},
isStacked: function isStacked() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isStacked;
},
isResponsive: function isResponsive() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.isResponsive;
},
isStickyHeader: function isStickyHeader() {
// Sniffed by <b-td> / <b-th>
// Sticky headers are only supported in thead
return this.bvTableRowGroup.isStickyHeader;
},
hasStickyHeader: function hasStickyHeader() {
// Sniffed by <b-tr> / <b-td> / <b-th>
// Needed to handle header background classes, due to lack of
// background color inheritance with Bootstrap v4 table CSS
return !this.isStacked && this.bvTableRowGroup.hasStickyHeader;
},
tableVariant: function tableVariant() {
// Sniffed by <b-td> / <b-th>
return this.bvTableRowGroup.tableVariant;
},
headVariant: function headVariant() {
// Sniffed by <b-td> / <b-th>
return this.inThead ? this.bvTableRowGroup.headVariant : null;
},
footVariant: function footVariant() {
// Sniffed by <b-td> / <b-th>
return this.inTfoot ? this.bvTableRowGroup.footVariant : null;
},
isRowDark: function isRowDark() {
return this.headVariant === LIGHT || this.footVariant === LIGHT ? false : this.headVariant === DARK || this.footVariant === DARK ? true : this.isDark;
},
trClasses: function trClasses() {
return [this.variant ? "".concat(this.isRowDark ? 'bg' : 'table', "-").concat(this.variant) : null];
},
trAttrs: function trAttrs() {
return _objectSpread({
role: 'row'
}, this.$attrs);
}
},
render: function render(h) {
return h('tr', {
class: this.trClasses,
attrs: this.trAttrs,
// Pass native listeners to child
on: this.$listeners
}, this.normalizeSlot('default', {}));
}
});