210 lines
6.9 KiB
JavaScript
210 lines
6.9 KiB
JavaScript
|
import { createTextVNode as _createTextVNode, mergeProps as _mergeProps, createVNode as _createVNode, Fragment as _Fragment } from "vue";
|
||
|
// Styles
|
||
|
import "./VRating.css";
|
||
|
|
||
|
// Components
|
||
|
import { VBtn } from "../VBtn/index.mjs"; // Composables
|
||
|
import { makeComponentProps } from "../../composables/component.mjs";
|
||
|
import { makeDensityProps } from "../../composables/density.mjs";
|
||
|
import { IconValue } from "../../composables/icons.mjs";
|
||
|
import { useLocale } from "../../composables/locale.mjs";
|
||
|
import { useProxiedModel } from "../../composables/proxiedModel.mjs";
|
||
|
import { makeSizeProps } from "../../composables/size.mjs";
|
||
|
import { makeTagProps } from "../../composables/tag.mjs";
|
||
|
import { makeThemeProps, provideTheme } from "../../composables/theme.mjs"; // Utilities
|
||
|
import { computed, shallowRef } from 'vue';
|
||
|
import { clamp, createRange, genericComponent, getUid, propsFactory, useRender } from "../../util/index.mjs"; // Types
|
||
|
export const makeVRatingProps = propsFactory({
|
||
|
name: String,
|
||
|
itemAriaLabel: {
|
||
|
type: String,
|
||
|
default: '$vuetify.rating.ariaLabel.item'
|
||
|
},
|
||
|
activeColor: String,
|
||
|
color: String,
|
||
|
clearable: Boolean,
|
||
|
disabled: Boolean,
|
||
|
emptyIcon: {
|
||
|
type: IconValue,
|
||
|
default: '$ratingEmpty'
|
||
|
},
|
||
|
fullIcon: {
|
||
|
type: IconValue,
|
||
|
default: '$ratingFull'
|
||
|
},
|
||
|
halfIncrements: Boolean,
|
||
|
hover: Boolean,
|
||
|
length: {
|
||
|
type: [Number, String],
|
||
|
default: 5
|
||
|
},
|
||
|
readonly: Boolean,
|
||
|
modelValue: {
|
||
|
type: [Number, String],
|
||
|
default: 0
|
||
|
},
|
||
|
itemLabels: Array,
|
||
|
itemLabelPosition: {
|
||
|
type: String,
|
||
|
default: 'top',
|
||
|
validator: v => ['top', 'bottom'].includes(v)
|
||
|
},
|
||
|
ripple: Boolean,
|
||
|
...makeComponentProps(),
|
||
|
...makeDensityProps(),
|
||
|
...makeSizeProps(),
|
||
|
...makeTagProps(),
|
||
|
...makeThemeProps()
|
||
|
}, 'VRating');
|
||
|
export const VRating = genericComponent()({
|
||
|
name: 'VRating',
|
||
|
props: makeVRatingProps(),
|
||
|
emits: {
|
||
|
'update:modelValue': value => true
|
||
|
},
|
||
|
setup(props, _ref) {
|
||
|
let {
|
||
|
slots
|
||
|
} = _ref;
|
||
|
const {
|
||
|
t
|
||
|
} = useLocale();
|
||
|
const {
|
||
|
themeClasses
|
||
|
} = provideTheme(props);
|
||
|
const rating = useProxiedModel(props, 'modelValue');
|
||
|
const normalizedValue = computed(() => clamp(parseFloat(rating.value), 0, +props.length));
|
||
|
const range = computed(() => createRange(Number(props.length), 1));
|
||
|
const increments = computed(() => range.value.flatMap(v => props.halfIncrements ? [v - 0.5, v] : [v]));
|
||
|
const hoverIndex = shallowRef(-1);
|
||
|
const itemState = computed(() => increments.value.map(value => {
|
||
|
const isHovering = props.hover && hoverIndex.value > -1;
|
||
|
const isFilled = normalizedValue.value >= value;
|
||
|
const isHovered = hoverIndex.value >= value;
|
||
|
const isFullIcon = isHovering ? isHovered : isFilled;
|
||
|
const icon = isFullIcon ? props.fullIcon : props.emptyIcon;
|
||
|
const activeColor = props.activeColor ?? props.color;
|
||
|
const color = isFilled || isHovered ? activeColor : props.color;
|
||
|
return {
|
||
|
isFilled,
|
||
|
isHovered,
|
||
|
icon,
|
||
|
color
|
||
|
};
|
||
|
}));
|
||
|
const eventState = computed(() => [0, ...increments.value].map(value => {
|
||
|
function onMouseenter() {
|
||
|
hoverIndex.value = value;
|
||
|
}
|
||
|
function onMouseleave() {
|
||
|
hoverIndex.value = -1;
|
||
|
}
|
||
|
function onClick() {
|
||
|
if (props.disabled || props.readonly) return;
|
||
|
rating.value = normalizedValue.value === value && props.clearable ? 0 : value;
|
||
|
}
|
||
|
return {
|
||
|
onMouseenter: props.hover ? onMouseenter : undefined,
|
||
|
onMouseleave: props.hover ? onMouseleave : undefined,
|
||
|
onClick
|
||
|
};
|
||
|
}));
|
||
|
const name = computed(() => props.name ?? `v-rating-${getUid()}`);
|
||
|
function VRatingItem(_ref2) {
|
||
|
let {
|
||
|
value,
|
||
|
index,
|
||
|
showStar = true
|
||
|
} = _ref2;
|
||
|
const {
|
||
|
onMouseenter,
|
||
|
onMouseleave,
|
||
|
onClick
|
||
|
} = eventState.value[index + 1];
|
||
|
const id = `${name.value}-${String(value).replace('.', '-')}`;
|
||
|
const btnProps = {
|
||
|
color: itemState.value[index]?.color,
|
||
|
density: props.density,
|
||
|
disabled: props.disabled,
|
||
|
icon: itemState.value[index]?.icon,
|
||
|
ripple: props.ripple,
|
||
|
size: props.size,
|
||
|
variant: 'plain'
|
||
|
};
|
||
|
return _createVNode(_Fragment, null, [_createVNode("label", {
|
||
|
"for": id,
|
||
|
"class": {
|
||
|
'v-rating__item--half': props.halfIncrements && value % 1 > 0,
|
||
|
'v-rating__item--full': props.halfIncrements && value % 1 === 0
|
||
|
},
|
||
|
"onMouseenter": onMouseenter,
|
||
|
"onMouseleave": onMouseleave,
|
||
|
"onClick": onClick
|
||
|
}, [_createVNode("span", {
|
||
|
"class": "v-rating__hidden"
|
||
|
}, [t(props.itemAriaLabel, value, props.length)]), !showStar ? undefined : slots.item ? slots.item({
|
||
|
...itemState.value[index],
|
||
|
props: btnProps,
|
||
|
value,
|
||
|
index,
|
||
|
rating: normalizedValue.value
|
||
|
}) : _createVNode(VBtn, _mergeProps({
|
||
|
"aria-label": t(props.itemAriaLabel, value, props.length)
|
||
|
}, btnProps), null)]), _createVNode("input", {
|
||
|
"class": "v-rating__hidden",
|
||
|
"name": name.value,
|
||
|
"id": id,
|
||
|
"type": "radio",
|
||
|
"value": value,
|
||
|
"checked": normalizedValue.value === value,
|
||
|
"tabindex": -1,
|
||
|
"readonly": props.readonly,
|
||
|
"disabled": props.disabled
|
||
|
}, null)]);
|
||
|
}
|
||
|
function createLabel(labelProps) {
|
||
|
if (slots['item-label']) return slots['item-label'](labelProps);
|
||
|
if (labelProps.label) return _createVNode("span", null, [labelProps.label]);
|
||
|
return _createVNode("span", null, [_createTextVNode("\xA0")]);
|
||
|
}
|
||
|
useRender(() => {
|
||
|
const hasLabels = !!props.itemLabels?.length || slots['item-label'];
|
||
|
return _createVNode(props.tag, {
|
||
|
"class": ['v-rating', {
|
||
|
'v-rating--hover': props.hover,
|
||
|
'v-rating--readonly': props.readonly
|
||
|
}, themeClasses.value, props.class],
|
||
|
"style": props.style
|
||
|
}, {
|
||
|
default: () => [_createVNode(VRatingItem, {
|
||
|
"value": 0,
|
||
|
"index": -1,
|
||
|
"showStar": false
|
||
|
}, null), range.value.map((value, i) => _createVNode("div", {
|
||
|
"class": "v-rating__wrapper"
|
||
|
}, [hasLabels && props.itemLabelPosition === 'top' ? createLabel({
|
||
|
value,
|
||
|
index: i,
|
||
|
label: props.itemLabels?.[i]
|
||
|
}) : undefined, _createVNode("div", {
|
||
|
"class": "v-rating__item"
|
||
|
}, [props.halfIncrements ? _createVNode(_Fragment, null, [_createVNode(VRatingItem, {
|
||
|
"value": value - 0.5,
|
||
|
"index": i * 2
|
||
|
}, null), _createVNode(VRatingItem, {
|
||
|
"value": value,
|
||
|
"index": i * 2 + 1
|
||
|
}, null)]) : _createVNode(VRatingItem, {
|
||
|
"value": value,
|
||
|
"index": i
|
||
|
}, null)]), hasLabels && props.itemLabelPosition === 'bottom' ? createLabel({
|
||
|
value,
|
||
|
index: i,
|
||
|
label: props.itemLabels?.[i]
|
||
|
}) : undefined]))]
|
||
|
});
|
||
|
});
|
||
|
return {};
|
||
|
}
|
||
|
});
|
||
|
//# sourceMappingURL=VRating.mjs.map
|