quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
335 lines (288 loc) • 9.33 kB
JavaScript
import { h, ref, computed, onBeforeUpdate, getCurrentInstance } from 'vue'
import QIcon from '../icon/QIcon.js'
import useSize, {
useSizeProps
} from '../../composables/private.use-size/use-size.js'
import {
useFormProps,
useFormAttrs,
useFormInject
} from '../../composables/use-form/private.use-form.js'
import { createComponent } from '../../utils/private.create/create.js'
import { stopAndPrevent } from '../../utils/event/event.js'
import { between } from '../../utils/format/format.js'
import { hMergeSlot } from '../../utils/private.render/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
if (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
)
}
}
})