@material-ui/lab
Version:
Material-UI Lab - Incubator for Material-UI React components.
606 lines (505 loc) • 17.9 kB
JavaScript
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.styles = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _clsx = _interopRequireDefault(require("clsx"));
var _utils = require("@material-ui/utils");
var _styles = require("@material-ui/core/styles");
var _utils2 = require("@material-ui/core/utils");
var _Star = _interopRequireDefault(require("../internal/svg-icons/Star"));
function clamp(value, min, max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
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 styles = function styles(theme) {
return {
/* Styles applied to the root element. */
root: {
display: 'inline-flex',
position: 'relative',
fontSize: theme.typography.pxToRem(24),
color: '#ffb400',
cursor: 'pointer',
textAlign: 'left',
WebkitTapHighlightColor: 'transparent',
'&$disabled': {
opacity: 0.5,
pointerEvents: 'none'
},
'&$focusVisible $iconActive': {
outline: '1px solid #999'
}
},
/* Styles applied to the root element if `size="small"`. */
sizeSmall: {
fontSize: theme.typography.pxToRem(18)
},
/* Styles applied to the root element if `size="large"`. */
sizeLarge: {
fontSize: theme.typography.pxToRem(30)
},
/* Styles applied to the root element if `readOnly={true}`. */
readOnly: {
pointerEvents: 'none'
},
/* Pseudo-class applied to the root element if `disabled={true}`. */
disabled: {},
/* Pseudo-class applied to the root element if keyboard focused. */
focusVisible: {},
/* Visually hide an element. */
visuallyhidden: {
border: 0,
clip: 'rect(0 0 0 0)',
height: 1,
margin: -1,
color: '#000',
overflow: 'hidden',
padding: 0,
position: 'absolute',
top: 20,
width: 1
},
/* Styles applied to the pristine label. */
pristine: {
'input:focus + &': {
top: 0,
bottom: 0,
position: 'absolute',
outline: '1px solid #999',
width: '100%'
}
},
/* Styles applied to the label elements. */
label: {
cursor: 'inherit'
},
/* Styles applied to the icon wrapping elements. */
icon: {
display: 'flex',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest
}),
// Fix mouseLeave issue.
// https://github.com/facebook/react/issues/4492
pointerEvents: 'none'
},
/* Styles applied to the icon wrapping elements when empty. */
iconEmpty: {
color: theme.palette.action.disabled
},
/* Styles applied to the icon wrapping elements when filled. */
iconFilled: {},
/* Styles applied to the icon wrapping elements when hover. */
iconHover: {},
/* Styles applied to the icon wrapping elements when focus. */
iconFocus: {},
/* Styles applied to the icon wrapping elements when active. */
iconActive: {
transform: 'scale(1.2)'
},
/* Styles applied to the icon wrapping elements when decimals are necessary. */
decimal: {
position: 'relative'
}
};
};
exports.styles = styles;
function IconContainer(props) {
var value = props.value,
other = (0, _objectWithoutProperties2.default)(props, ["value"]);
return /*#__PURE__*/React.createElement("span", other);
}
process.env.NODE_ENV !== "production" ? IconContainer.propTypes = {
value: _propTypes.default.number.isRequired
} : void 0;
var defaultIcon = /*#__PURE__*/React.createElement(_Star.default, {
fontSize: "inherit"
});
function defaultLabelText(value) {
return "".concat(value, " Star").concat(value !== 1 ? 's' : '');
}
var Rating = /*#__PURE__*/React.forwardRef(function Rating(props, ref) {
var classes = props.classes,
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,
emptyIcon = 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$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 = (0, _objectWithoutProperties2.default)(props, ["classes", "className", "defaultValue", "disabled", "emptyIcon", "emptyLabelText", "getLabelText", "icon", "IconContainerComponent", "max", "name", "onChange", "onChangeActive", "onMouseLeave", "onMouseMove", "precision", "readOnly", "size", "value"]);
var name = (0, _utils2.unstable_useId)(nameProp);
var _useControlled = (0, _utils2.useControlled)({
controlled: valueProp,
default: defaultValue,
name: 'Rating'
}),
_useControlled2 = (0, _slicedToArray2.default)(_useControlled, 2),
valueDerived = _useControlled2[0],
setValueState = _useControlled2[1];
var valueRounded = roundValueToPrecision(valueDerived, precision);
var theme = (0, _styles.useTheme)();
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 = (0, _utils2.useIsFocusVisible)(),
isFocusVisible = _useIsFocusVisible.isFocusVisible,
onBlurVisible = _useIsFocusVisible.onBlurVisible,
focusVisibleRef = _useIsFocusVisible.ref;
var _React$useState2 = React.useState(false),
focusVisible = _React$useState2[0],
setFocusVisible = _React$useState2[1];
var rootRef = React.useRef();
var handleFocusRef = (0, _utils2.useForkRef)(focusVisibleRef, rootRef);
var handleRef = (0, _utils2.useForkRef)(handleFocusRef, 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;
var _rootNode$firstChild$ = rootNode.firstChild.getBoundingClientRect(),
width = _rootNode$firstChild$.width;
var percent;
if (theme.direction === 'rtl') {
percent = (right - event.clientX) / (width * max);
} else {
percent = (event.clientX - left) / (width * max);
}
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 = parseFloat(event.target.value);
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) {
if (isFocusVisible(event)) {
setFocusVisible(true);
}
var newFocus = parseFloat(event.target.value);
setState(function (prev) {
return {
hover: prev.hover,
focus: newFocus
};
});
if (onChangeActive && focus !== newFocus) {
onChangeActive(event, newFocus);
}
};
var handleBlur = function handleBlur(event) {
if (hover !== -1) {
return;
}
if (focusVisible !== false) {
setFocusVisible(false);
onBlurVisible();
}
var newFocus = -1;
setState(function (prev) {
return {
hover: prev.hover,
focus: newFocus
};
});
if (onChangeActive && focus !== newFocus) {
onChangeActive(event, newFocus);
}
};
var item = function item(state, labelProps) {
var id = "".concat(name, "-").concat(String(state.value).replace('.', '-'));
var container = /*#__PURE__*/React.createElement(IconContainerComponent, {
value: state.value,
className: (0, _clsx.default)(classes.icon, state.filled ? classes.iconFilled : classes.iconEmpty, state.hover && classes.iconHover, state.focus && classes.iconFocus, state.active && classes.iconActive)
}, emptyIcon && !state.filled ? emptyIcon : icon);
if (readOnly) {
return /*#__PURE__*/React.createElement("span", (0, _extends2.default)({
key: state.value
}, labelProps), container);
}
return /*#__PURE__*/React.createElement(React.Fragment, {
key: state.value
}, /*#__PURE__*/React.createElement("label", (0, _extends2.default)({
className: classes.label,
htmlFor: id
}, labelProps), container, /*#__PURE__*/React.createElement("span", {
className: classes.visuallyhidden
}, getLabelText(state.value))), /*#__PURE__*/React.createElement("input", {
onFocus: handleFocus,
onBlur: handleBlur,
onChange: handleChange,
onClick: handleClear,
disabled: disabled,
value: state.value,
id: id,
type: "radio",
name: name,
checked: state.checked,
className: classes.visuallyhidden
}));
};
return /*#__PURE__*/React.createElement("span", (0, _extends2.default)({
ref: handleRef,
onMouseMove: handleMouseMove,
onMouseLeave: handleMouseLeave,
className: (0, _clsx.default)(classes.root, className, size !== 'medium' && classes["size".concat((0, _utils2.capitalize)(size))], disabled && classes.disabled, focusVisible && classes.focusVisible, readOnly && classes.readOnly),
role: readOnly ? 'img' : null,
"aria-label": readOnly ? getLabelText(value) : null
}, other), Array.from(new Array(max)).map(function (_, index) {
var itemValue = index + 1;
if (precision < 1) {
var items = Array.from(new Array(1 / precision));
return /*#__PURE__*/React.createElement("span", {
key: itemValue,
className: (0, _clsx.default)(classes.decimal, itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1) && classes.iconActive)
}, items.map(function ($, indexDecimal) {
var itemDecimalValue = roundValueToPrecision(itemValue - 1 + (indexDecimal + 1) * precision, precision);
return item({
value: itemDecimalValue,
filled: itemDecimalValue <= value,
hover: itemDecimalValue <= hover,
focus: itemDecimalValue <= focus,
checked: itemDecimalValue === valueRounded
}, {
style: items.length - 1 === indexDecimal ? {} : {
width: itemDecimalValue === value ? "".concat((indexDecimal + 1) * precision * 100, "%") : '0%',
overflow: 'hidden',
zIndex: 1,
position: 'absolute'
}
});
}));
}
return item({
value: itemValue,
active: itemValue === value && (hover !== -1 || focus !== -1),
filled: itemValue <= value,
hover: itemValue <= hover,
focus: itemValue <= focus,
checked: itemValue === valueRounded
});
}), !readOnly && !disabled && valueRounded == null && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("input", {
value: "",
id: "".concat(name, "-empty"),
type: "radio",
name: name,
defaultChecked: true,
className: classes.visuallyhidden
}), /*#__PURE__*/React.createElement("label", {
className: classes.pristine,
htmlFor: "".concat(name, "-empty")
}, /*#__PURE__*/React.createElement("span", {
className: classes.visuallyhidden
}, emptyLabelText))));
});
process.env.NODE_ENV !== "production" ? Rating.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.
* See [CSS API](#css) below for more details.
*/
classes: _propTypes.default.object,
/**
* @ignore
*/
className: _propTypes.default.string,
/**
* The default value. Use when the component is not controlled.
*/
defaultValue: _propTypes.default.number,
/**
* If `true`, the rating will be disabled.
*/
disabled: _propTypes.default.bool,
/**
* The icon to display when empty.
*/
emptyIcon: _propTypes.default.node,
/**
* The label read when the rating input is 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.
*
* For localization purposes, you can use the provided [translations](/guides/localization/).
*
* @param {number} value The rating label's value to format.
* @returns {string}
*/
getLabelText: _propTypes.default.func,
/**
* The icon to display.
*/
icon: _propTypes.default.node,
/**
* The component containing the icon.
*/
IconContainerComponent: _propTypes.default.elementType,
/**
* Maximum rating.
*/
max: _propTypes.default.number,
/**
* The name attribute of the radio `input` elements.
* If `readOnly` is false, the prop is required,
* this input name`should be unique within the parent form.
*/
name: (0, _utils.chainPropTypes)(_propTypes.default.string, function (props) {
if (!props.readOnly && !props.name) {
return new Error(['Material-UI: The prop `name` is required (when `readOnly` is false).', 'Additionally, the input name should be unique within the parent form.'].join('\n'));
}
return null;
}),
/**
* Callback fired when the value changes.
*
* @param {object} event The event source of the callback.
* @param {number} value The new value.
*/
onChange: _propTypes.default.func,
/**
* Callback function that is fired when the hover state changes.
*
* @param {object} 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.
*/
precision: (0, _utils.chainPropTypes)(_propTypes.default.number, function (props) {
if (props.precision < 0.1) {
return new Error(['Material-UI: 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.
*/
readOnly: _propTypes.default.bool,
/**
* The size of the rating.
*/
size: _propTypes.default.oneOf(['large', 'medium', 'small']),
/**
* The rating value.
*/
value: _propTypes.default.number
} : void 0;
var _default = (0, _styles.withStyles)(styles, {
name: 'MuiRating'
})(Rating);
exports.default = _default;