UNPKG

17.9 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
7Object.defineProperty(exports, "__esModule", {
8 value: true
9});
10exports.default = exports.styles = void 0;
11
12var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
13
14var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
15
16var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
17
18var React = _interopRequireWildcard(require("react"));
19
20var _propTypes = _interopRequireDefault(require("prop-types"));
21
22var _clsx = _interopRequireDefault(require("clsx"));
23
24var _utils = require("@material-ui/utils");
25
26var _styles = require("@material-ui/core/styles");
27
28var _utils2 = require("@material-ui/core/utils");
29
30var _Star = _interopRequireDefault(require("../internal/svg-icons/Star"));
31
32function clamp(value, min, max) {
33 if (value < min) {
34 return min;
35 }
36
37 if (value > max) {
38 return max;
39 }
40
41 return value;
42}
43
44function getDecimalPrecision(num) {
45 var decimalPart = num.toString().split('.')[1];
46 return decimalPart ? decimalPart.length : 0;
47}
48
49function roundValueToPrecision(value, precision) {
50 if (value == null) {
51 return value;
52 }
53
54 var nearest = Math.round(value / precision) * precision;
55 return Number(nearest.toFixed(getDecimalPrecision(precision)));
56}
57
58var styles = function styles(theme) {
59 return {
60 /* Styles applied to the root element. */
61 root: {
62 display: 'inline-flex',
63 position: 'relative',
64 fontSize: theme.typography.pxToRem(24),
65 color: '#ffb400',
66 cursor: 'pointer',
67 textAlign: 'left',
68 WebkitTapHighlightColor: 'transparent',
69 '&$disabled': {
70 opacity: 0.5,
71 pointerEvents: 'none'
72 },
73 '&$focusVisible $iconActive': {
74 outline: '1px solid #999'
75 }
76 },
77
78 /* Styles applied to the root element if `size="small"`. */
79 sizeSmall: {
80 fontSize: theme.typography.pxToRem(18)
81 },
82
83 /* Styles applied to the root element if `size="large"`. */
84 sizeLarge: {
85 fontSize: theme.typography.pxToRem(30)
86 },
87
88 /* Styles applied to the root element if `readOnly={true}`. */
89 readOnly: {
90 pointerEvents: 'none'
91 },
92
93 /* Pseudo-class applied to the root element if `disabled={true}`. */
94 disabled: {},
95
96 /* Pseudo-class applied to the root element if keyboard focused. */
97 focusVisible: {},
98
99 /* Visually hide an element. */
100 visuallyhidden: {
101 border: 0,
102 clip: 'rect(0 0 0 0)',
103 height: 1,
104 margin: -1,
105 color: '#000',
106 overflow: 'hidden',
107 padding: 0,
108 position: 'absolute',
109 top: 20,
110 width: 1
111 },
112
113 /* Styles applied to the pristine label. */
114 pristine: {
115 'input:focus + &': {
116 top: 0,
117 bottom: 0,
118 position: 'absolute',
119 outline: '1px solid #999',
120 width: '100%'
121 }
122 },
123
124 /* Styles applied to the label elements. */
125 label: {
126 cursor: 'inherit'
127 },
128
129 /* Styles applied to the icon wrapping elements. */
130 icon: {
131 display: 'flex',
132 transition: theme.transitions.create('transform', {
133 duration: theme.transitions.duration.shortest
134 }),
135 // Fix mouseLeave issue.
136 // https://github.com/facebook/react/issues/4492
137 pointerEvents: 'none'
138 },
139
140 /* Styles applied to the icon wrapping elements when empty. */
141 iconEmpty: {
142 color: theme.palette.action.disabled
143 },
144
145 /* Styles applied to the icon wrapping elements when filled. */
146 iconFilled: {},
147
148 /* Styles applied to the icon wrapping elements when hover. */
149 iconHover: {},
150
151 /* Styles applied to the icon wrapping elements when focus. */
152 iconFocus: {},
153
154 /* Styles applied to the icon wrapping elements when active. */
155 iconActive: {
156 transform: 'scale(1.2)'
157 },
158
159 /* Styles applied to the icon wrapping elements when decimals are necessary. */
160 decimal: {
161 position: 'relative'
162 }
163 };
164};
165
166exports.styles = styles;
167
168function IconContainer(props) {
169 var value = props.value,
170 other = (0, _objectWithoutProperties2.default)(props, ["value"]);
171 return /*#__PURE__*/React.createElement("span", other);
172}
173
174process.env.NODE_ENV !== "production" ? IconContainer.propTypes = {
175 value: _propTypes.default.number.isRequired
176} : void 0;
177var defaultIcon = /*#__PURE__*/React.createElement(_Star.default, {
178 fontSize: "inherit"
179});
180
181function defaultLabelText(value) {
182 return "".concat(value, " Star").concat(value !== 1 ? 's' : '');
183}
184
185var Rating = /*#__PURE__*/React.forwardRef(function Rating(props, ref) {
186 var classes = props.classes,
187 className = props.className,
188 _props$defaultValue = props.defaultValue,
189 defaultValue = _props$defaultValue === void 0 ? null : _props$defaultValue,
190 _props$disabled = props.disabled,
191 disabled = _props$disabled === void 0 ? false : _props$disabled,
192 emptyIcon = props.emptyIcon,
193 _props$emptyLabelText = props.emptyLabelText,
194 emptyLabelText = _props$emptyLabelText === void 0 ? 'Empty' : _props$emptyLabelText,
195 _props$getLabelText = props.getLabelText,
196 getLabelText = _props$getLabelText === void 0 ? defaultLabelText : _props$getLabelText,
197 _props$icon = props.icon,
198 icon = _props$icon === void 0 ? defaultIcon : _props$icon,
199 _props$IconContainerC = props.IconContainerComponent,
200 IconContainerComponent = _props$IconContainerC === void 0 ? IconContainer : _props$IconContainerC,
201 _props$max = props.max,
202 max = _props$max === void 0 ? 5 : _props$max,
203 nameProp = props.name,
204 onChange = props.onChange,
205 onChangeActive = props.onChangeActive,
206 onMouseLeave = props.onMouseLeave,
207 onMouseMove = props.onMouseMove,
208 _props$precision = props.precision,
209 precision = _props$precision === void 0 ? 1 : _props$precision,
210 _props$readOnly = props.readOnly,
211 readOnly = _props$readOnly === void 0 ? false : _props$readOnly,
212 _props$size = props.size,
213 size = _props$size === void 0 ? 'medium' : _props$size,
214 valueProp = props.value,
215 other = (0, _objectWithoutProperties2.default)(props, ["classes", "className", "defaultValue", "disabled", "emptyIcon", "emptyLabelText", "getLabelText", "icon", "IconContainerComponent", "max", "name", "onChange", "onChangeActive", "onMouseLeave", "onMouseMove", "precision", "readOnly", "size", "value"]);
216 var name = (0, _utils2.unstable_useId)(nameProp);
217
218 var _useControlled = (0, _utils2.useControlled)({
219 controlled: valueProp,
220 default: defaultValue,
221 name: 'Rating'
222 }),
223 _useControlled2 = (0, _slicedToArray2.default)(_useControlled, 2),
224 valueDerived = _useControlled2[0],
225 setValueState = _useControlled2[1];
226
227 var valueRounded = roundValueToPrecision(valueDerived, precision);
228 var theme = (0, _styles.useTheme)();
229
230 var _React$useState = React.useState({
231 hover: -1,
232 focus: -1
233 }),
234 _React$useState$ = _React$useState[0],
235 hover = _React$useState$.hover,
236 focus = _React$useState$.focus,
237 setState = _React$useState[1];
238
239 var value = valueRounded;
240
241 if (hover !== -1) {
242 value = hover;
243 }
244
245 if (focus !== -1) {
246 value = focus;
247 }
248
249 var _useIsFocusVisible = (0, _utils2.useIsFocusVisible)(),
250 isFocusVisible = _useIsFocusVisible.isFocusVisible,
251 onBlurVisible = _useIsFocusVisible.onBlurVisible,
252 focusVisibleRef = _useIsFocusVisible.ref;
253
254 var _React$useState2 = React.useState(false),
255 focusVisible = _React$useState2[0],
256 setFocusVisible = _React$useState2[1];
257
258 var rootRef = React.useRef();
259 var handleFocusRef = (0, _utils2.useForkRef)(focusVisibleRef, rootRef);
260 var handleRef = (0, _utils2.useForkRef)(handleFocusRef, ref);
261
262 var handleMouseMove = function handleMouseMove(event) {
263 if (onMouseMove) {
264 onMouseMove(event);
265 }
266
267 var rootNode = rootRef.current;
268
269 var _rootNode$getBounding = rootNode.getBoundingClientRect(),
270 right = _rootNode$getBounding.right,
271 left = _rootNode$getBounding.left;
272
273 var _rootNode$firstChild$ = rootNode.firstChild.getBoundingClientRect(),
274 width = _rootNode$firstChild$.width;
275
276 var percent;
277
278 if (theme.direction === 'rtl') {
279 percent = (right - event.clientX) / (width * max);
280 } else {
281 percent = (event.clientX - left) / (width * max);
282 }
283
284 var newHover = roundValueToPrecision(max * percent + precision / 2, precision);
285 newHover = clamp(newHover, precision, max);
286 setState(function (prev) {
287 return prev.hover === newHover && prev.focus === newHover ? prev : {
288 hover: newHover,
289 focus: newHover
290 };
291 });
292 setFocusVisible(false);
293
294 if (onChangeActive && hover !== newHover) {
295 onChangeActive(event, newHover);
296 }
297 };
298
299 var handleMouseLeave = function handleMouseLeave(event) {
300 if (onMouseLeave) {
301 onMouseLeave(event);
302 }
303
304 var newHover = -1;
305 setState({
306 hover: newHover,
307 focus: newHover
308 });
309
310 if (onChangeActive && hover !== newHover) {
311 onChangeActive(event, newHover);
312 }
313 };
314
315 var handleChange = function handleChange(event) {
316 var newValue = parseFloat(event.target.value);
317 setValueState(newValue);
318
319 if (onChange) {
320 onChange(event, newValue);
321 }
322 };
323
324 var handleClear = function handleClear(event) {
325 // Ignore keyboard events
326 // https://github.com/facebook/react/issues/7407
327 if (event.clientX === 0 && event.clientY === 0) {
328 return;
329 }
330
331 setState({
332 hover: -1,
333 focus: -1
334 });
335 setValueState(null);
336
337 if (onChange && parseFloat(event.target.value) === valueRounded) {
338 onChange(event, null);
339 }
340 };
341
342 var handleFocus = function handleFocus(event) {
343 if (isFocusVisible(event)) {
344 setFocusVisible(true);
345 }
346
347 var newFocus = parseFloat(event.target.value);
348 setState(function (prev) {
349 return {
350 hover: prev.hover,
351 focus: newFocus
352 };
353 });
354
355 if (onChangeActive && focus !== newFocus) {
356 onChangeActive(event, newFocus);
357 }
358 };
359
360 var handleBlur = function handleBlur(event) {
361 if (hover !== -1) {
362 return;
363 }
364
365 if (focusVisible !== false) {
366 setFocusVisible(false);
367 onBlurVisible();
368 }
369
370 var newFocus = -1;
371 setState(function (prev) {
372 return {
373 hover: prev.hover,
374 focus: newFocus
375 };
376 });
377
378 if (onChangeActive && focus !== newFocus) {
379 onChangeActive(event, newFocus);
380 }
381 };
382
383 var item = function item(state, labelProps) {
384 var id = "".concat(name, "-").concat(String(state.value).replace('.', '-'));
385 var container = /*#__PURE__*/React.createElement(IconContainerComponent, {
386 value: state.value,
387 className: (0, _clsx.default)(classes.icon, state.filled ? classes.iconFilled : classes.iconEmpty, state.hover && classes.iconHover, state.focus && classes.iconFocus, state.active && classes.iconActive)
388 }, emptyIcon && !state.filled ? emptyIcon : icon);
389
390 if (readOnly) {
391 return /*#__PURE__*/React.createElement("span", (0, _extends2.default)({
392 key: state.value
393 }, labelProps), container);
394 }
395
396 return /*#__PURE__*/React.createElement(React.Fragment, {
397 key: state.value
398 }, /*#__PURE__*/React.createElement("label", (0, _extends2.default)({
399 className: classes.label,
400 htmlFor: id
401 }, labelProps), container, /*#__PURE__*/React.createElement("span", {
402 className: classes.visuallyhidden
403 }, getLabelText(state.value))), /*#__PURE__*/React.createElement("input", {
404 onFocus: handleFocus,
405 onBlur: handleBlur,
406 onChange: handleChange,
407 onClick: handleClear,
408 disabled: disabled,
409 value: state.value,
410 id: id,
411 type: "radio",
412 name: name,
413 checked: state.checked,
414 className: classes.visuallyhidden
415 }));
416 };
417
418 return /*#__PURE__*/React.createElement("span", (0, _extends2.default)({
419 ref: handleRef,
420 onMouseMove: handleMouseMove,
421 onMouseLeave: handleMouseLeave,
422 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),
423 role: readOnly ? 'img' : null,
424 "aria-label": readOnly ? getLabelText(value) : null
425 }, other), Array.from(new Array(max)).map(function (_, index) {
426 var itemValue = index + 1;
427
428 if (precision < 1) {
429 var items = Array.from(new Array(1 / precision));
430 return /*#__PURE__*/React.createElement("span", {
431 key: itemValue,
432 className: (0, _clsx.default)(classes.decimal, itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1) && classes.iconActive)
433 }, items.map(function ($, indexDecimal) {
434 var itemDecimalValue = roundValueToPrecision(itemValue - 1 + (indexDecimal + 1) * precision, precision);
435 return item({
436 value: itemDecimalValue,
437 filled: itemDecimalValue <= value,
438 hover: itemDecimalValue <= hover,
439 focus: itemDecimalValue <= focus,
440 checked: itemDecimalValue === valueRounded
441 }, {
442 style: items.length - 1 === indexDecimal ? {} : {
443 width: itemDecimalValue === value ? "".concat((indexDecimal + 1) * precision * 100, "%") : '0%',
444 overflow: 'hidden',
445 zIndex: 1,
446 position: 'absolute'
447 }
448 });
449 }));
450 }
451
452 return item({
453 value: itemValue,
454 active: itemValue === value && (hover !== -1 || focus !== -1),
455 filled: itemValue <= value,
456 hover: itemValue <= hover,
457 focus: itemValue <= focus,
458 checked: itemValue === valueRounded
459 });
460 }), !readOnly && !disabled && valueRounded == null && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("input", {
461 value: "",
462 id: "".concat(name, "-empty"),
463 type: "radio",
464 name: name,
465 defaultChecked: true,
466 className: classes.visuallyhidden
467 }), /*#__PURE__*/React.createElement("label", {
468 className: classes.pristine,
469 htmlFor: "".concat(name, "-empty")
470 }, /*#__PURE__*/React.createElement("span", {
471 className: classes.visuallyhidden
472 }, emptyLabelText))));
473});
474process.env.NODE_ENV !== "production" ? Rating.propTypes = {
475 // ----------------------------- Warning --------------------------------
476 // | These PropTypes are generated from the TypeScript type definitions |
477 // | To update them edit the d.ts file and run "yarn proptypes" |
478 // ----------------------------------------------------------------------
479
480 /**
481 * Override or extend the styles applied to the component.
482 * See [CSS API](#css) below for more details.
483 */
484 classes: _propTypes.default.object,
485
486 /**
487 * @ignore
488 */
489 className: _propTypes.default.string,
490
491 /**
492 * The default value. Use when the component is not controlled.
493 */
494 defaultValue: _propTypes.default.number,
495
496 /**
497 * If `true`, the rating will be disabled.
498 */
499 disabled: _propTypes.default.bool,
500
501 /**
502 * The icon to display when empty.
503 */
504 emptyIcon: _propTypes.default.node,
505
506 /**
507 * The label read when the rating input is empty.
508 */
509 emptyLabelText: _propTypes.default.node,
510
511 /**
512 * Accepts a function which returns a string value that provides a user-friendly name for the current value of the rating.
513 *
514 * For localization purposes, you can use the provided [translations](/guides/localization/).
515 *
516 * @param {number} value The rating label's value to format.
517 * @returns {string}
518 */
519 getLabelText: _propTypes.default.func,
520
521 /**
522 * The icon to display.
523 */
524 icon: _propTypes.default.node,
525
526 /**
527 * The component containing the icon.
528 */
529 IconContainerComponent: _propTypes.default.elementType,
530
531 /**
532 * Maximum rating.
533 */
534 max: _propTypes.default.number,
535
536 /**
537 * The name attribute of the radio `input` elements.
538 * If `readOnly` is false, the prop is required,
539 * this input name`should be unique within the parent form.
540 */
541 name: (0, _utils.chainPropTypes)(_propTypes.default.string, function (props) {
542 if (!props.readOnly && !props.name) {
543 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'));
544 }
545
546 return null;
547 }),
548
549 /**
550 * Callback fired when the value changes.
551 *
552 * @param {object} event The event source of the callback.
553 * @param {number} value The new value.
554 */
555 onChange: _propTypes.default.func,
556
557 /**
558 * Callback function that is fired when the hover state changes.
559 *
560 * @param {object} event The event source of the callback.
561 * @param {number} value The new value.
562 */
563 onChangeActive: _propTypes.default.func,
564
565 /**
566 * @ignore
567 */
568 onMouseLeave: _propTypes.default.func,
569
570 /**
571 * @ignore
572 */
573 onMouseMove: _propTypes.default.func,
574
575 /**
576 * The minimum increment value change allowed.
577 */
578 precision: (0, _utils.chainPropTypes)(_propTypes.default.number, function (props) {
579 if (props.precision < 0.1) {
580 return new Error(['Material-UI: The prop `precision` should be above 0.1.', 'A value below this limit has an imperceptible impact.'].join('\n'));
581 }
582
583 return null;
584 }),
585
586 /**
587 * Removes all hover effects and pointer events.
588 */
589 readOnly: _propTypes.default.bool,
590
591 /**
592 * The size of the rating.
593 */
594 size: _propTypes.default.oneOf(['large', 'medium', 'small']),
595
596 /**
597 * The rating value.
598 */
599 value: _propTypes.default.number
600} : void 0;
601
602var _default = (0, _styles.withStyles)(styles, {
603 name: 'MuiRating'
604})(Rating);
605
606exports.default = _default;
\No newline at end of file