quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
376 lines (328 loc) • 9.57 kB
JavaScript
import { h, ref, watch, computed, getCurrentInstance } from 'vue'
import QBtn from '../btn/QBtn.js'
import QInput from '../input/QInput.js'
import useDark, { useDarkProps } from '../../composables/private/use-dark.js'
import { createComponent } from '../../utils/private/create.js'
import { between } from '../../utils/format.js'
import { isKeyCode } from '../../utils/private/key-composition.js'
export default createComponent({
name: 'QPagination',
props: {
...useDarkProps,
modelValue: {
type: Number,
required: true
},
min: {
type: Number,
default: 1
},
max: {
type: Number,
required: true
},
color: {
type: String,
default: 'primary'
},
textColor: String,
activeColor: String,
activeTextColor: String,
inputStyle: [ Array, String, Object ],
inputClass: [ Array, String, Object ],
size: String,
disable: Boolean,
input: Boolean,
iconPrev: String,
iconNext: String,
iconFirst: String,
iconLast: String,
toFn: Function,
boundaryLinks: {
type: Boolean,
default: null
},
boundaryNumbers: {
type: Boolean,
default: null
},
directionLinks: {
type: Boolean,
default: null
},
ellipses: {
type: Boolean,
default: null
},
maxPages: {
type: Number,
default: 0,
validator: v => v >= 0
},
ripple: {
type: [ Boolean, Object ],
default: null
},
round: Boolean,
rounded: Boolean,
flat: Boolean,
outline: Boolean,
unelevated: Boolean,
push: Boolean,
glossy: Boolean,
dense: Boolean,
padding: {
type: String,
default: '3px 2px'
}
},
emits: [ 'update:modelValue' ],
setup (props, { emit }) {
const { proxy } = getCurrentInstance()
const { $q } = proxy
const isDark = useDark(props, $q)
const newPage = ref(null)
const model = computed({
get: () => props.modelValue,
set: val => {
val = parseInt(val, 10)
if (props.disable || isNaN(val)) {
return
}
const value = between(val, props.min, props.max)
if (props.modelValue !== value) {
emit('update:modelValue', value)
}
}
})
watch(() => props.min + props.max, () => {
model.value = props.modelValue
})
function getBool (val, otherwise) {
return [ true, false ].includes(val)
? val
: otherwise
}
const classes = computed(() =>
'q-pagination row no-wrap items-center'
+ (props.disable === true ? ' disabled' : '')
)
const inputPlaceholder = computed(() => model.value + ' / ' + props.max)
const __boundaryLinks = computed(() => getBool(props.boundaryLinks, props.input))
const __boundaryNumbers = computed(() => getBool(props.boundaryNumbers, !props.input))
const __directionLinks = computed(() => getBool(props.directionLinks, props.input))
const __ellipses = computed(() => getBool(props.ellipses, !props.input))
const icons = computed(() => {
const ico = [
props.iconFirst || $q.iconSet.pagination.first,
props.iconPrev || $q.iconSet.pagination.prev,
props.iconNext || $q.iconSet.pagination.next,
props.iconLast || $q.iconSet.pagination.last
]
return $q.lang.rtl === true ? ico.reverse() : ico
})
const attrs = computed(() => (
props.disable === true
? { 'aria-disabled': 'true' }
: {}
))
const btnProps = computed(() => ({
round: props.round,
rounded: props.rounded,
outline: props.outline,
unelevated: props.unelevated,
push: props.push,
glossy: props.glossy,
dense: props.dense,
padding: props.padding,
color: props.color,
flat: true,
size: props.size,
ripple: props.ripple !== null
? props.ripple
: true
}))
const activeBtnProps = computed(() => ({
flat: props.flat,
color: props.activeColor || props.color,
textColor: props.activeTextColor || props.textColor
}))
function set (value) {
model.value = value
}
function setByOffset (offset) {
model.value = model.value + offset
}
function updateModel () {
model.value = newPage.value
newPage.value = null
}
function getBtn (cfg, page) {
const data = { ...btnProps.value, ...cfg }
if (page !== void 0) {
if (props.toFn !== void 0) {
data.to = props.toFn(page)
}
else {
data.onClick = () => set(page)
}
}
return h(QBtn, data)
}
// expose public methods
Object.assign(proxy, { set, setByOffset })
return () => {
const
contentStart = [],
contentEnd = [],
contentMiddle = []
if (__boundaryLinks.value) {
contentStart.push(getBtn({
key: 'bls',
disable: props.disable || props.modelValue <= props.min,
icon: icons.value[ 0 ]
}, props.min))
contentEnd.unshift(getBtn({
key: 'ble',
disable: props.disable || props.modelValue >= props.max,
icon: icons.value[ 3 ]
}, props.max))
}
if (__directionLinks.value) {
contentStart.push(getBtn({
key: 'bdp',
disable: props.disable || props.modelValue <= props.min,
icon: icons.value[ 1 ]
}, props.modelValue - 1))
contentEnd.unshift(getBtn({
key: 'bdn',
disable: props.disable || props.modelValue >= props.max,
icon: icons.value[ 2 ]
}, props.modelValue + 1))
}
if (props.input === true) {
contentMiddle.push(h(QInput, {
class: 'inline',
style: {
width: `${ inputPlaceholder.value.length / 1.5 }em`
},
type: 'number',
dense: true,
value: newPage.value,
disable: props.disable,
dark: isDark.value,
borderless: true,
inputClass: props.inputClass,
inputStyle: props.inputStyle,
placeholder: inputPlaceholder.value,
min: props.min,
max: props.max,
'onUpdate:modelValue' (value) { newPage.value = value },
onKeyup (e) { isKeyCode(e, 13) === true && updateModel() },
onBlur: updateModel
}))
}
else { // is type select
let
maxPages = Math.max(
props.maxPages,
1 + (__ellipses.value ? 2 : 0) + (__boundaryNumbers.value ? 2 : 0)
),
pgFrom = props.min,
pgTo = props.max,
ellipsesStart = false,
ellipsesEnd = false,
boundaryStart = false,
boundaryEnd = false
if (props.maxPages && maxPages < (props.max - props.min + 1)) {
maxPages = 1 + Math.floor(maxPages / 2) * 2
pgFrom = Math.max(props.min, Math.min(props.max - maxPages + 1, props.modelValue - Math.floor(maxPages / 2)))
pgTo = Math.min(props.max, pgFrom + maxPages - 1)
if (__boundaryNumbers.value) {
boundaryStart = true
pgFrom += 1
}
if (__ellipses.value && pgFrom > (props.min + (__boundaryNumbers.value ? 1 : 0))) {
ellipsesStart = true
pgFrom += 1
}
if (__boundaryNumbers.value) {
boundaryEnd = true
pgTo -= 1
}
if (__ellipses.value && pgTo < (props.max - (__boundaryNumbers.value ? 1 : 0))) {
ellipsesEnd = true
pgTo -= 1
}
}
const style = {
minWidth: `${ Math.max(2, String(props.max).length) }em`
}
if (boundaryStart) {
const active = props.min === props.modelValue
contentStart.push(getBtn({
key: 'bns',
style,
disable: props.disable,
flat: !active,
label: props.min,
...(active ? activeBtnProps.value : {})
}, props.min))
}
if (boundaryEnd) {
const active = props.max === props.modelValue
contentEnd.unshift(getBtn({
key: 'bne',
style,
disable: props.disable,
flat: !active,
label: props.max,
...(active ? activeBtnProps.value : {})
}, props.max))
}
if (ellipsesStart) {
contentStart.push(getBtn({
key: 'bes',
style,
disable: props.disable,
label: '…',
ripple: false
}, pgFrom - 1))
}
if (ellipsesEnd) {
contentEnd.unshift(getBtn({
key: 'bee',
style,
disable: props.disable,
label: '…',
ripple: false
}, pgTo + 1))
}
for (let i = pgFrom; i <= pgTo; i++) {
const btn = {
key: `bpg${ i }`,
style,
disable: props.disable,
label: i
}
if (i === props.modelValue) {
Object.assign(btn, activeBtnProps.value)
}
contentMiddle.push(getBtn(btn, i))
}
}
return h('div', {
class: classes.value,
...attrs.value
}, [
contentStart,
h('div', {
class: 'row justify-center'
}, [
contentMiddle
]),
contentEnd
])
}
}
})