583 lines
19 KiB
JavaScript
583 lines
19 KiB
JavaScript
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) :
|
|
typeof define === 'function' && define.amd ? define(['react'], factory) :
|
|
(global = global || self, global.ReactRating = factory(global.React));
|
|
}(this, function (React) { 'use strict';
|
|
|
|
React = React && React.hasOwnProperty('default') ? React['default'] : React;
|
|
|
|
function _typeof(obj) {
|
|
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
_typeof = function (obj) {
|
|
return typeof obj;
|
|
};
|
|
} else {
|
|
_typeof = function (obj) {
|
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
};
|
|
}
|
|
|
|
return _typeof(obj);
|
|
}
|
|
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _extends() {
|
|
_extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
return _extends.apply(this, arguments);
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
function _inherits(subClass, superClass) {
|
|
if (typeof superClass !== "function" && superClass !== null) {
|
|
throw new TypeError("Super expression must either be null or a function");
|
|
}
|
|
|
|
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
|
constructor: {
|
|
value: subClass,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
if (superClass) _setPrototypeOf(subClass, superClass);
|
|
}
|
|
|
|
function _getPrototypeOf(o) {
|
|
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
|
return o.__proto__ || Object.getPrototypeOf(o);
|
|
};
|
|
return _getPrototypeOf(o);
|
|
}
|
|
|
|
function _setPrototypeOf(o, p) {
|
|
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
|
|
return _setPrototypeOf(o, p);
|
|
}
|
|
|
|
function _assertThisInitialized(self) {
|
|
if (self === void 0) {
|
|
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
function _possibleConstructorReturn(self, call) {
|
|
if (call && (typeof call === "object" || typeof call === "function")) {
|
|
return call;
|
|
}
|
|
|
|
return _assertThisInitialized(self);
|
|
}
|
|
|
|
var style = {
|
|
display: 'inline-block',
|
|
borderRadius: '50%',
|
|
border: '5px double white',
|
|
width: 30,
|
|
height: 30
|
|
};
|
|
var Style = {
|
|
empty: _objectSpread({}, style, {
|
|
backgroundColor: '#ccc'
|
|
}),
|
|
full: _objectSpread({}, style, {
|
|
backgroundColor: 'black'
|
|
}),
|
|
placeholder: _objectSpread({}, style, {
|
|
backgroundColor: 'red'
|
|
})
|
|
};
|
|
|
|
// Return the corresponding React node for an icon.
|
|
var _iconNode = function _iconNode(icon) {
|
|
// If it is already a React Element just return it.
|
|
if (React.isValidElement(icon)) {
|
|
return icon;
|
|
} // If it is an object, try to use it as a CSS style object.
|
|
|
|
|
|
if (_typeof(icon) === 'object' && icon !== null) {
|
|
return React.createElement("span", {
|
|
style: icon
|
|
});
|
|
} // If it is a string, use it as class names.
|
|
|
|
|
|
if (Object.prototype.toString.call(icon) === '[object String]') {
|
|
return React.createElement("span", {
|
|
className: icon
|
|
});
|
|
}
|
|
};
|
|
|
|
var RatingSymbol =
|
|
/*#__PURE__*/
|
|
function (_React$PureComponent) {
|
|
_inherits(RatingSymbol, _React$PureComponent);
|
|
|
|
function RatingSymbol() {
|
|
_classCallCheck(this, RatingSymbol);
|
|
|
|
return _possibleConstructorReturn(this, _getPrototypeOf(RatingSymbol).apply(this, arguments));
|
|
}
|
|
|
|
_createClass(RatingSymbol, [{
|
|
key: "render",
|
|
value: function render() {
|
|
var _iconContainerStyle;
|
|
|
|
var _this$props = this.props,
|
|
index = _this$props.index,
|
|
inactiveIcon = _this$props.inactiveIcon,
|
|
activeIcon = _this$props.activeIcon,
|
|
percent = _this$props.percent,
|
|
direction = _this$props.direction,
|
|
readonly = _this$props.readonly,
|
|
onClick = _this$props.onClick,
|
|
onMouseMove = _this$props.onMouseMove;
|
|
|
|
var backgroundNode = _iconNode(inactiveIcon);
|
|
|
|
var showbgIcon = percent < 100;
|
|
var bgIconContainerStyle = showbgIcon ? {} : {
|
|
visibility: 'hidden'
|
|
};
|
|
|
|
var iconNode = _iconNode(activeIcon);
|
|
|
|
var iconContainerStyle = (_iconContainerStyle = {
|
|
display: 'inline-block',
|
|
position: 'absolute',
|
|
overflow: 'hidden',
|
|
top: 0
|
|
}, _defineProperty(_iconContainerStyle, direction === 'rtl' ? 'right' : 'left', 0), _defineProperty(_iconContainerStyle, "width", "".concat(percent, "%")), _iconContainerStyle);
|
|
var style = {
|
|
cursor: !readonly ? 'pointer' : 'inherit',
|
|
display: 'inline-block',
|
|
position: 'relative'
|
|
};
|
|
|
|
function handleMouseMove(e) {
|
|
if (onMouseMove) {
|
|
onMouseMove(index, e);
|
|
}
|
|
}
|
|
|
|
function handleMouseClick(e) {
|
|
if (onClick) {
|
|
// [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent)
|
|
// We must prevent firing click event twice on touch devices.
|
|
e.preventDefault();
|
|
onClick(index, e);
|
|
}
|
|
}
|
|
|
|
return React.createElement("span", {
|
|
style: style,
|
|
onClick: handleMouseClick,
|
|
onMouseMove: handleMouseMove,
|
|
onTouchMove: handleMouseMove,
|
|
onTouchEnd: handleMouseClick
|
|
}, React.createElement("span", {
|
|
style: bgIconContainerStyle
|
|
}, backgroundNode), React.createElement("span", {
|
|
style: iconContainerStyle
|
|
}, iconNode));
|
|
}
|
|
}]);
|
|
|
|
return RatingSymbol;
|
|
}(React.PureComponent); // Define propTypes only in development. They will be void in production.
|
|
|
|
var Rating =
|
|
/*#__PURE__*/
|
|
function (_React$PureComponent) {
|
|
_inherits(Rating, _React$PureComponent);
|
|
|
|
function Rating(props) {
|
|
var _this;
|
|
|
|
_classCallCheck(this, Rating);
|
|
|
|
_this = _possibleConstructorReturn(this, _getPrototypeOf(Rating).call(this, props));
|
|
_this.state = {
|
|
// Indicates the value that is displayed to the user in the form of symbols.
|
|
// It can be either 0 (for no displayed symbols) or (0, end]
|
|
displayValue: _this.props.value,
|
|
// Indicates if the user is currently hovering over the rating element
|
|
interacting: false
|
|
};
|
|
_this.onMouseLeave = _this.onMouseLeave.bind(_assertThisInitialized(_assertThisInitialized(_this)));
|
|
_this.symbolMouseMove = _this.symbolMouseMove.bind(_assertThisInitialized(_assertThisInitialized(_this)));
|
|
_this.symbolClick = _this.symbolClick.bind(_assertThisInitialized(_assertThisInitialized(_this)));
|
|
return _this;
|
|
}
|
|
|
|
_createClass(Rating, [{
|
|
key: "UNSAFE_componentWillReceiveProps",
|
|
value: function UNSAFE_componentWillReceiveProps(nextProps) {
|
|
var valueChanged = this.props.value !== nextProps.value;
|
|
this.setState(function (prevState) {
|
|
return {
|
|
displayValue: valueChanged ? nextProps.value : prevState.displayValue
|
|
};
|
|
});
|
|
}
|
|
}, {
|
|
key: "componentDidUpdate",
|
|
value: function componentDidUpdate(prevProps, prevState) {
|
|
// Ignore state update due to value changed from props.
|
|
// Usually originated through an onClick event.
|
|
if (prevProps.value !== this.props.value) {
|
|
return;
|
|
} // When hover ends, call this.props.onHover with no value.
|
|
|
|
|
|
if (prevState.interacting && !this.state.interacting) {
|
|
return this.props.onHover();
|
|
} // When hover over.
|
|
|
|
|
|
if (this.state.interacting) {
|
|
this.props.onHover(this.state.displayValue);
|
|
}
|
|
}
|
|
}, {
|
|
key: "symbolClick",
|
|
value: function symbolClick(symbolIndex, event) {
|
|
var value = this.calculateDisplayValue(symbolIndex, event);
|
|
this.props.onClick(value, event);
|
|
}
|
|
}, {
|
|
key: "symbolMouseMove",
|
|
value: function symbolMouseMove(symbolIndex, event) {
|
|
var value = this.calculateDisplayValue(symbolIndex, event); // This call should cause an update only if the state changes.
|
|
// Mainly the first time the mouse enters and whenever the value changes.
|
|
// So DidComponentUpdate is NOT called for every mouse movement.
|
|
|
|
this.setState({
|
|
interacting: !this.props.readonly,
|
|
displayValue: value
|
|
});
|
|
}
|
|
}, {
|
|
key: "onMouseLeave",
|
|
value: function onMouseLeave() {
|
|
this.setState({
|
|
displayValue: this.props.value,
|
|
interacting: false
|
|
});
|
|
}
|
|
}, {
|
|
key: "calculateDisplayValue",
|
|
value: function calculateDisplayValue(symbolIndex, event) {
|
|
var percentage = this.calculateHoverPercentage(event); // Get the closest top fraction.
|
|
|
|
var fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions; // Truncate decimal trying to avoid float precission issues.
|
|
|
|
var precision = Math.pow(10, 3);
|
|
var displayValue = symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision); // ensure the returned value is greater than 0 and lower than totalSymbols
|
|
|
|
return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions;
|
|
}
|
|
}, {
|
|
key: "calculateHoverPercentage",
|
|
value: function calculateHoverPercentage(event) {
|
|
var clientX = event.nativeEvent.type.indexOf("touch") > -1 ? event.nativeEvent.type.indexOf("touchend") > -1 ? event.changedTouches[0].clientX : event.touches[0].clientX : event.clientX;
|
|
var targetRect = event.target.getBoundingClientRect();
|
|
var delta = this.props.direction === 'rtl' ? targetRect.right - clientX : clientX - targetRect.left; // Returning 0 if the delta is negative solves the flickering issue
|
|
|
|
return delta < 0 ? 0 : delta / targetRect.width;
|
|
}
|
|
}, {
|
|
key: "render",
|
|
value: function render() {
|
|
var _this$props = this.props,
|
|
readonly = _this$props.readonly,
|
|
quiet = _this$props.quiet,
|
|
totalSymbols = _this$props.totalSymbols,
|
|
value = _this$props.value,
|
|
placeholderValue = _this$props.placeholderValue,
|
|
direction = _this$props.direction,
|
|
emptySymbol = _this$props.emptySymbol,
|
|
fullSymbol = _this$props.fullSymbol,
|
|
placeholderSymbol = _this$props.placeholderSymbol,
|
|
className = _this$props.className,
|
|
id = _this$props.id,
|
|
style = _this$props.style,
|
|
tabIndex = _this$props.tabIndex;
|
|
var _this$state = this.state,
|
|
displayValue = _this$state.displayValue,
|
|
interacting = _this$state.interacting;
|
|
var symbolNodes = [];
|
|
var empty = [].concat(emptySymbol);
|
|
var full = [].concat(fullSymbol);
|
|
var placeholder = [].concat(placeholderSymbol);
|
|
var shouldDisplayPlaceholder = placeholderValue !== 0 && value === 0 && !interacting; // The value that will be used as base for calculating how to render the symbols
|
|
|
|
var renderedValue;
|
|
|
|
if (shouldDisplayPlaceholder) {
|
|
renderedValue = placeholderValue;
|
|
} else {
|
|
renderedValue = quiet ? value : displayValue;
|
|
} // The amount of full symbols
|
|
|
|
|
|
var fullSymbols = Math.floor(renderedValue);
|
|
|
|
for (var i = 0; i < totalSymbols; i++) {
|
|
var percent = void 0; // Calculate each symbol's fullness percentage
|
|
|
|
if (i - fullSymbols < 0) {
|
|
percent = 100;
|
|
} else if (i - fullSymbols === 0) {
|
|
percent = (renderedValue - i) * 100;
|
|
} else {
|
|
percent = 0;
|
|
}
|
|
|
|
symbolNodes.push(React.createElement(RatingSymbol, _extends({
|
|
key: i,
|
|
index: i,
|
|
readonly: readonly,
|
|
inactiveIcon: empty[i % empty.length],
|
|
activeIcon: shouldDisplayPlaceholder ? placeholder[i % full.length] : full[i % full.length],
|
|
percent: percent,
|
|
direction: direction
|
|
}, !readonly && {
|
|
onClick: this.symbolClick,
|
|
onMouseMove: this.symbolMouseMove,
|
|
onTouchMove: this.symbolMouseMove,
|
|
onTouchEnd: this.symbolClick
|
|
})));
|
|
}
|
|
|
|
return React.createElement("span", _extends({
|
|
id: id,
|
|
style: _objectSpread({}, style, {
|
|
display: 'inline-block',
|
|
direction: direction
|
|
}),
|
|
className: className,
|
|
tabIndex: tabIndex,
|
|
"aria-label": this.props['aria-label']
|
|
}, !readonly && {
|
|
onMouseLeave: this.onMouseLeave
|
|
}), symbolNodes);
|
|
}
|
|
}]);
|
|
|
|
return Rating;
|
|
}(React.PureComponent); // Define propTypes only in development.
|
|
|
|
function noop() {}
|
|
|
|
noop._name = 'react_rating_noop';
|
|
|
|
var RatingAPILayer =
|
|
/*#__PURE__*/
|
|
function (_React$PureComponent) {
|
|
_inherits(RatingAPILayer, _React$PureComponent);
|
|
|
|
function RatingAPILayer(props) {
|
|
var _this;
|
|
|
|
_classCallCheck(this, RatingAPILayer);
|
|
|
|
_this = _possibleConstructorReturn(this, _getPrototypeOf(RatingAPILayer).call(this, props));
|
|
_this.state = {
|
|
value: props.initialRating
|
|
};
|
|
_this.handleClick = _this.handleClick.bind(_assertThisInitialized(_assertThisInitialized(_this)));
|
|
_this.handleHover = _this.handleHover.bind(_assertThisInitialized(_assertThisInitialized(_this)));
|
|
return _this;
|
|
}
|
|
|
|
_createClass(RatingAPILayer, [{
|
|
key: "UNSAFE_componentWillReceiveProps",
|
|
value: function UNSAFE_componentWillReceiveProps(nextProps) {
|
|
this.setState({
|
|
value: nextProps.initialRating
|
|
});
|
|
}
|
|
}, {
|
|
key: "handleClick",
|
|
value: function handleClick(value, e) {
|
|
var _this2 = this;
|
|
|
|
var newValue = this.translateDisplayValueToValue(value);
|
|
this.props.onClick(newValue); // Avoid calling setState if not necessary. Micro optimisation.
|
|
|
|
if (this.state.value !== newValue) {
|
|
// If we have a new value trigger onChange callback.
|
|
this.setState({
|
|
value: newValue
|
|
}, function () {
|
|
return _this2.props.onChange(_this2.state.value);
|
|
});
|
|
}
|
|
}
|
|
}, {
|
|
key: "handleHover",
|
|
value: function handleHover(displayValue) {
|
|
var value = displayValue === undefined ? displayValue : this.translateDisplayValueToValue(displayValue);
|
|
this.props.onHover(value);
|
|
}
|
|
}, {
|
|
key: "translateDisplayValueToValue",
|
|
value: function translateDisplayValueToValue(displayValue) {
|
|
var translatedValue = displayValue * this.props.step + this.props.start; // minimum value cannot be equal to start, since it's exclusive
|
|
|
|
return translatedValue === this.props.start ? translatedValue + 1 / this.props.fractions : translatedValue;
|
|
}
|
|
}, {
|
|
key: "tranlateValueToDisplayValue",
|
|
value: function tranlateValueToDisplayValue(value) {
|
|
if (value === undefined) {
|
|
return 0;
|
|
}
|
|
|
|
return (value - this.props.start) / this.props.step;
|
|
}
|
|
}, {
|
|
key: "render",
|
|
value: function render() {
|
|
var _this$props = this.props,
|
|
step = _this$props.step,
|
|
emptySymbol = _this$props.emptySymbol,
|
|
fullSymbol = _this$props.fullSymbol,
|
|
placeholderSymbol = _this$props.placeholderSymbol,
|
|
readonly = _this$props.readonly,
|
|
quiet = _this$props.quiet,
|
|
fractions = _this$props.fractions,
|
|
direction = _this$props.direction,
|
|
start = _this$props.start,
|
|
stop = _this$props.stop,
|
|
id = _this$props.id,
|
|
className = _this$props.className,
|
|
style = _this$props.style,
|
|
tabIndex = _this$props.tabIndex;
|
|
|
|
function calculateTotalSymbols(start, stop, step) {
|
|
return Math.floor((stop - start) / step);
|
|
}
|
|
|
|
return React.createElement(Rating, {
|
|
id: id,
|
|
style: style,
|
|
className: className,
|
|
tabIndex: tabIndex,
|
|
"aria-label": this.props['aria-label'],
|
|
totalSymbols: calculateTotalSymbols(start, stop, step),
|
|
value: this.tranlateValueToDisplayValue(this.state.value),
|
|
placeholderValue: this.tranlateValueToDisplayValue(this.props.placeholderRating),
|
|
readonly: readonly,
|
|
quiet: quiet,
|
|
fractions: fractions,
|
|
direction: direction,
|
|
emptySymbol: emptySymbol,
|
|
fullSymbol: fullSymbol,
|
|
placeholderSymbol: placeholderSymbol,
|
|
onClick: this.handleClick,
|
|
onHover: this.handleHover
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return RatingAPILayer;
|
|
}(React.PureComponent);
|
|
|
|
RatingAPILayer.defaultProps = {
|
|
start: 0,
|
|
stop: 5,
|
|
step: 1,
|
|
readonly: false,
|
|
quiet: false,
|
|
fractions: 1,
|
|
direction: 'ltr',
|
|
onHover: noop,
|
|
onClick: noop,
|
|
onChange: noop,
|
|
emptySymbol: Style.empty,
|
|
fullSymbol: Style.full,
|
|
placeholderSymbol: Style.placeholder
|
|
}; // Define propTypes only in development.
|
|
|
|
return RatingAPILayer;
|
|
|
|
}));
|
|
//# sourceMappingURL=react-rating.umd.js.map
|