VRating.mjs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { createTextVNode as _createTextVNode, mergeProps as _mergeProps, createVNode as _createVNode, Fragment as _Fragment } from "vue";
  2. // Styles
  3. import "./VRating.css";
  4. // Components
  5. import { VBtn } from "../VBtn/index.mjs"; // Composables
  6. import { makeComponentProps } from "../../composables/component.mjs";
  7. import { makeDensityProps } from "../../composables/density.mjs";
  8. import { IconValue } from "../../composables/icons.mjs";
  9. import { useLocale } from "../../composables/locale.mjs";
  10. import { useProxiedModel } from "../../composables/proxiedModel.mjs";
  11. import { makeSizeProps } from "../../composables/size.mjs";
  12. import { makeTagProps } from "../../composables/tag.mjs";
  13. import { makeThemeProps, provideTheme } from "../../composables/theme.mjs"; // Utilities
  14. import { computed, shallowRef } from 'vue';
  15. import { clamp, createRange, genericComponent, getUid, propsFactory, useRender } from "../../util/index.mjs"; // Types
  16. export const makeVRatingProps = propsFactory({
  17. name: String,
  18. itemAriaLabel: {
  19. type: String,
  20. default: '$vuetify.rating.ariaLabel.item'
  21. },
  22. activeColor: String,
  23. color: String,
  24. clearable: Boolean,
  25. disabled: Boolean,
  26. emptyIcon: {
  27. type: IconValue,
  28. default: '$ratingEmpty'
  29. },
  30. fullIcon: {
  31. type: IconValue,
  32. default: '$ratingFull'
  33. },
  34. halfIncrements: Boolean,
  35. hover: Boolean,
  36. length: {
  37. type: [Number, String],
  38. default: 5
  39. },
  40. readonly: Boolean,
  41. modelValue: {
  42. type: [Number, String],
  43. default: 0
  44. },
  45. itemLabels: Array,
  46. itemLabelPosition: {
  47. type: String,
  48. default: 'top',
  49. validator: v => ['top', 'bottom'].includes(v)
  50. },
  51. ripple: Boolean,
  52. ...makeComponentProps(),
  53. ...makeDensityProps(),
  54. ...makeSizeProps(),
  55. ...makeTagProps(),
  56. ...makeThemeProps()
  57. }, 'VRating');
  58. export const VRating = genericComponent()({
  59. name: 'VRating',
  60. props: makeVRatingProps(),
  61. emits: {
  62. 'update:modelValue': value => true
  63. },
  64. setup(props, _ref) {
  65. let {
  66. slots
  67. } = _ref;
  68. const {
  69. t
  70. } = useLocale();
  71. const {
  72. themeClasses
  73. } = provideTheme(props);
  74. const rating = useProxiedModel(props, 'modelValue');
  75. const normalizedValue = computed(() => clamp(parseFloat(rating.value), 0, +props.length));
  76. const range = computed(() => createRange(Number(props.length), 1));
  77. const increments = computed(() => range.value.flatMap(v => props.halfIncrements ? [v - 0.5, v] : [v]));
  78. const hoverIndex = shallowRef(-1);
  79. const itemState = computed(() => increments.value.map(value => {
  80. const isHovering = props.hover && hoverIndex.value > -1;
  81. const isFilled = normalizedValue.value >= value;
  82. const isHovered = hoverIndex.value >= value;
  83. const isFullIcon = isHovering ? isHovered : isFilled;
  84. const icon = isFullIcon ? props.fullIcon : props.emptyIcon;
  85. const activeColor = props.activeColor ?? props.color;
  86. const color = isFilled || isHovered ? activeColor : props.color;
  87. return {
  88. isFilled,
  89. isHovered,
  90. icon,
  91. color
  92. };
  93. }));
  94. const eventState = computed(() => [0, ...increments.value].map(value => {
  95. function onMouseenter() {
  96. hoverIndex.value = value;
  97. }
  98. function onMouseleave() {
  99. hoverIndex.value = -1;
  100. }
  101. function onClick() {
  102. if (props.disabled || props.readonly) return;
  103. rating.value = normalizedValue.value === value && props.clearable ? 0 : value;
  104. }
  105. return {
  106. onMouseenter: props.hover ? onMouseenter : undefined,
  107. onMouseleave: props.hover ? onMouseleave : undefined,
  108. onClick
  109. };
  110. }));
  111. const name = computed(() => props.name ?? `v-rating-${getUid()}`);
  112. function VRatingItem(_ref2) {
  113. let {
  114. value,
  115. index,
  116. showStar = true
  117. } = _ref2;
  118. const {
  119. onMouseenter,
  120. onMouseleave,
  121. onClick
  122. } = eventState.value[index + 1];
  123. const id = `${name.value}-${String(value).replace('.', '-')}`;
  124. const btnProps = {
  125. color: itemState.value[index]?.color,
  126. density: props.density,
  127. disabled: props.disabled,
  128. icon: itemState.value[index]?.icon,
  129. ripple: props.ripple,
  130. size: props.size,
  131. variant: 'plain'
  132. };
  133. return _createVNode(_Fragment, null, [_createVNode("label", {
  134. "for": id,
  135. "class": {
  136. 'v-rating__item--half': props.halfIncrements && value % 1 > 0,
  137. 'v-rating__item--full': props.halfIncrements && value % 1 === 0
  138. },
  139. "onMouseenter": onMouseenter,
  140. "onMouseleave": onMouseleave,
  141. "onClick": onClick
  142. }, [_createVNode("span", {
  143. "class": "v-rating__hidden"
  144. }, [t(props.itemAriaLabel, value, props.length)]), !showStar ? undefined : slots.item ? slots.item({
  145. ...itemState.value[index],
  146. props: btnProps,
  147. value,
  148. index,
  149. rating: normalizedValue.value
  150. }) : _createVNode(VBtn, _mergeProps({
  151. "aria-label": t(props.itemAriaLabel, value, props.length)
  152. }, btnProps), null)]), _createVNode("input", {
  153. "class": "v-rating__hidden",
  154. "name": name.value,
  155. "id": id,
  156. "type": "radio",
  157. "value": value,
  158. "checked": normalizedValue.value === value,
  159. "tabindex": -1,
  160. "readonly": props.readonly,
  161. "disabled": props.disabled
  162. }, null)]);
  163. }
  164. function createLabel(labelProps) {
  165. if (slots['item-label']) return slots['item-label'](labelProps);
  166. if (labelProps.label) return _createVNode("span", null, [labelProps.label]);
  167. return _createVNode("span", null, [_createTextVNode("\xA0")]);
  168. }
  169. useRender(() => {
  170. const hasLabels = !!props.itemLabels?.length || slots['item-label'];
  171. return _createVNode(props.tag, {
  172. "class": ['v-rating', {
  173. 'v-rating--hover': props.hover,
  174. 'v-rating--readonly': props.readonly
  175. }, themeClasses.value, props.class],
  176. "style": props.style
  177. }, {
  178. default: () => [_createVNode(VRatingItem, {
  179. "value": 0,
  180. "index": -1,
  181. "showStar": false
  182. }, null), range.value.map((value, i) => _createVNode("div", {
  183. "class": "v-rating__wrapper"
  184. }, [hasLabels && props.itemLabelPosition === 'top' ? createLabel({
  185. value,
  186. index: i,
  187. label: props.itemLabels?.[i]
  188. }) : undefined, _createVNode("div", {
  189. "class": "v-rating__item"
  190. }, [props.halfIncrements ? _createVNode(_Fragment, null, [_createVNode(VRatingItem, {
  191. "value": value - 0.5,
  192. "index": i * 2
  193. }, null), _createVNode(VRatingItem, {
  194. "value": value,
  195. "index": i * 2 + 1
  196. }, null)]) : _createVNode(VRatingItem, {
  197. "value": value,
  198. "index": i
  199. }, null)]), hasLabels && props.itemLabelPosition === 'bottom' ? createLabel({
  200. value,
  201. index: i,
  202. label: props.itemLabels?.[i]
  203. }) : undefined]))]
  204. });
  205. });
  206. return {};
  207. }
  208. });
  209. //# sourceMappingURL=VRating.mjs.map