🌐 AI搜索 & 代理 主页
Skip to content

Commit 7e18c61

Browse files
authored
feat(b-pagination/b-pagination-nav): allow page change to be prevented (closes #5679) (#5755)
* feat(b-pagination/b-pagination-nav): allow page change to be prevented * Update pagination-nav.spec.js * Update pagination.spec.js
1 parent d83a2b1 commit 7e18c61

File tree

9 files changed

+268
-104
lines changed

9 files changed

+268
-104
lines changed

src/components/pagination-nav/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,19 @@ To disable auto active page detection, set the `no-page-detect` prop to `true`.
490490
detected. For larger `number-of-pages`, this check can take some time so you may want to manually
491491
control which page is the active via the `v-model` and the `no-page-detect` prop.
492492

493+
## Preventing a page from being selected
494+
495+
You can listen for the `page-click` event, which provides an option to prevent the page from being
496+
selected. The event is emitted with two arguments:
497+
498+
- `bvEvent`: The `BvEvent` object. Call `bvEvt.preventDefault()` to cancel page selection
499+
- `page`: Page number to select (starting with `1`)
500+
501+
For accessibility reasons, when using the `page-click` event to prevent a page from being selected,
502+
you should provide some means of notification to the user as to why the page is not able to be
503+
selected. It is recommended to use the `disabled` attribute on the `<b-pagination-nav>` component
504+
instead of using the `page-click` event (as `disabled` is more intuitive for screen reader users).
505+
493506
## Accessibility
494507

495508
The `<b-pagination-nav>` component provides many features to support assistive technology users,

src/components/pagination-nav/package.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,21 +154,37 @@
154154
"events": [
155155
{
156156
"event": "input",
157-
"description": "when page changes via user interaction or programmatically",
157+
"description": "Emitted when page changes via user interaction or programmatically",
158158
"args": [
159159
{
160160
"arg": "page",
161-
"description": "Selected page number (starting with 1), or null if no page found"
161+
"description": "Selected page number (starting with `1`), or `null` if no page found"
162162
}
163163
]
164164
},
165165
{
166166
"event": "change",
167-
"description": "when page changes via user interaction",
167+
"description": "Emitted when page changes via user interaction",
168168
"args": [
169169
{
170170
"arg": "page",
171-
"description": "Selected page number (starting with 1)"
171+
"description": "Selected page number (starting with `1`)"
172+
}
173+
]
174+
},
175+
{
176+
"event": "page-click",
177+
"description": "Emitted when a page button was clicked. Cancelable",
178+
"version": "2.17.0",
179+
"args": [
180+
{
181+
"arg": "bvEvt",
182+
"type": "BvEvent",
183+
"description": "The `BvEvent` object. Call `bvEvt.preventDefault()` to cancel page selection"
184+
},
185+
{
186+
"arg": "page",
187+
"description": "Page number to select (starting with `1`)"
172188
}
173189
]
174190
}

src/components/pagination-nav/pagination-nav.js

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Vue from '../../utils/vue'
22
import looseEqual from '../../utils/loose-equal'
3+
import { BvEvent } from '../../utils/bv-event.class'
34
import { getComponentConfig } from '../../utils/config'
45
import { attemptBlur, requestAF } from '../../utils/dom'
56
import { isBrowser } from '../../utils/env'
@@ -129,23 +130,37 @@ export const BPaginationNav = /*#__PURE__*/ Vue.extend({
129130
this.guessCurrentPage()
130131
})
131132
},
132-
onClick(pageNum, evt) {
133+
onClick(evt, pageNumber) {
133134
// Dont do anything if clicking the current active page
134-
if (pageNum === this.currentPage) {
135+
if (pageNumber === this.currentPage) {
135136
return
136137
}
138+
139+
const target = evt.currentTarget || evt.target
140+
141+
// Emit a user-cancelable `page-click` event
142+
const clickEvt = new BvEvent('page-click', {
143+
cancelable: true,
144+
vueTarget: this,
145+
target
146+
})
147+
this.$emit(clickEvt.type, clickEvt, pageNumber)
148+
if (clickEvt.defaultPrevented) {
149+
return
150+
}
151+
152+
// Update the `v-model`
153+
// Done in in requestAF() to allow browser to complete the
154+
// native browser click handling of a link
137155
requestAF(() => {
138-
// Update the v-model
139-
// Done in in requestAF() to allow browser to complete the
140-
// native browser click handling of a link
141-
this.currentPage = pageNum
142-
this.$emit('change', pageNum)
156+
this.currentPage = pageNumber
157+
this.$emit('change', pageNumber)
143158
})
159+
160+
// Emulate native link click page reloading behaviour by blurring the
161+
// paginator and returning focus to the document
162+
// Done in a `nextTick()` to ensure rendering complete
144163
this.$nextTick(() => {
145-
// Emulate native link click page reloading behaviour by blurring the
146-
// paginator and returning focus to the document
147-
// Done in a `nextTick()` to ensure rendering complete
148-
const target = evt.currentTarget || evt.target
149164
attemptBlur(target)
150165
})
151166
},

src/components/pagination-nav/pagination-nav.spec.js

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -407,61 +407,101 @@ describe('pagination-nav', () => {
407407
})
408408

409409
it('clicking buttons updates the v-model', async () => {
410-
const wrapper = mount(BPaginationNav, {
411-
propsData: {
412-
baseUrl: '#', // needed to prevent JSDOM errors
413-
numberOfPages: 3,
414-
value: 1,
415-
limit: 10
410+
const App = {
411+
methods: {
412+
onPageClick(bvEvt, page) {
413+
// Prevent 3rd page from being selected
414+
if (page === 3) {
415+
bvEvt.preventDefault()
416+
}
417+
}
418+
},
419+
render(h) {
420+
return h(BPaginationNav, {
421+
props: {
422+
baseUrl: '#', // Needed to prevent JSDOM errors
423+
numberOfPages: 5,
424+
value: 1,
425+
limit: 10
426+
},
427+
on: { 'page-click': this.onPageClick }
428+
})
416429
}
417-
})
418-
expect(wrapper.element.tagName).toBe('NAV')
430+
}
431+
432+
const wrapper = mount(App)
433+
expect(wrapper).toBeDefined()
419434

420-
expect(wrapper.findAll('li').length).toBe(7)
435+
const paginationNav = wrapper.findComponent(BPaginationNav)
436+
expect(paginationNav).toBeDefined()
437+
expect(paginationNav.element.tagName).toBe('NAV')
421438

422-
expect(wrapper.vm.computedCurrentPage).toBe(1)
423-
expect(wrapper.emitted('input')).not.toBeDefined()
439+
// Grab the page links
440+
const lis = paginationNav.findAll('li')
441+
expect(lis.length).toBe(9)
424442

425-
// Click on current page button (does nothing)
426-
await wrapper
427-
.findAll('li')
443+
expect(paginationNav.vm.computedCurrentPage).toBe(1)
444+
expect(paginationNav.emitted('input')).not.toBeDefined()
445+
expect(paginationNav.emitted('change')).not.toBeDefined()
446+
expect(paginationNav.emitted('page-click')).not.toBeDefined()
447+
448+
// Click on current (1st) page link (does nothing)
449+
await lis
428450
.at(2)
429451
.find('a')
430452
.trigger('click')
431453
await waitRAF()
432-
expect(wrapper.vm.computedCurrentPage).toBe(1)
433-
expect(wrapper.emitted('input')).not.toBeDefined()
454+
expect(paginationNav.vm.computedCurrentPage).toBe(1)
455+
expect(paginationNav.emitted('input')).not.toBeDefined()
456+
expect(paginationNav.emitted('change')).not.toBeDefined()
457+
expect(paginationNav.emitted('page-click')).not.toBeDefined()
434458

435-
// Click on 2nd page button
436-
await wrapper
437-
.findAll('li')
459+
// Click on 2nd page link
460+
await lis
438461
.at(3)
439462
.find('a')
440463
.trigger('click')
441464
await waitRAF()
442-
expect(wrapper.vm.computedCurrentPage).toBe(2)
443-
expect(wrapper.emitted('input')).toBeDefined()
444-
expect(wrapper.emitted('input')[0][0]).toBe(2)
445-
446-
// Click goto last button
447-
await wrapper
448-
.findAll('li')
449-
.at(6)
465+
expect(paginationNav.vm.computedCurrentPage).toBe(2)
466+
expect(paginationNav.emitted('input')).toBeDefined()
467+
expect(paginationNav.emitted('change')).toBeDefined()
468+
expect(paginationNav.emitted('page-click')).toBeDefined()
469+
expect(paginationNav.emitted('input')[0][0]).toBe(2)
470+
expect(paginationNav.emitted('change')[0][0]).toBe(2)
471+
expect(paginationNav.emitted('page-click').length).toBe(1)
472+
473+
// Click goto last page link
474+
await lis
475+
.at(8)
450476
.find('a')
451-
.trigger('keydown.space') // Generates a click event
477+
.trigger('click')
452478
await waitRAF()
453-
expect(wrapper.vm.computedCurrentPage).toBe(3)
454-
expect(wrapper.emitted('input')[1][0]).toBe(3)
479+
expect(paginationNav.vm.computedCurrentPage).toBe(5)
480+
expect(paginationNav.emitted('input')[1][0]).toBe(5)
481+
expect(paginationNav.emitted('change')[1][0]).toBe(5)
482+
expect(paginationNav.emitted('page-click').length).toBe(2)
455483

456-
// Click prev button
457-
await wrapper
458-
.findAll('li')
484+
// Click prev page link
485+
await lis
459486
.at(1)
460487
.find('a')
461488
.trigger('click')
462489
await waitRAF()
463-
expect(wrapper.vm.computedCurrentPage).toBe(2)
464-
expect(wrapper.emitted('input')[2][0]).toBe(2)
490+
expect(paginationNav.vm.computedCurrentPage).toBe(4)
491+
expect(paginationNav.emitted('input')[2][0]).toBe(4)
492+
expect(paginationNav.emitted('change')[2][0]).toBe(4)
493+
expect(paginationNav.emitted('page-click').length).toBe(3)
494+
495+
// Click on 3rd page link (prevented)
496+
await lis
497+
.at(4)
498+
.find('a')
499+
.trigger('click')
500+
await waitRAF()
501+
expect(paginationNav.vm.computedCurrentPage).toBe(4)
502+
expect(paginationNav.emitted('input').length).toBe(3)
503+
expect(paginationNav.emitted('change').length).toBe(3)
504+
expect(paginationNav.emitted('page-click').length).toBe(4)
465505

466506
wrapper.destroy()
467507
})

src/components/pagination/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,19 @@ By default the pagination component is left aligned. Change the alignment to `ce
364364
<!-- b-pagination-alignment.vue -->
365365
```
366366

367+
## Preventing a page from being selected
368+
369+
You can listen for the `page-click` event, which provides an option to prevent the page from being
370+
selected. The event is emitted with two arguments:
371+
372+
- `bvEvent`: The `BvEvent` object. Call `bvEvt.preventDefault()` to cancel page selection
373+
- `page`: Page number to select (starting with `1`)
374+
375+
For accessibility reasons, when using the `page-click` event to prevent a page from being selected,
376+
you should provide some means of notification to the user as to why the page is not able to be
377+
selected. It is recommended to use the `disabled` attribute on the `<b-pagination>` component
378+
instead of using the `page-click` event (as `disabled` is more intuitive for screen reader users).
379+
367380
## Accessibility
368381

369382
The `<b-pagination>` component provides many features to support assistive technology users, such as

src/components/pagination/package.json

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,23 +138,37 @@
138138
"events": [
139139
{
140140
"event": "input",
141-
"description": "when page changes via user interaction or programmatically",
141+
"description": "Emitted when page changes via user interaction or programmatically",
142142
"args": [
143143
{
144144
"arg": "page",
145-
"type": "Number",
146-
"description": "Selected page number (starting with 1)"
145+
"description": "Selected page number (starting with `1`), or `null` if no page found"
147146
}
148147
]
149148
},
150149
{
151150
"event": "change",
152-
"description": "when page changes via user interaction",
151+
"description": "Emitted when page changes via user interaction",
153152
"args": [
154153
{
155154
"arg": "page",
156-
"type": "Number",
157-
"description": "Selected page number (starting with 1)"
155+
"description": "Selected page number (starting with `1`)"
156+
}
157+
]
158+
},
159+
{
160+
"event": "page-click",
161+
"description": "Emitted when a page button was clicked. Cancelable",
162+
"version": "2.17.0",
163+
"args": [
164+
{
165+
"arg": "bvEvt",
166+
"type": "BvEvent",
167+
"description": "The `BvEvent` object. Call `bvEvt.preventDefault()` to cancel page selection"
168+
},
169+
{
170+
"arg": "page",
171+
"description": "Page number to select (starting with `1`)"
158172
}
159173
]
160174
}

src/components/pagination/pagination.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Vue from '../../utils/vue'
2+
import { BvEvent } from '../../utils/bv-event.class'
23
import { getComponentConfig } from '../../utils/config'
34
import { attemptFocus, isVisible } from '../../utils/dom'
45
import { isUndefinedOrNull } from '../../utils/inspect'
@@ -87,8 +88,8 @@ export const BPagination = /*#__PURE__*/ Vue.extend({
8788
this.currentPage = currentPage
8889
} else {
8990
this.$nextTick(() => {
90-
// If this value parses to NaN or a value less than 1
91-
// Trigger an initial emit of 'null' if no page specified
91+
// If this value parses to `NaN` or a value less than `1`
92+
// trigger an initial emit of `null` if no page specified
9293
this.currentPage = 0
9394
})
9495
}
@@ -99,23 +100,34 @@ export const BPagination = /*#__PURE__*/ Vue.extend({
99100
},
100101
methods: {
101102
// These methods are used by the render function
102-
onClick(num, evt) {
103-
// Handle edge cases where number of pages has changed (i.e. if perPage changes)
104-
// This should normally not happen, but just in case.
105-
if (num > this.numberOfPages) {
106-
/* istanbul ignore next */
107-
num = this.numberOfPages
108-
} else if (num < 1) {
109-
/* istanbul ignore next */
110-
num = 1
103+
onClick(evt, pageNumber) {
104+
// Dont do anything if clicking the current active page
105+
if (pageNumber === this.currentPage) {
106+
return
111107
}
112-
// Update the v-model
113-
this.currentPage = num
108+
109+
const { target } = evt
110+
111+
// Emit a user-cancelable `page-click` event
112+
const clickEvt = new BvEvent('page-click', {
113+
cancelable: true,
114+
vueTarget: this,
115+
target
116+
})
117+
this.$emit(clickEvt.type, clickEvt, pageNumber)
118+
if (clickEvt.defaultPrevented) {
119+
return
120+
}
121+
122+
console.log(evt, pageNumber)
123+
124+
// Update the `v-model`
125+
this.currentPage = pageNumber
114126
// Emit event triggered by user interaction
115127
this.$emit('change', this.currentPage)
128+
129+
// Keep the current button focused if possible
116130
this.$nextTick(() => {
117-
// Keep the current button focused if possible
118-
const target = evt.target
119131
if (isVisible(target) && this.$el.contains(target)) {
120132
attemptFocus(target)
121133
} else {

0 commit comments

Comments
 (0)