UNPKG

bootstrap-vue

Version:

BootstrapVue, with over 40 plugins and more than 80 custom components, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.

314 lines (277 loc) 11 kB
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 }; } } };