From e5ec208daab29d2c3fe39e4fe67852ac6f6c96ac Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 15 Dec 2018 15:46:41 -0400 Subject: [PATCH 01/33] fix(pagination mixin): remove computed prop with side effects Converts computed prop that had side effects to a method. --- src/mixins/pagination.js | 70 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/mixins/pagination.js b/src/mixins/pagination.js index 728cbe45a37..dcc3c6283b8 100644 --- a/src/mixins/pagination.js +++ b/src/mixins/pagination.js @@ -116,40 +116,6 @@ export default { return 'justify-content-end' } return '' - }, - pageList () { - // TODO: generatePageList() has side effects. Computed props should not have side effects! - const { startNum, numLinks } = this.generatePageList() - - // Generate list of page numbers - const pages = makePageArray(startNum, numLinks) - - // We limit to a total of 3 page buttons on small screens - // Ellipsis will also be hidden on small screens - if (pages.length > 3) { - const idx = this.currentPage - startNum - if (idx === 0) { - // Keep leftmost 3 buttons visible - for (let i = 3; i < pages.length; i++) { - pages[i].className = 'd-none d-sm-flex' - } - } else if (idx === pages.length - 1) { - // Keep rightmost 3 buttons visible - for (let i = 0; i < pages.length - 3; i++) { - pages[i].className = 'd-none d-sm-flex' - } - } else { - // hide left button(s) - for (let i = 0; i < idx - 1; i++) { - pages[i].className = 'd-none d-sm-flex' - } - // hide right button(s) - for (let i = pages.length - 1; i > idx + 1; i--) { - pages[i].className = 'd-none d-sm-flex' - } - } - } - return pages } }, watch: { @@ -217,6 +183,39 @@ export default { } return { startNum, numLinks } }, + getPageList () { + const { startNum, numLinks } = this.generatePageList() + + // Generate list of page numbers + const pages = makePageArray(startNum, numLinks) + + // We limit to a total of 3 page buttons on small screens + // Ellipsis will also be hidden on small screens + if (pages.length > 3) { + const idx = this.currentPage - startNum + if (idx === 0) { + // Keep leftmost 3 buttons visible + for (let i = 3; i < pages.length; i++) { + pages[i].className = 'd-none d-sm-flex' + } + } else if (idx === pages.length - 1) { + // Keep rightmost 3 buttons visible + for (let i = 0; i < pages.length - 3; i++) { + pages[i].className = 'd-none d-sm-flex' + } + } else { + // hide left button(s) + for (let i = 0; i < idx - 1; i++) { + pages[i].className = 'd-none d-sm-flex' + } + // hide right button(s) + for (let i = pages.length - 1; i > idx + 1; i--) { + pages[i].className = 'd-none d-sm-flex' + } + } + } + return pages + }, isActive (pagenum) { return pagenum === this.currentPage }, @@ -290,6 +289,7 @@ export default { }, render (h) { const buttons = [] + const pageList = this.getPageList() // Factory function for prev/next/first/last buttons const makeEndBtns = (linkTo, ariaLabel, btnText, pageTest) => { @@ -386,7 +386,7 @@ export default { buttons.push(this.showFirstDots ? makeEllipsis() : h(false)) // Individual Page links - this.pageList.forEach(page => { + pageList.forEach(page => { let inner let pageNum = stripScripts(this.makePage(page.number)) if (this.disabled) { From 4ed5bad1640685f207ac2950c91a1063502341da Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 15 Dec 2018 18:50:57 -0400 Subject: [PATCH 02/33] pagination: allow numbers as strings for perPage and totalRows --- src/components/pagination/pagination.js | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/pagination/pagination.js b/src/components/pagination/pagination.js index eaa956d7f1c..21c7bf5a54e 100644 --- a/src/components/pagination/pagination.js +++ b/src/components/pagination/pagination.js @@ -1,14 +1,27 @@ import paginationMixin from '../../mixins/pagination' import { isVisible } from '../../utils/dom' +const DEFAULT_PER_PAGE = 20 +const DEFAULT_TOTAL_ROWS = 20 + +function sanitizePerPage(value) { + const perPage = parseInt(value, 10) || DEFAULT_PER_PAGE + return perPage < 1 ? 1 : perPage +} + +function sanitizeTotalRows(value) { + const totalRows = parseInt(value, 10) || DEFAULT_TOTAL_ROWS + return totalRows < 0 ? 0 : totalRows +} + const props = { perPage: { - type: Number, - default: 20 + type: [Number, String], + default: DEFAULT_PER_PAGE }, totalRows: { - type: Number, - default: 20 + type: [Number, String], + default: DEFAULT_TOTAL_ROWS }, ariaControls: { type: String, @@ -23,7 +36,7 @@ export default { props, computed: { numberOfPages () { - const result = Math.ceil(this.totalRows / this.perPage) + const result = Math.ceil(sanitizeTotalRows(this.totalRows) / sanitizePerPage(this.perPage)) return (result < 1) ? 1 : result } }, @@ -36,7 +49,10 @@ export default { } else if (num < 1) { num = 1 } + // Update the v-model this.currentPage = num + // Emit event triggerd by user interaction + this.$emit('change', this.currentPage) this.$nextTick(() => { // Keep the current button focused if possible const target = evt.target @@ -46,12 +62,12 @@ export default { this.focusCurrent() } }) - this.$emit('change', this.currentPage) }, makePage (pagenum) { return pagenum }, linkProps (pagenum) { + // Always '#' for pagination component return { href: '#' } } } From 1ae21ffdb26cf98ff8c27c558b05c7ad9d957a94 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 15 Dec 2018 19:01:03 -0400 Subject: [PATCH 03/33] lint --- src/components/pagination/pagination.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pagination/pagination.js b/src/components/pagination/pagination.js index 21c7bf5a54e..ac35f532388 100644 --- a/src/components/pagination/pagination.js +++ b/src/components/pagination/pagination.js @@ -4,12 +4,12 @@ import { isVisible } from '../../utils/dom' const DEFAULT_PER_PAGE = 20 const DEFAULT_TOTAL_ROWS = 20 -function sanitizePerPage(value) { +function sanitizePerPage (value) { const perPage = parseInt(value, 10) || DEFAULT_PER_PAGE return perPage < 1 ? 1 : perPage } -function sanitizeTotalRows(value) { +function sanitizeTotalRows (value) { const totalRows = parseInt(value, 10) || DEFAULT_TOTAL_ROWS return totalRows < 0 ? 0 : totalRows } From 459fcaf74520ade2af6edb72a52b01a9e7c09b5c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 15 Dec 2018 19:02:04 -0400 Subject: [PATCH 04/33] pagination-nav: allow number-of-pages to be a string --- .../pagination-nav/pagination-nav.js | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/components/pagination-nav/pagination-nav.js b/src/components/pagination-nav/pagination-nav.js index d59ae215e74..85b7b1543ce 100644 --- a/src/components/pagination-nav/pagination-nav.js +++ b/src/components/pagination-nav/pagination-nav.js @@ -1,4 +1,4 @@ -import { assign } from '../../utils/object' +import warn from '../../utils/warn' import paginationMixin from '../../mixins/pagination' import { pickLinkProps } from '../link/link' @@ -6,33 +6,40 @@ import { pickLinkProps } from '../link/link' const routerProps = pickLinkProps('activeClass', 'exactActiveClass', 'append', 'exact', 'replace', 'target', 'rel') // Props object -const props = assign( +const props = { // pagination-nav specific props - { - numberOfPages: { - type: Number, - default: 1 - }, - baseUrl: { - type: String, - default: '/' - }, - useRouter: { - type: Boolean, - default: false - }, - linkGen: { - type: Function, - default: null - }, - pageGen: { - type: Function, - default: null + numberOfPages: { + type: [Number, String], + default: 1, + validator (value) { + const num = parseInt(value, 10) + if (isNaN(num) || num < 1) { + warn('b-pagination: prop "number-of-pages" must be a number greater than 0') + return false + } + return true } }, + baseUrl: { + type: String, + default: '/' + }, + useRouter: { + type: Boolean, + default: false + }, + linkGen: { + type: Function, + default: null + }, + pageGen: { + type: Function, + default: null + }, // Router specific props - routerProps -) + ...routerProps +} + // Our render function is brought in via the pagination mixin // @vue/component export default { @@ -46,6 +53,7 @@ export default { }, methods: { onClick (pageNum, evt) { + // Update the v-model this.currentPage = pageNum }, makePage (pagenum) { @@ -70,14 +78,15 @@ export default { disabled: this.disabled } if (this.useRouter || typeof link === 'object') { - props = assign(props, { + props = { + ...props, to: link, exact: this.exact, activeClass: this.activeClass, exactActiveClass: this.exactActiveClass, append: this.append, replace: this.replace - }) + } } return props } From da8a1dfe38dc37214a993a6093b68382d55319e4 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 15 Dec 2018 22:53:31 -0400 Subject: [PATCH 05/33] Update pagination.js --- src/mixins/pagination.js | 446 ++++++++++++++++++++++++--------------- 1 file changed, 274 insertions(+), 172 deletions(-) diff --git a/src/mixins/pagination.js b/src/mixins/pagination.js index dcc3c6283b8..87cdadb9e78 100644 --- a/src/mixins/pagination.js +++ b/src/mixins/pagination.js @@ -2,21 +2,55 @@ * Comon props, computed, data, render function, and methods for b-pagination and b-pagination-nav */ +import warn from '../utils/warn' import range from '../utils/range' import KeyCodes from '../utils/key-codes' import stripScripts from '../utils/strip-scripts' import { isVisible, isDisabled, selectAll, getAttr } from '../utils/dom' import bLink from '../components/link/link' +// Threshold of limit size when we start/stop showing ellipsis +const ELLIPSIS_THRESHOLD = 3 + +// Default # of buttons limit +const DEFAULT_LIMIT = 5 + // Make an array of N to N+X function makePageArray (startNum, numPages) { return range(numPages).map(function (value, index) { - return { number: index + startNum, className: null } + return { number: index + startNum, classes: null } }) } -// Threshold of limit size when we start/stop showing ellipsis -const ELLIPSIS_THRESHOLD = 3 +// Sanitize the provided Limit value (converting to a number) +function sanitizeLimit(value) { + const limit = parseInt(value, 10) || 1 + return limit < 1 ? DEFAULT_LIMIT : limit +} + +// Sanitize the provided numberOfPages value (converting to a number) +function sanitizeNumPages(value) { + let num = parseInt(value, 10) || 1 + return num < 1 ? 1 : num +} + +// Sanitize the provided current page number (converting to a number) +function sanitizeCurPage(value, numPages) { + let page = parseInt(value, 10) || 1 + return page > numPages ? numPages : (page < 1 ? 1 : page) +} + +// Links don't normally respond to SPACE, so we add that functionality via this handler +function onSpaceKey(evt) { + if (evt.keyCode === KeyCodes.SPACE) { + evt.preventDefault() // Stop page from scrolling + evt.stopImmediatePropagation() + evt.stopPropagation() + // Trigger the click event on the link + evt.currentTarget.click() + return false + } +} // Props object const props = { @@ -25,12 +59,30 @@ const props = { default: false }, value: { - type: Number, - default: 1 + type: [Number, String], + default: 1, + validator (value) { + const num = parseInt(value, 10) + /* istanbul ignore if */ + if (isNaN(num) || num < 1) { + warn('pagination: v-model value must be a number greater than 0') + return false + } + return true + } }, limit: { - type: Number, - default: 5 + type: [Number, String], + default: DEFAULT_LIMIT, + validator (value) { + const num = parseInt(value, 10) + /* istanbul ignore if */ + if (isNaN(num) || num < 1) { + warn('pagination: prop "limit" must be a number greater than 0') + return false + } + return true + } }, size: { type: String, @@ -100,9 +152,9 @@ export default { props, data () { return { - showFirstDots: false, - showLastDots: false, - currentPage: this.value + currentPage: 1, + localNumPages: 1, + localLimit: DEFAULT_LIMIT } }, computed: { @@ -116,123 +168,134 @@ export default { return 'justify-content-end' } return '' - } - }, - watch: { - currentPage (newPage, oldPage) { - if (newPage !== oldPage) { - this.$emit('input', newPage) - } }, - value (newValue, oldValue) { - if (newValue !== oldValue) { - this.currentPage = newValue + showFirstDots () { + // Should we show the first ellipsis + const limit = this.limit + const numPages = this.localNumPages + let show = false + if (!this.hideEllipsis && numPages > limit) { + if ((numPages - this.currentPage + 2) < limit && limit > ELLIPSIS_THRESHOLD) { + show = true + } else if (limit > ELLIPSIS_THRESHOLD) { + show = true + } } - } - }, - methods: { - generatePageList () { - // Sanity checks - if (this.currentPage > this.numberOfPages) { - this.currentPage = this.numberOfPages - } else if (this.currentPage < 1) { - this.currentPage = 1 + return show + }, + showLastDots () { + // Should we show the last ellipsis + const limit = this.limit + let show = false + if (!this.hideEllipsis && this.localNumPages > limit) { + if (this.currentPage < (limit - 1) && limit > ELLIPSIS_THRESHOLD) { + show = true + } else if (limit > ELLIPSIS_THRESHOLD) { + show = true + } } - // - Hide first ellipsis marker - this.showFirstDots = false - // - Hide last ellipsis marker - this.showLastDots = false - let numLinks = this.limit + return show + }, + pageList () { + // Generates the pageList array + const currentPage = this.currentPage + const numberOfPages = this.localNumPages + const hideEllipsis = this.hideEllipsis + const limit = this.localLimit + let numLinks = limit let startNum = 1 - if (this.numberOfPages <= this.limit) { + + // Determine the starting page link number and number of links to generate + if (numberOfPages <= limit) { // Special Case: Less pages available than the limit of displayed pages - numLinks = this.numberOfPages - } else if ( - this.currentPage < this.limit - 1 && - this.limit > ELLIPSIS_THRESHOLD - ) { + numLinks = numberOfPages + } else if (currentPage < (limit - 1) && limit > ELLIPSIS_THRESHOLD) { // We are near the beginning of the page list - if (!this.hideEllipsis) { - numLinks = this.limit - 1 - this.showLastDots = true + if (!hideEllipsis) { + numLinks = limit - 1 } - } else if ( - this.numberOfPages - this.currentPage + 2 < this.limit && - this.limit > ELLIPSIS_THRESHOLD - ) { + } else if ((numberOfPages - currentPage + 2) < limit && limit > ELLIPSIS_THRESHOLD) { // We are near the end of the list - if (!this.hideEllipsis) { - this.showFirstDots = true - numLinks = this.limit - 1 + if (!hideEllipsis) { + numLinks = limit - 1 } - startNum = this.numberOfPages - numLinks + 1 + startNum = numberOfPages - numLinks + 1 } else { // We are somewhere in the middle of the page list - if (this.limit > ELLIPSIS_THRESHOLD && !this.hideEllipsis) { - this.showFirstDots = true - this.showLastDots = true - numLinks = this.limit - 2 + if (limit > ELLIPSIS_THRESHOLD && !hideEllipsis) { + numLinks = limit - 2 } - startNum = this.currentPage - Math.floor(numLinks / 2) + startNum = currentPage - Math.floor(numLinks / 2) } // Sanity checks if (startNum < 1) { startNum = 1 - } else if (startNum > this.numberOfPages - numLinks) { - startNum = this.numberOfPages - numLinks + 1 + } else if (startNum > (numberOfPages - numLinks)) { + startNum = numberOfPages - numLinks + 1 } - return { startNum, numLinks } - }, - getPageList () { - const { startNum, numLinks } = this.generatePageList() // Generate list of page numbers const pages = makePageArray(startNum, numLinks) - - // We limit to a total of 3 page buttons on small screens - // Ellipsis will also be hidden on small screens + // We limit to a total of 3 page buttons on XS screens + // So add classes to page links to hide them for XS breakpoint + // Note: Ellipsis will also be hidden on XS screens + // TODO: Make this visual limit configurable based on breakpoint(s) if (pages.length > 3) { - const idx = this.currentPage - startNum + const idx = currentPage - startNum if (idx === 0) { - // Keep leftmost 3 buttons visible + // Keep leftmost 3 buttons visible when current page is first page for (let i = 3; i < pages.length; i++) { - pages[i].className = 'd-none d-sm-flex' + pages[i].classes = 'd-none d-sm-flex' } } else if (idx === pages.length - 1) { - // Keep rightmost 3 buttons visible + // Keep rightmost 3 buttons visible when current page is last page for (let i = 0; i < pages.length - 3; i++) { - pages[i].className = 'd-none d-sm-flex' + pages[i].classes = 'd-none d-sm-flex' } } else { - // hide left button(s) + // Hide all except current page, current page - 1 and current page + 1 for (let i = 0; i < idx - 1; i++) { - pages[i].className = 'd-none d-sm-flex' + // hide some left button(s) + pages[i].classes = 'd-none d-sm-flex' } - // hide right button(s) for (let i = pages.length - 1; i > idx + 1; i--) { - pages[i].className = 'd-none d-sm-flex' + // hide some right button(s) + pages[i].classes = 'd-none d-sm-flex' } } } return pages + } + }, + watch: { + value (newValue, oldValue) { + if (newValue !== oldValue) { + this.currentPage = sanitizeCurPage(newValue, this.localNumPages) + } }, - isActive (pagenum) { - return pagenum === this.currentPage - }, - pageItemClasses (page) { - return [ - 'page-item', - this.disabled ? 'disabled' : '', - this.isActive(page.number) ? 'active' : '', - page.className - ] + currentPage (newValue, oldValue) { + if (newValue !== oldValue) { + this.$emit('input', newValue) + } }, - pageLinkClasses (page) { - return [ - 'page-link', - this.disabled ? 'disabled' : '' - ] + numberOfPages (newValue, oldValue) { + if (newValue !== oldValue) { + this.localNumPages = sanitizeNumPages(newValue) + } }, + limit (newValue, oldValue) { + if (newValue !== oldValue) { + this.localLimit = sanitizeLimit(newValue) + } + } + }, + beforeCreate () { + // Set our default values in data, before any watchers are set up + this.localLimit = sanitizeLimit(this.limit) + this.localNumPages = sanitizeNumPages(this.numberOfPages) + this.currentPage = sanitizeCurPage(this.value, this.localNumPages) + }, + methods: { getButtons () { // Return only buttons that are visible return selectAll('a.page-link', this.$el).filter(btn => isVisible(btn)) @@ -289,38 +352,37 @@ export default { }, render (h) { const buttons = [] - const pageList = this.getPageList() + const numberOfPages = this.localNumPages + const disabled = this.disabled + const pages = this.pageData + + // Helper function + const isActivePage = (pageNum) => pageNum === this.currentPage // Factory function for prev/next/first/last buttons - const makeEndBtns = (linkTo, ariaLabel, btnText, pageTest) => { + const makeEndBtn = (linkTo, ariaLabel, btnSlot, btnText, pageTest, key) => { let button - pageTest = pageTest || linkTo // Page # to test against to disable - if (this.disabled || this.isActive(pageTest)) { + const domProps = btnSlot ? {} : { innerHTML: stripScripts(btnText) } + const staticClass = 'page-item' + const attrs = { + role: 'none presentation', + 'aria-hidden': disabled ? 'true' : null + } + if (disabled || isActivePage(pageTest) || linkTo < 1 || linkTo > numberOfPages) { button = h( 'li', - { - class: ['page-item', 'disabled'], - attrs: { role: 'none presentation', 'aria-hidden': 'true' } - }, - [ - h('span', { - class: ['page-link'], - domProps: { innerHTML: stripScripts(btnText) } - }) - ] + { key, attrs, staticClass, class: ['disabled'] }, + [h('span', { staticClass: 'page-link', domProps }, [btnSlot])] ) } else { button = h( 'li', - { - class: ['page-item'], - attrs: { role: 'none presentation' } - }, + { key, attrs, staticClass }, [ h( 'b-link', { - class: ['page-link'], + staticClass: 'page-link', props: this.linkProps(linkTo), attrs: { role: 'menuitem', @@ -329,24 +391,11 @@ export default { 'aria-controls': this.ariaControls || null }, on: { - click: evt => { - this.onClick(linkTo, evt) - }, - keydown: evt => { - // Links don't normally respond to SPACE, so we add that functionality - if (evt.keyCode === KeyCodes.SPACE) { - evt.preventDefault() - this.onClick(linkTo, evt) - } - } + click: evt => { this.onClick(linkTo, evt) }, + keydown: onSpaceKey } }, - [ - h('span', { - attrs: { 'aria-hidden': 'true' }, - domProps: { innerHTML: stripScripts(btnText) } - }) - ] + [h('span', { domProps }, [btnSlot])] ) ] ) @@ -355,19 +404,23 @@ export default { } // Ellipsis factory - const makeEllipsis = () => { + const makeEllipsis = (isLast) => { return h( 'li', { + key: `elipsis-${isLast ? 'last' : 'first'}`, class: ['page-item', 'disabled', 'd-none', 'd-sm-flex'], attrs: { role: 'separator' } }, [ this.$slots['ellipsis-text'] || - h('span', { - class: ['page-link'], - domProps: { innerHTML: stripScripts(this.ellipsisText) } - }) + h( + 'span', + { + class: ['page-link'], + domProps: { innerHTML: stripScripts(this.ellipsisText) } + } + ) ] ) } @@ -376,58 +429,85 @@ export default { buttons.push( this.hideGotoEndButtons ? h(false) - : makeEndBtns(1, this.labelFirstPage, this.$slots['first-text'] || stripScripts(this.firstText)) + : makeEndBtn( + 1, + this.labelFirstPage, + this.$slots['first-text'], + this.firstText, + 1, + 'bookend-goto-first' + ) ) // Goto Previous page button - buttons.push(makeEndBtns(this.currentPage - 1, this.labelPrevPage, this.$slots['prev-text'] || this.prevText, 1)) + buttons.push( + makeEndBtn( + this.currentPage - 1, + this.labelPrevPage, + this.$slots['prev-text'], + this.prevText, + 1, + 'bookend-goto-prev' + ) + ) // First Ellipsis Bookend - buttons.push(this.showFirstDots ? makeEllipsis() : h(false)) + buttons.push(this.showFirstDots ? makeEllipsis(false) : h(false)) // Individual Page links - pageList.forEach(page => { + this.pageList.forEach(page => { let inner - let pageNum = stripScripts(this.makePage(page.number)) - if (this.disabled) { - inner = h('span', { - class: ['page-link'], - domProps: { innerHTML: pageNum } - }) + const domProps = { innerHTML: stripScripts(this.makePage(page.number)) } + const active = isActivePage(page.number) + const staticClass = 'page-link' + const attrs = { + role: 'menuitemradio', + 'aria-disabled': disabled ? 'true' : null, + 'aria-controls': this.ariaControls || null, + 'aria-label': `${this.labelPage} ${page.number}`, + 'aria-checked': active ? 'true' : 'false', + 'aria-posinset': page.number, + 'aria-setsize': numberOfPages, + // ARIA "roving tabindex" method + tabindex: disabled ? null : (active ? '0' : '-1') + } + if (disabled) { + inner = h( + 'span', + { + key: `page-${page.number}-link-disabled`, + staticClass:, + attrs, + domProps + } + ) } else { - const active = this.isActive(page.number) - inner = h('b-link', { - class: this.pageLinkClasses(page), - props: this.linkProps(page.number), - attrs: { - role: 'menuitemradio', - tabindex: active ? '0' : '-1', - 'aria-controls': this.ariaControls || null, - 'aria-label': `${this.labelPage} ${page.number}`, - 'aria-checked': active ? 'true' : 'false', - 'aria-posinset': page.number, - 'aria-setsize': this.numberOfPages - }, - domProps: { innerHTML: pageNum }, - on: { - click: evt => { - this.onClick(page.number, evt) - }, - keydown: evt => { - if (evt.keyCode === KeyCodes.SPACE) { - evt.preventDefault() - this.onClick(page.number, evt) - } + inner = h( + 'b-link', + { + key: `page-${page.number}-link`, + props: this.linkProps(page.number), + staticClass, + attrs, + domProps, + on: { + click: evt => { this.onClick(page.number, evt) }, + keydown: onSpaceKey } } - }) + ) } buttons.push( h( 'li', { - key: page.number, - class: this.pageItemClasses(page), + key: `page-${page.number}`, + staticClass: 'page-item', + class: [ + disabled ? 'disabled' : '', + active ? 'active' : '', + page.classes + ], attrs: { role: 'none presentation' } }, [inner] @@ -436,15 +516,17 @@ export default { }) // Last Ellipsis Bookend - buttons.push(this.showLastDots ? makeEllipsis() : h(false)) + buttons.push(this.showLastDots ? makeEllipsis(true) : h(false)) // Goto Next page button buttons.push( - makeEndBtns( + makeEndBtn( this.currentPage + 1, this.labelNextPage, - this.$slots['next-text'] || this.nextText, - this.numberOfPages + this.$slots['next-text'], + this.nextText, + numberOfPages, + 'bookend-goto-next' ) ) @@ -452,7 +534,14 @@ export default { buttons.push( this.hideGotoEndButtons ? h(false) - : makeEndBtns(this.numberOfPages, this.labelLastPage, this.$slots['last-text'] || this.lastText) + : makeEndBtn( + numberOfPages, + this.labelLastPage, + this.$slots['last-text'], + this.lastText, + numberOfPages, + 'bookend-goto-last' + ) ) // Assemble the paginatiom buttons @@ -463,7 +552,7 @@ export default { class: ['pagination', 'b-pagination', this.btnSize, this.alignment], attrs: { role: 'menubar', - 'aria-disabled': this.disabled ? 'true' : 'false', + 'aria-disabled': disabled ? 'true' : 'false', 'aria-label': this.ariaLabel || null }, on: { @@ -484,6 +573,19 @@ export default { ) // if we are pagination-nav, wrap in '