quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
276 lines (229 loc) • 8.66 kB
JavaScript
import { h, ref, computed, onBeforeUpdate, getCurrentInstance } from 'vue'
import QIcon from '../icon/QIcon.js'
import useSize, { useSizeProps } from '../../composables/private/use-size.js'
import { useFormProps, useFormAttrs, useFormInject } from '../../composables/private/use-form.js'
import { createComponent } from '../../utils/private/create.js'
import { stopAndPrevent } from '../../utils/event.js'
import { between } from '../../utils/format.js'
import { hMergeSlot } from '../../utils/private/render.js'
export default createComponent({
name: 'QRating',
props: {
...useSizeProps,
...useFormProps,
modelValue: {
type: Number,
required: true
},
max: {
type: [ String, Number ],
default: 5
},
icon: [ String, Array ],
iconHalf: [ String, Array ],
iconSelected: [ String, Array ],
iconAriaLabel: [ String, Array ],
color: [ String, Array ],
colorHalf: [ String, Array ],
colorSelected: [ String, Array ],
noReset: Boolean,
noDimming: Boolean,
readonly: Boolean,
disable: Boolean
},
emits: [ 'update:modelValue' ],
setup (props, { slots, emit }) {
const { proxy: { $q } } = getCurrentInstance()
const sizeStyle = useSize(props)
const formAttrs = useFormAttrs(props)
const injectFormInput = useFormInject(formAttrs)
const mouseModel = ref(0)
let iconRefs = {}
const editable = computed(() =>
props.readonly !== true && props.disable !== true
)
const classes = computed(() =>
'q-rating row inline items-center'
+ ` q-rating--${ editable.value === true ? '' : 'non-' }editable`
+ (props.noDimming === true ? ' q-rating--no-dimming' : '')
+ (props.disable === true ? ' disabled' : '')
+ (
props.color !== void 0 && Array.isArray(props.color) === false
? ` text-${ props.color }`
: ''
)
)
const iconData = computed(() => {
const
iconLen = Array.isArray(props.icon) === true ? props.icon.length : 0,
selIconLen = Array.isArray(props.iconSelected) === true ? props.iconSelected.length : 0,
halfIconLen = Array.isArray(props.iconHalf) === true ? props.iconHalf.length : 0,
colorLen = Array.isArray(props.color) === true ? props.color.length : 0,
selColorLen = Array.isArray(props.colorSelected) === true ? props.colorSelected.length : 0,
halfColorLen = Array.isArray(props.colorHalf) === true ? props.colorHalf.length : 0
return {
iconLen,
icon: iconLen > 0 ? props.icon[ iconLen - 1 ] : props.icon,
selIconLen,
selIcon: selIconLen > 0 ? props.iconSelected[ selIconLen - 1 ] : props.iconSelected,
halfIconLen,
halfIcon: halfIconLen > 0 ? props.iconHalf[ selIconLen - 1 ] : props.iconHalf,
colorLen,
color: colorLen > 0 ? props.color[ colorLen - 1 ] : props.color,
selColorLen,
selColor: selColorLen > 0 ? props.colorSelected[ selColorLen - 1 ] : props.colorSelected,
halfColorLen,
halfColor: halfColorLen > 0 ? props.colorHalf[ halfColorLen - 1 ] : props.colorHalf
}
})
const iconLabel = computed(() => {
if (typeof props.iconAriaLabel === 'string') {
const label = props.iconAriaLabel.length > 0 ? `${ props.iconAriaLabel } ` : ''
return i => `${ label }${ i }`
}
if (Array.isArray(props.iconAriaLabel) === true) {
const iMax = props.iconAriaLabel.length
if (iMax > 0) {
return i => props.iconAriaLabel[ Math.min(i, iMax) - 1 ]
}
}
return (i, label) => `${ label } ${ i }`
})
const stars = computed(() => {
const
acc = [],
icons = iconData.value,
ceil = Math.ceil(props.modelValue),
tabindex = editable.value === true ? 0 : null
const halfIndex = props.iconHalf === void 0 || ceil === props.modelValue
? -1
: ceil
for (let i = 1; i <= props.max; i++) {
const
active = (mouseModel.value === 0 && props.modelValue >= i) || (mouseModel.value > 0 && mouseModel.value >= i),
half = halfIndex === i && mouseModel.value < i,
exSelected = mouseModel.value > 0 && (half === true ? ceil : props.modelValue) >= i && mouseModel.value < i,
color = half === true
? (i <= icons.halfColorLen ? props.colorHalf[ i - 1 ] : icons.halfColor)
: (
icons.selColor !== void 0 && active === true
? (i <= icons.selColorLen ? props.colorSelected[ i - 1 ] : icons.selColor)
: (i <= icons.colorLen ? props.color[ i - 1 ] : icons.color)
),
name = (
half === true
? (i <= icons.halfIconLen ? props.iconHalf[ i - 1 ] : icons.halfIcon)
: (
icons.selIcon !== void 0 && (active === true || exSelected === true)
? (i <= icons.selIconLen ? props.iconSelected[ i - 1 ] : icons.selIcon)
: (i <= icons.iconLen ? props.icon[ i - 1 ] : icons.icon)
)
) || $q.iconSet.rating.icon
acc.push({
name: (
half === true
? (i <= icons.halfIconLen ? props.iconHalf[ i - 1 ] : icons.halfIcon)
: (
icons.selIcon !== void 0 && (active === true || exSelected === true)
? (i <= icons.selIconLen ? props.iconSelected[ i - 1 ] : icons.selIcon)
: (i <= icons.iconLen ? props.icon[ i - 1 ] : icons.icon)
)
) || $q.iconSet.rating.icon,
attrs: {
tabindex,
role: 'radio',
'aria-checked': props.modelValue === i ? 'true' : 'false',
'aria-label': iconLabel.value(i, name)
},
iconClass: 'q-rating__icon'
+ (active === true || half === true ? ' q-rating__icon--active' : '')
+ (exSelected === true ? ' q-rating__icon--exselected' : '')
+ (mouseModel.value === i ? ' q-rating__icon--hovered' : '')
+ (color !== void 0 ? ` text-${ color }` : '')
})
}
return acc
})
const attributes = computed(() => {
const attrs = { role: 'radiogroup' }
if (props.disable === true) {
attrs[ 'aria-disabled' ] = 'true'
}
if (props.readonly === true) {
attrs[ 'aria-readonly' ] = 'true'
}
return attrs
})
function set (value) {
if (editable.value === true) {
const
model = between(parseInt(value, 10), 1, parseInt(props.max, 10)),
newVal = props.noReset !== true && props.modelValue === model ? 0 : model
newVal !== props.modelValue && emit('update:modelValue', newVal)
mouseModel.value = 0
}
}
function setHoverValue (value) {
if (editable.value === true) {
mouseModel.value = value
}
}
function onKeyup (e, i) {
switch (e.keyCode) {
case 13:
case 32:
set(i)
return stopAndPrevent(e)
case 37: // LEFT ARROW
case 40: // DOWN ARROW
if (iconRefs[ `rt${ i - 1 }` ]) {
iconRefs[ `rt${ i - 1 }` ].focus()
}
return stopAndPrevent(e)
case 39: // RIGHT ARROW
case 38: // UP ARROW
if (iconRefs[ `rt${ i + 1 }` ]) {
iconRefs[ `rt${ i + 1 }` ].focus()
}
return stopAndPrevent(e)
}
}
function resetMouseModel () {
mouseModel.value = 0
}
onBeforeUpdate(() => {
iconRefs = {}
})
return () => {
const child = []
stars.value.forEach(({ iconClass, name, attrs }, index) => {
const i = index + 1
child.push(
h('div', {
key: i,
ref: el => { iconRefs[ `rt${ i }` ] = el },
class: 'q-rating__icon-container flex flex-center',
...attrs,
onClick () { set(i) },
onMouseover () { setHoverValue(i) },
onMouseout: resetMouseModel,
onFocus () { setHoverValue(i) },
onBlur: resetMouseModel,
onKeyup (e) { onKeyup(e, i) }
}, hMergeSlot(
slots[ `tip-${ i }` ],
[ h(QIcon, { class: iconClass, name }) ]
))
)
})
if (props.name !== void 0 && props.disable !== true) {
injectFormInput(child, 'push')
}
return h('div', {
class: classes.value,
style: sizeStyle.value,
...attributes.value
}, child)
}
}
})