'use client'; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _extends from "@babel/runtime/helpers/esm/extends"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import clamp from '@mui/utils/clamp'; import visuallyHidden from '@mui/utils/visuallyHidden'; import chainPropTypes from '@mui/utils/chainPropTypes'; import composeClasses from '@mui/utils/composeClasses'; import { useRtl } from '@mui/system/RtlProvider'; import { capitalize, useForkRef, useIsFocusVisible, useControlled, unstable_useId as useId } from '../utils'; import Star from '../internal/svg-icons/Star'; import StarBorder from '../internal/svg-icons/StarBorder'; import useThemeProps from '../styles/useThemeProps'; import styled, { slotShouldForwardProp } from '../styles/styled'; import ratingClasses, { getRatingUtilityClass } from './ratingClasses'; import { jsx as _jsx } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime"; function getDecimalPrecision(num) { var decimalPart = num.toString().split('.')[1]; return decimalPart ? decimalPart.length : 0; } function roundValueToPrecision(value, precision) { if (value == null) { return value; } var nearest = Math.round(value / precision) * precision; return Number(nearest.toFixed(getDecimalPrecision(precision))); } var useUtilityClasses = function useUtilityClasses(ownerState) { var classes = ownerState.classes, size = ownerState.size, readOnly = ownerState.readOnly, disabled = ownerState.disabled, emptyValueFocused = ownerState.emptyValueFocused, focusVisible = ownerState.focusVisible; var slots = { root: ['root', "size".concat(capitalize(size)), disabled && 'disabled', focusVisible && 'focusVisible', readOnly && 'readOnly'], label: ['label', 'pristine'], labelEmptyValue: [emptyValueFocused && 'labelEmptyValueActive'], icon: ['icon'], iconEmpty: ['iconEmpty'], iconFilled: ['iconFilled'], iconHover: ['iconHover'], iconFocus: ['iconFocus'], iconActive: ['iconActive'], decimal: ['decimal'], visuallyHidden: ['visuallyHidden'] }; return composeClasses(slots, getRatingUtilityClass, classes); }; var RatingRoot = styled('span', { name: 'MuiRating', slot: 'Root', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; return [_defineProperty({}, "& .".concat(ratingClasses.visuallyHidden), styles.visuallyHidden), styles.root, styles["size".concat(capitalize(ownerState.size))], ownerState.readOnly && styles.readOnly]; } })(function (_ref2) { var theme = _ref2.theme, ownerState = _ref2.ownerState; return _extends(_defineProperty(_defineProperty(_defineProperty({ display: 'inline-flex', // Required to position the pristine input absolutely position: 'relative', fontSize: theme.typography.pxToRem(24), color: '#faaf00', cursor: 'pointer', textAlign: 'left', width: 'min-content', WebkitTapHighlightColor: 'transparent' }, "&.".concat(ratingClasses.disabled), { opacity: (theme.vars || theme).palette.action.disabledOpacity, pointerEvents: 'none' }), "&.".concat(ratingClasses.focusVisible, " .").concat(ratingClasses.iconActive), { outline: '1px solid #999' }), "& .".concat(ratingClasses.visuallyHidden), visuallyHidden), ownerState.size === 'small' && { fontSize: theme.typography.pxToRem(18) }, ownerState.size === 'large' && { fontSize: theme.typography.pxToRem(30) }, ownerState.readOnly && { pointerEvents: 'none' }); }); var RatingLabel = styled('label', { name: 'MuiRating', slot: 'Label', overridesResolver: function overridesResolver(_ref3, styles) { var ownerState = _ref3.ownerState; return [styles.label, ownerState.emptyValueFocused && styles.labelEmptyValueActive]; } })(function (_ref4) { var ownerState = _ref4.ownerState; return _extends({ cursor: 'inherit' }, ownerState.emptyValueFocused && { top: 0, bottom: 0, position: 'absolute', outline: '1px solid #999', width: '100%' }); }); var RatingIcon = styled('span', { name: 'MuiRating', slot: 'Icon', overridesResolver: function overridesResolver(props, styles) { var ownerState = props.ownerState; return [styles.icon, ownerState.iconEmpty && styles.iconEmpty, ownerState.iconFilled && styles.iconFilled, ownerState.iconHover && styles.iconHover, ownerState.iconFocus && styles.iconFocus, ownerState.iconActive && styles.iconActive]; } })(function (_ref5) { var theme = _ref5.theme, ownerState = _ref5.ownerState; return _extends({ // Fit wrapper to actual icon size. display: 'flex', transition: theme.transitions.create('transform', { duration: theme.transitions.duration.shortest }), // Fix mouseLeave issue. // https://github.com/facebook/react/issues/4492 pointerEvents: 'none' }, ownerState.iconActive && { transform: 'scale(1.2)' }, ownerState.iconEmpty && { color: (theme.vars || theme).palette.action.disabled }); }); var RatingDecimal = styled('span', { name: 'MuiRating', slot: 'Decimal', shouldForwardProp: function shouldForwardProp(prop) { return slotShouldForwardProp(prop) && prop !== 'iconActive'; }, overridesResolver: function overridesResolver(props, styles) { var iconActive = props.iconActive; return [styles.decimal, iconActive && styles.iconActive]; } })(function (_ref6) { var iconActive = _ref6.iconActive; return _extends({ position: 'relative' }, iconActive && { transform: 'scale(1.2)' }); }); function IconContainer(props) { var value = props.value, other = _objectWithoutProperties(props, ["value"]); return /*#__PURE__*/_jsx("span", _extends({}, other)); } process.env.NODE_ENV !== "production" ? IconContainer.propTypes = { value: PropTypes.number.isRequired } : void 0; function RatingItem(props) { var classes = props.classes, disabled = props.disabled, emptyIcon = props.emptyIcon, focus = props.focus, getLabelText = props.getLabelText, highlightSelectedOnly = props.highlightSelectedOnly, hover = props.hover, icon = props.icon, IconContainerComponent = props.IconContainerComponent, isActive = props.isActive, itemValue = props.itemValue, labelProps = props.labelProps, name = props.name, onBlur = props.onBlur, onChange = props.onChange, onClick = props.onClick, onFocus = props.onFocus, readOnly = props.readOnly, ownerState = props.ownerState, ratingValue = props.ratingValue, ratingValueRounded = props.ratingValueRounded; var isFilled = highlightSelectedOnly ? itemValue === ratingValue : itemValue <= ratingValue; var isHovered = itemValue <= hover; var isFocused = itemValue <= focus; var isChecked = itemValue === ratingValueRounded; var id = useId(); var container = /*#__PURE__*/_jsx(RatingIcon, { as: IconContainerComponent, value: itemValue, className: clsx(classes.icon, isFilled ? classes.iconFilled : classes.iconEmpty, isHovered && classes.iconHover, isFocused && classes.iconFocus, isActive && classes.iconActive), ownerState: _extends({}, ownerState, { iconEmpty: !isFilled, iconFilled: isFilled, iconHover: isHovered, iconFocus: isFocused, iconActive: isActive }), children: emptyIcon && !isFilled ? emptyIcon : icon }); if (readOnly) { return /*#__PURE__*/_jsx("span", _extends({}, labelProps, { children: container })); } return /*#__PURE__*/_jsxs(React.Fragment, { children: [/*#__PURE__*/_jsxs(RatingLabel, _extends({ ownerState: _extends({}, ownerState, { emptyValueFocused: undefined }), htmlFor: id }, labelProps, { children: [container, /*#__PURE__*/_jsx("span", { className: classes.visuallyHidden, children: getLabelText(itemValue) })] })), /*#__PURE__*/_jsx("input", { className: classes.visuallyHidden, onFocus: onFocus, onBlur: onBlur, onChange: onChange, onClick: onClick, disabled: disabled, value: itemValue, id: id, type: "radio", name: name, checked: isChecked })] }); } process.env.NODE_ENV !== "production" ? RatingItem.propTypes = { classes: PropTypes.object.isRequired, disabled: PropTypes.bool.isRequired, emptyIcon: PropTypes.node, focus: PropTypes.number.isRequired, getLabelText: PropTypes.func.isRequired, highlightSelectedOnly: PropTypes.bool.isRequired, hover: PropTypes.number.isRequired, icon: PropTypes.node, IconContainerComponent: PropTypes.elementType.isRequired, isActive: PropTypes.bool.isRequired, itemValue: PropTypes.number.isRequired, labelProps: PropTypes.object, name: PropTypes.string, onBlur: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, onFocus: PropTypes.func.isRequired, ownerState: PropTypes.object.isRequired, ratingValue: PropTypes.number, ratingValueRounded: PropTypes.number, readOnly: PropTypes.bool.isRequired } : void 0; var defaultIcon = /*#__PURE__*/_jsx(Star, { fontSize: "inherit" }); var defaultEmptyIcon = /*#__PURE__*/_jsx(StarBorder, { fontSize: "inherit" }); function defaultLabelText(value) { return "".concat(value, " Star").concat(value !== 1 ? 's' : ''); } var Rating = /*#__PURE__*/React.forwardRef(function Rating(inProps, ref) { var props = useThemeProps({ name: 'MuiRating', props: inProps }); var className = props.className, _props$defaultValue = props.defaultValue, defaultValue = _props$defaultValue === void 0 ? null : _props$defaultValue, _props$disabled = props.disabled, disabled = _props$disabled === void 0 ? false : _props$disabled, _props$emptyIcon = props.emptyIcon, emptyIcon = _props$emptyIcon === void 0 ? defaultEmptyIcon : _props$emptyIcon, _props$emptyLabelText = props.emptyLabelText, emptyLabelText = _props$emptyLabelText === void 0 ? 'Empty' : _props$emptyLabelText, _props$getLabelText = props.getLabelText, getLabelText = _props$getLabelText === void 0 ? defaultLabelText : _props$getLabelText, _props$highlightSelec = props.highlightSelectedOnly, highlightSelectedOnly = _props$highlightSelec === void 0 ? false : _props$highlightSelec, _props$icon = props.icon, icon = _props$icon === void 0 ? defaultIcon : _props$icon, _props$IconContainerC = props.IconContainerComponent, IconContainerComponent = _props$IconContainerC === void 0 ? IconContainer : _props$IconContainerC, _props$max = props.max, max = _props$max === void 0 ? 5 : _props$max, nameProp = props.name, onChange = props.onChange, onChangeActive = props.onChangeActive, onMouseLeave = props.onMouseLeave, onMouseMove = props.onMouseMove, _props$precision = props.precision, precision = _props$precision === void 0 ? 1 : _props$precision, _props$readOnly = props.readOnly, readOnly = _props$readOnly === void 0 ? false : _props$readOnly, _props$size = props.size, size = _props$size === void 0 ? 'medium' : _props$size, valueProp = props.value, other = _objectWithoutProperties(props, ["className", "defaultValue", "disabled", "emptyIcon", "emptyLabelText", "getLabelText", "highlightSelectedOnly", "icon", "IconContainerComponent", "max", "name", "onChange", "onChangeActive", "onMouseLeave", "onMouseMove", "precision", "readOnly", "size", "value"]); var name = useId(nameProp); var _useControlled = useControlled({ controlled: valueProp, default: defaultValue, name: 'Rating' }), _useControlled2 = _slicedToArray(_useControlled, 2), valueDerived = _useControlled2[0], setValueState = _useControlled2[1]; var valueRounded = roundValueToPrecision(valueDerived, precision); var isRtl = useRtl(); var _React$useState = React.useState({ hover: -1, focus: -1 }), _React$useState$ = _React$useState[0], hover = _React$useState$.hover, focus = _React$useState$.focus, setState = _React$useState[1]; var value = valueRounded; if (hover !== -1) { value = hover; } if (focus !== -1) { value = focus; } var _useIsFocusVisible = useIsFocusVisible(), isFocusVisibleRef = _useIsFocusVisible.isFocusVisibleRef, handleBlurVisible = _useIsFocusVisible.onBlur, handleFocusVisible = _useIsFocusVisible.onFocus, focusVisibleRef = _useIsFocusVisible.ref; var _React$useState2 = React.useState(false), focusVisible = _React$useState2[0], setFocusVisible = _React$useState2[1]; var rootRef = React.useRef(); var handleRef = useForkRef(focusVisibleRef, rootRef, ref); var handleMouseMove = function handleMouseMove(event) { if (onMouseMove) { onMouseMove(event); } var rootNode = rootRef.current; var _rootNode$getBounding = rootNode.getBoundingClientRect(), right = _rootNode$getBounding.right, left = _rootNode$getBounding.left, containerWidth = _rootNode$getBounding.width; var percent; if (isRtl) { percent = (right - event.clientX) / containerWidth; } else { percent = (event.clientX - left) / containerWidth; } var newHover = roundValueToPrecision(max * percent + precision / 2, precision); newHover = clamp(newHover, precision, max); setState(function (prev) { return prev.hover === newHover && prev.focus === newHover ? prev : { hover: newHover, focus: newHover }; }); setFocusVisible(false); if (onChangeActive && hover !== newHover) { onChangeActive(event, newHover); } }; var handleMouseLeave = function handleMouseLeave(event) { if (onMouseLeave) { onMouseLeave(event); } var newHover = -1; setState({ hover: newHover, focus: newHover }); if (onChangeActive && hover !== newHover) { onChangeActive(event, newHover); } }; var handleChange = function handleChange(event) { var newValue = event.target.value === '' ? null : parseFloat(event.target.value); // Give mouse priority over keyboard // Fix https://github.com/mui/material-ui/issues/22827 if (hover !== -1) { newValue = hover; } setValueState(newValue); if (onChange) { onChange(event, newValue); } }; var handleClear = function handleClear(event) { // Ignore keyboard events // https://github.com/facebook/react/issues/7407 if (event.clientX === 0 && event.clientY === 0) { return; } setState({ hover: -1, focus: -1 }); setValueState(null); if (onChange && parseFloat(event.target.value) === valueRounded) { onChange(event, null); } }; var handleFocus = function handleFocus(event) { handleFocusVisible(event); if (isFocusVisibleRef.current === true) { setFocusVisible(true); } var newFocus = parseFloat(event.target.value); setState(function (prev) { return { hover: prev.hover, focus: newFocus }; }); }; var handleBlur = function handleBlur(event) { if (hover !== -1) { return; } handleBlurVisible(event); if (isFocusVisibleRef.current === false) { setFocusVisible(false); } var newFocus = -1; setState(function (prev) { return { hover: prev.hover, focus: newFocus }; }); }; var _React$useState3 = React.useState(false), emptyValueFocused = _React$useState3[0], setEmptyValueFocused = _React$useState3[1]; var ownerState = _extends({}, props, { defaultValue: defaultValue, disabled: disabled, emptyIcon: emptyIcon, emptyLabelText: emptyLabelText, emptyValueFocused: emptyValueFocused, focusVisible: focusVisible, getLabelText: getLabelText, icon: icon, IconContainerComponent: IconContainerComponent, max: max, precision: precision, readOnly: readOnly, size: size }); var classes = useUtilityClasses(ownerState); return /*#__PURE__*/_jsxs(RatingRoot, _extends({ ref: handleRef, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: clsx(classes.root, className, readOnly && 'MuiRating-readOnly'), ownerState: ownerState, role: readOnly ? 'img' : null, "aria-label": readOnly ? getLabelText(value) : null }, other, { children: [Array.from(new Array(max)).map(function (_, index) { var itemValue = index + 1; var ratingItemProps = { classes: classes, disabled: disabled, emptyIcon: emptyIcon, focus: focus, getLabelText: getLabelText, highlightSelectedOnly: highlightSelectedOnly, hover: hover, icon: icon, IconContainerComponent: IconContainerComponent, name: name, onBlur: handleBlur, onChange: handleChange, onClick: handleClear, onFocus: handleFocus, ratingValue: value, ratingValueRounded: valueRounded, readOnly: readOnly, ownerState: ownerState }; var isActive = itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1); if (precision < 1) { var items = Array.from(new Array(1 / precision)); return /*#__PURE__*/_jsx(RatingDecimal, { className: clsx(classes.decimal, isActive && classes.iconActive), ownerState: ownerState, iconActive: isActive, children: items.map(function ($, indexDecimal) { var itemDecimalValue = roundValueToPrecision(itemValue - 1 + (indexDecimal + 1) * precision, precision); return /*#__PURE__*/_jsx(RatingItem, _extends({}, ratingItemProps, { // The icon is already displayed as active isActive: false, itemValue: itemDecimalValue, labelProps: { style: items.length - 1 === indexDecimal ? {} : { width: itemDecimalValue === value ? "".concat((indexDecimal + 1) * precision * 100, "%") : '0%', overflow: 'hidden', position: 'absolute' } } }), itemDecimalValue); }) }, itemValue); } return /*#__PURE__*/_jsx(RatingItem, _extends({}, ratingItemProps, { isActive: isActive, itemValue: itemValue }), itemValue); }), !readOnly && !disabled && /*#__PURE__*/_jsxs(RatingLabel, { className: clsx(classes.label, classes.labelEmptyValue), ownerState: ownerState, children: [/*#__PURE__*/_jsx("input", { className: classes.visuallyHidden, value: "", id: "".concat(name, "-empty"), type: "radio", name: name, checked: valueRounded == null, onFocus: function onFocus() { return setEmptyValueFocused(true); }, onBlur: function onBlur() { return setEmptyValueFocused(false); }, onChange: handleChange }), /*#__PURE__*/_jsx("span", { className: classes.visuallyHidden, children: emptyLabelText })] })] })); }); process.env.NODE_ENV !== "production" ? Rating.propTypes /* remove-proptypes */ = { // ┌────────────────────────────── Warning ──────────────────────────────┐ // │ These PropTypes are generated from the TypeScript type definitions. │ // │ To update them, edit the d.ts file and run `pnpm proptypes`. │ // └─────────────────────────────────────────────────────────────────────┘ /** * Override or extend the styles applied to the component. */ classes: PropTypes.object, /** * @ignore */ className: PropTypes.string, /** * The default value. Use when the component is not controlled. * @default null */ defaultValue: PropTypes.number, /** * If `true`, the component is disabled. * @default false */ disabled: PropTypes.bool, /** * The icon to display when empty. * @default */ emptyIcon: PropTypes.node, /** * The label read when the rating input is empty. * @default 'Empty' */ emptyLabelText: PropTypes.node, /** * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating. * This is important for screen reader users. * * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). * @param {number} value The rating label's value to format. * @returns {string} * @default function defaultLabelText(value) { * return `${value} Star${value !== 1 ? 's' : ''}`; * } */ getLabelText: PropTypes.func, /** * If `true`, only the selected icon will be highlighted. * @default false */ highlightSelectedOnly: PropTypes.bool, /** * The icon to display. * @default */ icon: PropTypes.node, /** * The component containing the icon. * @default function IconContainer(props) { * const { value, ...other } = props; * return ; * } */ IconContainerComponent: PropTypes.elementType, /** * Maximum rating. * @default 5 */ max: PropTypes.number, /** * The name attribute of the radio `input` elements. * This input `name` should be unique within the page. * Being unique within a form is insufficient since the `name` is used to generated IDs. */ name: PropTypes.string, /** * Callback fired when the value changes. * @param {React.SyntheticEvent} event The event source of the callback. * @param {number|null} value The new value. */ onChange: PropTypes.func, /** * Callback function that is fired when the hover state changes. * @param {React.SyntheticEvent} event The event source of the callback. * @param {number} value The new value. */ onChangeActive: PropTypes.func, /** * @ignore */ onMouseLeave: PropTypes.func, /** * @ignore */ onMouseMove: PropTypes.func, /** * The minimum increment value change allowed. * @default 1 */ precision: chainPropTypes(PropTypes.number, function (props) { if (props.precision < 0.1) { return new Error(['MUI: The prop `precision` should be above 0.1.', 'A value below this limit has an imperceptible impact.'].join('\n')); } return null; }), /** * Removes all hover effects and pointer events. * @default false */ readOnly: PropTypes.bool, /** * The size of the component. * @default 'medium' */ size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['small', 'medium', 'large']), PropTypes.string]), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]), /** * The rating value. */ value: PropTypes.number } : void 0; export default Rating;