element-plus
Version:
A Component Library for Vue3.0
322 lines (285 loc) • 9.14 kB
text/typescript
import {
h,
ref,
provide,
computed,
defineComponent,
getCurrentInstance,
} from 'vue'
import { warn } from '@element-plus/utils/error'
import { t } from '@element-plus/locale'
import { IPagination } from './pagination'
import Prev from './prev.vue'
import Next from './next.vue'
import Sizes from './sizes.vue'
import Jumper from './jumper.vue'
import Total from './total.vue'
import Pager from './pager.vue'
/**
* It it user's responsibility to guarantee that the value of props.total... is number
* (same as pageSize, defaultPageSize, currentPage, defaultCurrentPage, pageCount)
* Otherwise we can reasonable infer that the corresponding field is absent
*/
const isAbsent = v => typeof v !== 'number'
const componentName = 'ElPagination'
export default defineComponent({
name: componentName,
components: {
Prev,
Next,
Sizes,
Jumper,
Total,
Pager,
},
props: {
total: {
type: Number,
},
pageSize: {
type: Number,
},
defaultPageSize: {
type: Number,
},
currentPage: {
type: Number,
},
defaultCurrentPage: {
type: Number,
},
pageCount: {
type: Number,
},
pagerCount: {
type: Number,
validator: (value: number) => {
return (
(value | 0) === value && value > 4 && value < 22 && value % 2 === 1
)
},
default: 7,
},
layout: {
type: String,
default: 'prev, pager, next, jumper, ->, total',
},
pageSizes: {
type: Array,
default: () => {
return [10, 20, 30, 40, 50, 100]
},
},
popperClass: {
type: String,
default: '',
},
prevText: {
type: String,
default: '',
},
nextText: {
type: String,
default: '',
},
small: Boolean,
background: Boolean,
disabled: Boolean,
hideOnSinglePage: Boolean,
},
emits: [
'update:current-page',
'update:page-size',
// events below are depracated
// v-model:current-page and v-model:page-size are better choices
'size-change',
'current-change',
'prev-click',
'next-click',
],
setup(props, { emit, slots }) {
const vnodeProps = getCurrentInstance().vnode.props || {}
// we can find @xxx="xxx" props on `vnodeProps` to check if user bind corresponding events
const hasCurrentPageListener = 'onUpdate:currentPage' in vnodeProps || 'onCurrentChange' in vnodeProps
const hasPageSizeListener = 'onUpdate:pageSize' in vnodeProps || 'onSizeChange' in vnodeProps
const assertValidUsage = computed(() => {
// Users have to set either one, otherwise count of pages cannot be determined
if (isAbsent(props.total) && isAbsent(props.pageCount)) return false
// <el-pagination ...otherProps :current-page="xxx" /> without corresponding listener is forbidden now
// Users have to use two way binding of `currentPage`
// If users just want to provide a default value, `defaultCurrentPage` is here for you
if (!isAbsent(props.currentPage) && !hasCurrentPageListener) return false
// When you want to change sizes, things get more complex, detailed below
// Basically the most important value we need is page count
// either directly from props.pageCount
// or calculated from props.total
// we will take props.pageCount precedence over props.total
if (props.layout.includes('sizes')) {
if (!isAbsent(props.pageCount)) {
// if props.pageCount is assign by user, then user have to watch pageSize change
// and recalculate pageCount
if (!hasPageSizeListener) return false
} else if (!isAbsent(props.total)) {
// Otherwise, we will see if user have props.pageSize defined
// If so, meaning user want to have pageSize controlled himself/herself from component
// Thus page size listener is required
// users are account for page size change
if (!isAbsent(props.pageSize)) {
if (!hasPageSizeListener) {
return false
}
} else {
// (else block just for explaination)
// else page size is controlled by el-pagination internally
}
}
}
return true
})
const innerPageSize = ref(isAbsent(props.defaultPageSize) ? 10 : props.defaultPageSize)
const innerCurrentPage = ref(isAbsent(props.defaultCurrentPage) ? 1 : props.defaultCurrentPage)
const pageSizeBridge = computed({
get() {
return isAbsent(props.pageSize) ? innerPageSize.value : props.pageSize
},
set(v: number) {
if (isAbsent(props.pageSize)) {
innerPageSize.value = v
}
if (hasPageSizeListener) {
emit('update:page-size', v)
emit('size-change', v)
}
},
})
const pageCountBridge = computed<number>(() => {
let pageCount = 0
if (!isAbsent(props.pageCount)) {
pageCount = props.pageCount
} else if (!isAbsent(props.total)) {
pageCount = Math.max(1, Math.ceil(props.total / pageSizeBridge.value))
}
// side effect
if (currentPageBridge.value > pageCount) {
currentPageBridge.value = pageCount
}
return pageCount
})
const currentPageBridge = computed<number>({
get() {
return isAbsent(props.currentPage) ? innerCurrentPage.value : props.currentPage
},
set(v) {
let newCurrentPage = v
if (v < 1) {
newCurrentPage = 1
} else if (v > pageCountBridge.value) {
newCurrentPage = pageCountBridge.value
}
if (isAbsent(props.currentPage)) {
innerCurrentPage.value = newCurrentPage
}
if (hasCurrentPageListener) {
emit('update:current-page', newCurrentPage)
emit('current-change', newCurrentPage)
}
},
})
function handleCurrentChange(val: number) {
currentPageBridge.value = val
}
function handleSizeChange(val: number) {
pageSizeBridge.value = val
const newPageCount = pageCountBridge.value
if (currentPageBridge.value > newPageCount) {
currentPageBridge.value = newPageCount
}
}
function prev() {
if (props.disabled) return
currentPageBridge.value -= 1
emit('prev-click', currentPageBridge.value)
}
function next() {
if (props.disabled) return
currentPageBridge.value += 1
emit('next-click', currentPageBridge.value)
}
provide<IPagination>('pagination', {
pageCount: pageCountBridge,
disabled: computed(() => props.disabled),
currentPage: currentPageBridge,
changeEvent: handleCurrentChange,
handleSizeChange,
})
return () => {
if (!assertValidUsage.value) {
warn(componentName, t('el.pagination.docRefer'))
return null
}
if (!props.layout) return null
if (props.hideOnSinglePage && pageCountBridge.value <= 1) return null
const rootChildren = []
const rightWrapperChildren = []
const rightWrapperRoot = h('div', { class: 'el-pagination__rightwrapper' }, rightWrapperChildren)
const TEMPLATE_MAP = {
prev: h(Prev, {
disabled: props.disabled,
currentPage: currentPageBridge.value,
prevText: props.prevText,
onClick: prev,
}),
jumper: h(Jumper),
pager: h(Pager, {
currentPage: currentPageBridge.value,
pageCount: pageCountBridge.value,
pagerCount: props.pagerCount,
onChange: handleCurrentChange,
disabled: props.disabled,
}),
next: h(Next, {
disabled: props.disabled,
currentPage: currentPageBridge.value,
pageCount: pageCountBridge.value,
nextText: props.nextText,
onClick: next,
}),
sizes: h(Sizes, {
pageSize: pageSizeBridge.value,
pageSizes: props.pageSizes,
popperClass: props.popperClass,
disabled: props.disabled,
}),
slot: slots?.default?.() ?? null,
total: h(Total, { total: isAbsent(props.total) ? 0 : props.total }),
}
const components = props.layout.split(',').map((item: string) => item.trim())
let haveRightWrapper = false
components.forEach((c: keyof typeof TEMPLATE_MAP | '->') => {
if (c === '->') {
haveRightWrapper = true
return
}
if (!haveRightWrapper) {
rootChildren.push(TEMPLATE_MAP[c])
} else {
rightWrapperChildren.push(TEMPLATE_MAP[c])
}
})
if (haveRightWrapper && rightWrapperChildren.length > 0) {
rootChildren.unshift(rightWrapperRoot)
}
return h('div', {
role: 'pagination',
'aria-label': 'pagination',
class: [
'el-pagination',
{
'is-background': props.background,
'el-pagination--small': props.small,
},
],
}, rootChildren)
}
},
})