UNPKG

@mui/material

Version:

Quickly build beautiful React apps. MUI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design.

740 lines (654 loc) 22.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _clsx = _interopRequireDefault(require("clsx")); var _utils = require("@mui/utils"); var _base = require("@mui/base"); var _useTheme = _interopRequireDefault(require("../styles/useTheme")); var _utils2 = require("../utils"); var _Star = _interopRequireDefault(require("../internal/svg-icons/Star")); var _StarBorder = _interopRequireDefault(require("../internal/svg-icons/StarBorder")); var _useThemeProps = _interopRequireDefault(require("../styles/useThemeProps")); var _styled = _interopRequireWildcard(require("../styles/styled")); var _ratingClasses = _interopRequireWildcard(require("./ratingClasses")); var _jsxRuntime = require("react/jsx-runtime"); const _excluded = ["value"], _excluded2 = ["className", "defaultValue", "disabled", "emptyIcon", "emptyLabelText", "getLabelText", "highlightSelectedOnly", "icon", "IconContainerComponent", "max", "name", "onChange", "onChangeActive", "onMouseLeave", "onMouseMove", "precision", "readOnly", "size", "value"]; function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function clamp(value, min, max) { if (value < min) { return min; } if (value > max) { return max; } return value; } function getDecimalPrecision(num) { const decimalPart = num.toString().split('.')[1]; return decimalPart ? decimalPart.length : 0; } function roundValueToPrecision(value, precision) { if (value == null) { return value; } const nearest = Math.round(value / precision) * precision; return Number(nearest.toFixed(getDecimalPrecision(precision))); } const useUtilityClasses = ownerState => { const { classes, size, readOnly, disabled, emptyValueFocused, focusVisible } = ownerState; const slots = { root: ['root', `size${(0, _utils2.capitalize)(size)}`, disabled && 'disabled', focusVisible && 'focusVisible', readOnly && 'readyOnly'], label: ['label', 'pristine'], labelEmptyValue: [emptyValueFocused && 'labelEmptyValueActive'], icon: ['icon'], iconEmpty: ['iconEmpty'], iconFilled: ['iconFilled'], iconHover: ['iconHover'], iconFocus: ['iconFocus'], iconActive: ['iconActive'], decimal: ['decimal'], visuallyHidden: ['visuallyHidden'] }; return (0, _base.unstable_composeClasses)(slots, _ratingClasses.getRatingUtilityClass, classes); }; const RatingRoot = (0, _styled.default)('span', { name: 'MuiRating', slot: 'Root', overridesResolver: (props, styles) => { const { ownerState } = props; return [{ [`& .${_ratingClasses.default.visuallyHidden}`]: styles.visuallyHidden }, styles.root, styles[`size${(0, _utils2.capitalize)(ownerState.size)}`], ownerState.readOnly && styles.readOnly]; } })(({ theme, ownerState }) => (0, _extends2.default)({ display: 'inline-flex', // Required to position the pristine input absolutely position: 'relative', fontSize: theme.typography.pxToRem(24), color: '#faaf00', cursor: 'pointer', textAlign: 'left', WebkitTapHighlightColor: 'transparent', [`&.${_ratingClasses.default.disabled}`]: { opacity: theme.palette.action.disabledOpacity, pointerEvents: 'none' }, [`&.${_ratingClasses.default.focusVisible} .${_ratingClasses.default.iconActive}`]: { outline: '1px solid #999' }, [`& .${_ratingClasses.default.visuallyHidden}`]: _utils.visuallyHidden }, ownerState.size === 'small' && { fontSize: theme.typography.pxToRem(18) }, ownerState.size === 'large' && { fontSize: theme.typography.pxToRem(30) }, ownerState.readOnly && { pointerEvents: 'none' })); const RatingLabel = (0, _styled.default)('label', { name: 'MuiRating', slot: 'Label', overridesResolver: (props, styles) => styles.label })(({ ownerState }) => (0, _extends2.default)({ cursor: 'inherit' }, ownerState.emptyValueFocused && { top: 0, bottom: 0, position: 'absolute', outline: '1px solid #999', width: '100%' })); const RatingIcon = (0, _styled.default)('span', { name: 'MuiRating', slot: 'Icon', overridesResolver: (props, styles) => { const { ownerState } = props; return [styles.icon, ownerState.iconEmpty && styles.iconEmpty, ownerState.iconFilled && styles.iconFilled, ownerState.iconHover && styles.iconHover, ownerState.iconFocus && styles.iconFocus, ownerState.iconActive && styles.iconActive]; } })(({ theme, ownerState }) => (0, _extends2.default)({ // 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.palette.action.disabled })); const RatingDecimal = (0, _styled.default)('span', { name: 'MuiRating', slot: 'Decimal', shouldForwardProp: prop => (0, _styled.slotShouldForwardProp)(prop) && prop !== 'iconActive', overridesResolver: (props, styles) => { const { iconActive } = props; return [styles.decimal, iconActive && styles.iconActive]; } })(({ iconActive }) => (0, _extends2.default)({ position: 'relative' }, iconActive && { transform: 'scale(1.2)' })); function IconContainer(props) { const other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", (0, _extends2.default)({}, other)); } process.env.NODE_ENV !== "production" ? IconContainer.propTypes = { value: _propTypes.default.number.isRequired } : void 0; function RatingItem(props) { const { classes, disabled, emptyIcon, focus, getLabelText, highlightSelectedOnly, hover, icon, IconContainerComponent, isActive, itemValue, labelProps, name, onBlur, onChange, onClick, onFocus, readOnly, ownerState, ratingValue, ratingValueRounded } = props; const isFilled = highlightSelectedOnly ? itemValue === ratingValue : itemValue <= ratingValue; const isHovered = itemValue <= hover; const isFocused = itemValue <= focus; const isChecked = itemValue === ratingValueRounded; const id = (0, _utils2.unstable_useId)(); const container = /*#__PURE__*/(0, _jsxRuntime.jsx)(RatingIcon, { as: IconContainerComponent, value: itemValue, className: (0, _clsx.default)(classes.icon, isFilled ? classes.iconFilled : classes.iconEmpty, isHovered && classes.iconHover, isFocused && classes.iconFocus, isActive && classes.iconActive), ownerState: (0, _extends2.default)({}, ownerState, { iconEmpty: !isFilled, iconFilled: isFilled, iconHover: isHovered, iconFocus: isFocused, iconActive: isActive }), children: emptyIcon && !isFilled ? emptyIcon : icon }); if (readOnly) { return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", (0, _extends2.default)({}, labelProps, { children: container })); } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(RatingLabel, (0, _extends2.default)({ ownerState: (0, _extends2.default)({}, ownerState, { emptyValueFocused: undefined }), htmlFor: id }, labelProps, { children: [container, /*#__PURE__*/(0, _jsxRuntime.jsx)("span", { className: classes.visuallyHidden, children: getLabelText(itemValue) })] })), /*#__PURE__*/(0, _jsxRuntime.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.default.object.isRequired, disabled: _propTypes.default.bool.isRequired, emptyIcon: _propTypes.default.node, focus: _propTypes.default.number.isRequired, getLabelText: _propTypes.default.func.isRequired, highlightSelectedOnly: _propTypes.default.bool.isRequired, hover: _propTypes.default.number.isRequired, icon: _propTypes.default.node, IconContainerComponent: _propTypes.default.elementType.isRequired, isActive: _propTypes.default.bool.isRequired, itemValue: _propTypes.default.number.isRequired, labelProps: _propTypes.default.object, name: _propTypes.default.string, onBlur: _propTypes.default.func.isRequired, onChange: _propTypes.default.func.isRequired, onClick: _propTypes.default.func.isRequired, onFocus: _propTypes.default.func.isRequired, ownerState: _propTypes.default.object.isRequired, ratingValue: _propTypes.default.number, ratingValueRounded: _propTypes.default.number, readOnly: _propTypes.default.bool.isRequired } : void 0; const defaultIcon = /*#__PURE__*/(0, _jsxRuntime.jsx)(_Star.default, { fontSize: "inherit" }); const defaultEmptyIcon = /*#__PURE__*/(0, _jsxRuntime.jsx)(_StarBorder.default, { fontSize: "inherit" }); function defaultLabelText(value) { return `${value} Star${value !== 1 ? 's' : ''}`; } const Rating = /*#__PURE__*/React.forwardRef(function Rating(inProps, ref) { const props = (0, _useThemeProps.default)({ name: 'MuiRating', props: inProps }); const { className, defaultValue = null, disabled = false, emptyIcon = defaultEmptyIcon, emptyLabelText = 'Empty', getLabelText = defaultLabelText, highlightSelectedOnly = false, icon = defaultIcon, IconContainerComponent = IconContainer, max = 5, name: nameProp, onChange, onChangeActive, onMouseLeave, onMouseMove, precision = 1, readOnly = false, size = 'medium', value: valueProp } = props, other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded2); const name = (0, _utils2.unstable_useId)(nameProp); const [valueDerived, setValueState] = (0, _utils2.useControlled)({ controlled: valueProp, default: defaultValue, name: 'Rating' }); const valueRounded = roundValueToPrecision(valueDerived, precision); const theme = (0, _useTheme.default)(); const [{ hover, focus }, setState] = React.useState({ hover: -1, focus: -1 }); let value = valueRounded; if (hover !== -1) { value = hover; } if (focus !== -1) { value = focus; } const { isFocusVisibleRef, onBlur: handleBlurVisible, onFocus: handleFocusVisible, ref: focusVisibleRef } = (0, _utils2.useIsFocusVisible)(); const [focusVisible, setFocusVisible] = React.useState(false); const rootRef = React.useRef(); const handleFocusRef = (0, _utils2.useForkRef)(focusVisibleRef, rootRef); const handleRef = (0, _utils2.useForkRef)(handleFocusRef, ref); const handleMouseMove = event => { if (onMouseMove) { onMouseMove(event); } const rootNode = rootRef.current; const { right, left } = rootNode.getBoundingClientRect(); const { width } = rootNode.firstChild.getBoundingClientRect(); let percent; if (theme.direction === 'rtl') { percent = (right - event.clientX) / (width * max); } else { percent = (event.clientX - left) / (width * max); } let newHover = roundValueToPrecision(max * percent + precision / 2, precision); newHover = clamp(newHover, precision, max); setState(prev => prev.hover === newHover && prev.focus === newHover ? prev : { hover: newHover, focus: newHover }); setFocusVisible(false); if (onChangeActive && hover !== newHover) { onChangeActive(event, newHover); } }; const handleMouseLeave = event => { if (onMouseLeave) { onMouseLeave(event); } const newHover = -1; setState({ hover: newHover, focus: newHover }); if (onChangeActive && hover !== newHover) { onChangeActive(event, newHover); } }; const handleChange = event => { let newValue = event.target.value === '' ? null : parseFloat(event.target.value); // Give mouse priority over keyboard // Fix https://github.com/mui-org/material-ui/issues/22827 if (hover !== -1) { newValue = hover; } setValueState(newValue); if (onChange) { onChange(event, newValue); } }; const 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); } }; const handleFocus = event => { handleFocusVisible(event); if (isFocusVisibleRef.current === true) { setFocusVisible(true); } const newFocus = parseFloat(event.target.value); setState(prev => ({ hover: prev.hover, focus: newFocus })); }; const handleBlur = event => { if (hover !== -1) { return; } handleBlurVisible(event); if (isFocusVisibleRef.current === false) { setFocusVisible(false); } const newFocus = -1; setState(prev => ({ hover: prev.hover, focus: newFocus })); }; const [emptyValueFocused, setEmptyValueFocused] = React.useState(false); const ownerState = (0, _extends2.default)({}, props, { defaultValue, disabled, emptyIcon, emptyLabelText, emptyValueFocused, focusVisible, getLabelText, icon, IconContainerComponent, max, precision, readOnly, size }); const classes = useUtilityClasses(ownerState); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(RatingRoot, (0, _extends2.default)({ ref: handleRef, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: (0, _clsx.default)(classes.root, className), ownerState: ownerState, role: readOnly ? 'img' : null, "aria-label": readOnly ? getLabelText(value) : null }, other, { children: [Array.from(new Array(max)).map((_, index) => { const itemValue = index + 1; const ratingItemProps = { classes, disabled, emptyIcon, focus, getLabelText, highlightSelectedOnly, hover, icon, IconContainerComponent, name, onBlur: handleBlur, onChange: handleChange, onClick: handleClear, onFocus: handleFocus, ratingValue: value, ratingValueRounded: valueRounded, readOnly, ownerState }; const isActive = itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1); if (precision < 1) { const items = Array.from(new Array(1 / precision)); return /*#__PURE__*/(0, _jsxRuntime.jsx)(RatingDecimal, { className: (0, _clsx.default)(classes.decimal, isActive && classes.iconActive), ownerState: ownerState, iconActive: isActive, children: items.map(($, indexDecimal) => { const itemDecimalValue = roundValueToPrecision(itemValue - 1 + (indexDecimal + 1) * precision, precision); return /*#__PURE__*/(0, _jsxRuntime.jsx)(RatingItem, (0, _extends2.default)({}, ratingItemProps, { // The icon is already displayed as active isActive: false, itemValue: itemDecimalValue, labelProps: { style: items.length - 1 === indexDecimal ? {} : { width: itemDecimalValue === value ? `${(indexDecimal + 1) * precision * 100}%` : '0%', overflow: 'hidden', position: 'absolute' } } }), itemDecimalValue); }) }, itemValue); } return /*#__PURE__*/(0, _jsxRuntime.jsx)(RatingItem, (0, _extends2.default)({}, ratingItemProps, { isActive: isActive, itemValue: itemValue }), itemValue); }), !readOnly && !disabled && /*#__PURE__*/(0, _jsxRuntime.jsxs)(RatingLabel, { className: (0, _clsx.default)(classes.label, classes.labelEmptyValue), ownerState: ownerState, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("input", { className: classes.visuallyHidden, value: "", id: `${name}-empty`, type: "radio", name: name, checked: valueRounded == null, onFocus: () => setEmptyValueFocused(true), onBlur: () => setEmptyValueFocused(false), onChange: handleChange }), /*#__PURE__*/(0, _jsxRuntime.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 "yarn proptypes" | // ---------------------------------------------------------------------- /** * Override or extend the styles applied to the component. */ classes: _propTypes.default.object, /** * @ignore */ className: _propTypes.default.string, /** * The default value. Use when the component is not controlled. * @default null */ defaultValue: _propTypes.default.number, /** * If `true`, the component is disabled. * @default false */ disabled: _propTypes.default.bool, /** * The icon to display when empty. * @default <StarBorder fontSize="inherit" /> */ emptyIcon: _propTypes.default.node, /** * The label read when the rating input is empty. * @default 'Empty' */ emptyLabelText: _propTypes.default.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](/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.default.func, /** * If `true`, only the selected icon will be highlighted. * @default false */ highlightSelectedOnly: _propTypes.default.bool, /** * The icon to display. * @default <Star fontSize="inherit" /> */ icon: _propTypes.default.node, /** * The component containing the icon. * @default function IconContainer(props) { * const { value, ...other } = props; * return <span {...other} />; * } */ IconContainerComponent: _propTypes.default.elementType, /** * Maximum rating. * @default 5 */ max: _propTypes.default.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.default.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.default.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.default.func, /** * @ignore */ onMouseLeave: _propTypes.default.func, /** * @ignore */ onMouseMove: _propTypes.default.func, /** * The minimum increment value change allowed. * @default 1 */ precision: (0, _utils.chainPropTypes)(_propTypes.default.number, 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.default.bool, /** * The size of the component. * @default 'medium' */ size: _propTypes.default /* @typescript-to-proptypes-ignore */ .oneOfType([_propTypes.default.oneOf(['small', 'medium', 'large']), _propTypes.default.string]), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]), /** * The rating value. */ value: _propTypes.default.number } : void 0; var _default = Rating; exports.default = _default;