UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

453 lines (384 loc) 11.7 kB
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/use-dark.js' import { btnDesignOptions, btnPadding, getBtnDesign } from '../btn/use-btn.js' import { createComponent } from '../../utils/private.create/create.js' import { between } from '../../utils/format/format.js' import { isKeyCode } from '../../utils/private.keyboard/key-composition.js' function getBool (val, otherwise) { return [ true, false ].includes(val) ? val : otherwise } export default createComponent({ name: 'QPagination', props: { ...useDarkProps, modelValue: { type: Number, required: true }, min: { type: [ Number, String ], default: 1 }, max: { type: [ Number, String ], required: true }, maxPages: { type: [ Number, String ], default: 0, validator: v => ( (typeof v === 'string' ? parseInt(v, 10) : v) >= 0 ) }, 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 }, ripple: { type: [ Boolean, Object ], default: null }, round: Boolean, rounded: Boolean, flat: Boolean, outline: Boolean, unelevated: Boolean, push: Boolean, glossy: Boolean, color: { type: String, default: 'primary' }, textColor: String, activeDesign: { type: String, default: '', values: v => v === '' || btnDesignOptions.includes(v) }, activeColor: String, activeTextColor: String, gutter: String, padding: { type: String, default: '3px 2px' } }, emits: [ 'update:modelValue' ], setup (props, { emit }) { const { proxy } = getCurrentInstance() const { $q } = proxy const isDark = useDark(props, $q) const minProp = computed(() => parseInt(props.min, 10)) const maxProp = computed(() => parseInt(props.max, 10)) const maxPagesProp = computed(() => parseInt(props.maxPages, 10)) const inputPlaceholder = computed(() => model.value + ' / ' + maxProp.value) const boundaryLinksProp = computed(() => getBool(props.boundaryLinks, props.input)) const boundaryNumbersProp = computed(() => getBool(props.boundaryNumbers, !props.input)) const directionLinksProp = computed(() => getBool(props.directionLinks, props.input)) const ellipsesProp = computed(() => getBool(props.ellipses, !props.input)) 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, minProp.value, maxProp.value) if (props.modelValue !== value) { emit('update:modelValue', value) } } }) watch(() => `${ minProp.value }|${ maxProp.value }`, () => { model.value = props.modelValue }) const classes = computed(() => 'q-pagination row no-wrap items-center' + (props.disable === true ? ' disabled' : '') ) const gutterProp = computed(() => ( props.gutter in btnPadding ? `${ btnPadding[ props.gutter ] }px` : props.gutter || null )) const gutterStyle = computed(() => ( gutterProp.value !== null ? `--q-pagination-gutter-parent:-${ gutterProp.value };--q-pagination-gutter-child:${ gutterProp.value }` : null )) 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(() => ({ 'aria-disabled': props.disable === true ? 'true' : 'false', role: 'navigation' })) const btnDesignProp = computed(() => getBtnDesign(props, 'flat')) const btnProps = computed(() => ({ [ btnDesignProp.value ]: true, round: props.round, rounded: props.rounded, padding: props.padding, color: props.color, textColor: props.textColor, size: props.size, ripple: props.ripple !== null ? props.ripple : true })) const btnActiveDesignProp = computed(() => { // we also reset non-active design const acc = { [ btnDesignProp.value ]: false } if (props.activeDesign !== '') { acc[ props.activeDesign ] = true } return acc }) const activeBtnProps = computed(() => ({ ...btnActiveDesignProp.value, color: props.activeColor || props.color, textColor: props.activeTextColor || props.textColor })) const btnConfig = computed(() => { let maxPages = Math.max( maxPagesProp.value, 1 + (ellipsesProp.value ? 2 : 0) + (boundaryNumbersProp.value ? 2 : 0) ) const acc = { pgFrom: minProp.value, pgTo: maxProp.value, ellipsesStart: false, ellipsesEnd: false, boundaryStart: false, boundaryEnd: false, marginalStyle: { minWidth: `${ Math.max(2, String(maxProp.value).length) }em` } } if (maxPagesProp.value && maxPages < (maxProp.value - minProp.value + 1)) { maxPages = 1 + Math.floor(maxPages / 2) * 2 acc.pgFrom = Math.max(minProp.value, Math.min(maxProp.value - maxPages + 1, props.modelValue - Math.floor(maxPages / 2))) acc.pgTo = Math.min(maxProp.value, acc.pgFrom + maxPages - 1) if (boundaryNumbersProp.value) { acc.boundaryStart = true acc.pgFrom++ } if (ellipsesProp.value && acc.pgFrom > (minProp.value + (boundaryNumbersProp.value ? 1 : 0))) { acc.ellipsesStart = true acc.pgFrom++ } if (boundaryNumbersProp.value) { acc.boundaryEnd = true acc.pgTo-- } if (ellipsesProp.value && acc.pgTo < (maxProp.value - (boundaryNumbersProp.value ? 1 : 0))) { acc.ellipsesEnd = true acc.pgTo-- } } return acc }) function set (value) { model.value = value } function setByOffset (offset) { model.value = model.value + offset } const inputEvents = computed(() => { function updateModel () { model.value = newPage.value newPage.value = null } return { 'onUpdate:modelValue': val => { newPage.value = val }, onKeyup: e => { isKeyCode(e, 13) === true && updateModel() }, onBlur: updateModel } }) function getBtn (cfg, page, active) { const data = { 'aria-label': page, 'aria-current': 'false', ...btnProps.value, ...cfg } if (active === true) { Object.assign(data, { 'aria-current': 'true', ...activeBtnProps.value }) } 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 = [] const contentEnd = [] let contentMiddle if (boundaryLinksProp.value === true) { contentStart.push( getBtn({ key: 'bls', disable: props.disable || props.modelValue <= minProp.value, icon: icons.value[ 0 ] }, minProp.value) ) contentEnd.unshift( getBtn({ key: 'ble', disable: props.disable || props.modelValue >= maxProp.value, icon: icons.value[ 3 ] }, maxProp.value) ) } if (directionLinksProp.value === true) { contentStart.push( getBtn({ key: 'bdp', disable: props.disable || props.modelValue <= minProp.value, icon: icons.value[ 1 ] }, props.modelValue - 1) ) contentEnd.unshift( getBtn({ key: 'bdn', disable: props.disable || props.modelValue >= maxProp.value, icon: icons.value[ 2 ] }, props.modelValue + 1) ) } if (props.input !== true) { // has buttons instead of inputbox contentMiddle = [] const { pgFrom, pgTo, marginalStyle: style } = btnConfig.value if (btnConfig.value.boundaryStart === true) { const active = minProp.value === props.modelValue contentStart.push( getBtn({ key: 'bns', style, disable: props.disable, label: minProp.value }, minProp.value, active) ) } if (btnConfig.value.boundaryEnd === true) { const active = maxProp.value === props.modelValue contentEnd.unshift( getBtn({ key: 'bne', style, disable: props.disable, label: maxProp.value }, maxProp.value, active) ) } if (btnConfig.value.ellipsesStart === true) { contentStart.push( getBtn({ key: 'bes', style, disable: props.disable, label: '…', ripple: false }, pgFrom - 1) ) } if (btnConfig.value.ellipsesEnd === true) { contentEnd.unshift( getBtn({ key: 'bee', style, disable: props.disable, label: '…', ripple: false }, pgTo + 1) ) } for (let i = pgFrom; i <= pgTo; i++) { contentMiddle.push( getBtn({ key: `bpg${ i }`, style, disable: props.disable, label: i }, i, i === props.modelValue) ) } } return h('div', { class: classes.value, ...attrs.value }, [ h('div', { class: 'q-pagination__content row no-wrap items-center', style: gutterStyle.value }, [ ...contentStart, props.input === true ? 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: minProp.value, max: maxProp.value, ...inputEvents.value }) : h('div', { class: 'q-pagination__middle row justify-center' }, contentMiddle), ...contentEnd ]) ]) } } })