@coreui/vue-pro
Version:
UI Components Library for Vue.js
289 lines (270 loc) • 8.61 kB
text/typescript
import { PropType, defineComponent, h, ref, watch, withDirectives } from 'vue'
import vCTooltip from '../../directives/v-c-tooltip'
const CRating = defineComponent({
name: 'CRating',
props: {
/**
* Enables the clearing upon clicking the selected item again.
*/
allowClear: Boolean,
/**
* Toggle the disabled state for the component.
*/
disabled: Boolean,
/**
* If enabled, only the currently selected icon will be visibly highlighted.
*/
highlightOnlySelected: Boolean,
/**
* Specifies the total number of stars to be displayed in the star rating component. This property determines the scale of the rating, such as out of 5 stars, 10 stars, etc.
*/
itemCount: {
type: Number,
default: 5,
},
/**
* The default name for a value passed using v-model.
*/
modelValue: Number,
/**
* The name attribute of the radio input elements.
*/
name: String,
/**
* Minimum increment value change allowed.
*/
precision: {
type: Number,
default: 1,
},
/**
* Toggle the readonly state for the component.
*/
readOnly: Boolean,
/**
* Size the component small, large, or custom if you define custom icons with custom height.
*
* @values 'sm', 'lg', 'custom'
*/
size: {
type: String,
validator: (value: string) => {
return ['sm', 'lg', 'custom'].includes(value)
},
},
/**
* Enable tooltips with default values or set specific labels for each icon.
*/
tooltips: {
type: [Boolean, Array] as PropType<boolean | string[]>,
},
/**
* The `value` attribute of component.
* */
value: Number,
},
emits: [
/**
* Execute a function when a user changes the selected element.
*
* @property {number | null} value
*/
'change',
/**
* Execute a function when a user hover the element.
*
* @property {number | null} value
*/
'hover',
/**
* Emit the new value whenever there’s a change event.
*/
'update:modelValue',
],
setup(props, { slots, emit }) {
const cleared = ref(false)
const currentValue = ref((props.modelValue || props.value) ?? null)
const hoverValue = ref<number | null>(null)
const tooltipValue = ref<number | null>(null)
const name = props.name || `name${Math.floor(Math.random() * 1_000_000)}`
const uid = `id${Math.floor(Math.random() * 1_000_000)}`
watch(
() => props.value,
() => {
if (props.value !== undefined) {
currentValue.value = props.value
}
},
)
watch(
() => props.modelValue,
() => {
if (props.modelValue !== undefined) {
currentValue.value = props.modelValue
}
},
)
const handleMouseEnter = (value: number) => {
if (props.disabled || props.readOnly) {
return
}
emit('hover', value)
hoverValue.value = value
tooltipValue.value = value
}
const handleMouseLeave = () => {
if (props.disabled || props.readOnly) {
return
}
emit('hover', null)
hoverValue.value = null
}
const handleOnChange = (value: number) => {
if (props.disabled || props.readOnly) {
return
}
if (cleared.value) {
cleared.value = false
return
}
currentValue.value = value
emit('change', value)
emit('update:modelValue', value)
}
const handleOnClick = (value: number) => {
if (props.disabled || props.readOnly) {
return
}
if (props.allowClear && value === currentValue.value) {
emit('change', value)
cleared.value = true
currentValue.value = null
hoverValue.value = null
}
}
return () =>
h(
'div',
{
class: [
'rating',
{
[`rating-${props.size}`]: props.size,
disabled: props.disabled,
readonly: props.readOnly,
},
],
role: 'radiogroup',
},
[
Array.from({ length: props.itemCount }, (_, index) => {
const numberOfRadios = 1 / props.precision
return withDirectives(
h(
'div',
{
class: 'rating-item',
},
[
Array.from({ length: numberOfRadios }, (_, _index) => {
const isNotLastItem = _index + 1 < numberOfRadios
const value =
numberOfRadios === 1
? index + 1
: index + (_index + 1) * (1 * props.precision)
const id = `${uid}${value}`
const isItemChecked = () => value === currentValue.value
const isItemActive = () => {
if (
props.highlightOnlySelected
? hoverValue.value === value
: hoverValue.value && hoverValue.value >= value
) {
return true
}
if (
hoverValue.value === null &&
(props.highlightOnlySelected
? isItemChecked()
: currentValue.value && currentValue.value >= value)
) {
return true
}
return false
}
return [
h(
'label',
{
class: [
'rating-item-label',
{
active: isItemActive(),
},
],
for: id,
onClick: () => handleOnClick(value),
onMouseenter: () => handleMouseEnter(value),
onMouseleave: () => handleMouseLeave(),
...(isNotLastItem && {
style: {
zIndex: 1 / props.precision - _index,
position: 'absolute',
width: `${props.precision * (_index + 1) * 100}%`,
overflow: 'hidden',
opacity: 0,
},
}),
},
{
default: () => [
slots.icon
? h(
'div',
{ class: 'rating-item-custom-icon' },
slots.icon({ value: index + 1 }),
)
: h('div', { class: 'rating-item-icon' }),
slots.activeIcon &&
h(
'div',
{ class: 'rating-item-custom-icon-active' },
slots.activeIcon({ value: index + 1 }),
),
],
},
),
h('input', {
checked: isItemChecked(),
class: 'rating-item-input',
disabled: props.disabled || props.readOnly,
id: id,
name: name,
onBlur: () => handleMouseLeave(),
onChange: () => handleOnChange(value),
onFocus: () => handleMouseEnter(value),
type: 'radio',
value: value,
}),
]
}),
],
),
props.tooltips
? [
[
vCTooltip,
{
content: Array.isArray(props.tooltips) ? props.tooltips[index] : index + 1,
placement: 'top',
},
],
]
: [],
)
}),
],
)
},
})
export { CRating }