UNPKG

d2-ui

Version:
584 lines (521 loc) 19.6 kB
'use strict'; var _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; }; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _stylePropable = require('./mixins/style-propable'); var _stylePropable2 = _interopRequireDefault(_stylePropable); var _transitions = require('./styles/transitions'); var _transitions2 = _interopRequireDefault(_transitions); var _focusRipple = require('./ripples/focus-ripple'); var _focusRipple2 = _interopRequireDefault(_focusRipple); var _getMuiTheme = require('./styles/getMuiTheme'); var _getMuiTheme2 = _interopRequireDefault(_getMuiTheme); var _autoPrefix = require('./styles/auto-prefix'); var _autoPrefix2 = _interopRequireDefault(_autoPrefix); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } /** * Verifies min/max range. * @param {Object} props Properties of the React component. * @param {String} propName Name of the property to validate. * @param {String} componentName Name of the component whose property is being validated. * @returns {Object} Returns an Error if min >= max otherwise null. */ var minMaxPropType = function minMaxPropType(props, propName, componentName) { var error = _react2.default.PropTypes.number(props, propName, componentName); if (error !== null) return error; if (props.min >= props.max) { var errorMsg = propName === 'min' ? 'min should be less than max' : 'max should be greater than min'; return new Error(errorMsg); } }; /** * Verifies value is within the min/max range. * @param {Object} props Properties of the React component. * @param {String} propName Name of the property to validate. * @param {String} componentName Name of the component whose property is being validated. * @returns {Object} Returns an Error if the value is not within the range otherwise null. */ var valueInRangePropType = function valueInRangePropType(props, propName, componentName) { var error = _react2.default.PropTypes.number(props, propName, componentName); if (error !== null) return error; var value = props[propName]; if (value < props.min || props.max < value) { return new Error(propName + ' should be within the range specified by min and max'); } }; var Slider = _react2.default.createClass({ displayName: 'Slider', propTypes: { /** * The default value of the slider. */ defaultValue: valueInRangePropType, /** * Describe the slider. */ description: _react2.default.PropTypes.string, /** * Disables focus ripple if set to true. */ disableFocusRipple: _react2.default.PropTypes.bool, /** * If true, the slider will not be interactable. */ disabled: _react2.default.PropTypes.bool, /** * An error message for the slider. */ error: _react2.default.PropTypes.string, /** * The maximum value the slider can slide to on * a scale from 0 to 1 inclusive. Cannot be equal to min. */ max: minMaxPropType, /** * The minimum value the slider can slide to on a scale * from 0 to 1 inclusive. Cannot be equal to max. */ min: minMaxPropType, /** * The name of the slider. Behaves like the name attribute * of an input element. */ name: _react2.default.PropTypes.string, /** * Callback function that is fired when the focus has left the slider. */ onBlur: _react2.default.PropTypes.func, /** * Callback function that is fired when the user changes the slider's value. */ onChange: _react2.default.PropTypes.func, /** * Callback function that is fired when the slider has begun to move. */ onDragStart: _react2.default.PropTypes.func, /** * Callback function that is fried when the slide has stopped moving. */ onDragStop: _react2.default.PropTypes.func, /** * Callback fired when the user has focused on the slider. */ onFocus: _react2.default.PropTypes.func, /** * Whether or not the slider is required in a form. */ required: _react2.default.PropTypes.bool, /** * The granularity the slider can step through values. */ step: _react2.default.PropTypes.number, /** * Override the inline-styles of the root element. */ style: _react2.default.PropTypes.object, /** * The value of the slider. */ value: valueInRangePropType }, contextTypes: { muiTheme: _react2.default.PropTypes.object }, //for passing default theme context to children childContextTypes: { muiTheme: _react2.default.PropTypes.object }, mixins: [_stylePropable2.default], getDefaultProps: function getDefaultProps() { return { disabled: false, disableFocusRipple: false, max: 1, min: 0, required: true, step: 0.01, style: {} }; }, getInitialState: function getInitialState() { var value = this.props.value; if (value === undefined) { value = this.props.defaultValue !== undefined ? this.props.defaultValue : this.props.min; } var percent = (value - this.props.min) / (this.props.max - this.props.min); if (isNaN(percent)) percent = 0; return { active: false, dragging: false, focused: false, hovered: false, percent: percent, value: value, muiTheme: this.context.muiTheme || (0, _getMuiTheme2.default)() }; }, getChildContext: function getChildContext() { return { muiTheme: this.state.muiTheme }; }, componentWillReceiveProps: function componentWillReceiveProps(nextProps, nextContext) { var newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({ muiTheme: newMuiTheme }); if (nextProps.value !== undefined && !this.state.dragging) { this.setValue(nextProps.value); } }, getTheme: function getTheme() { return this.state.muiTheme.slider; }, getStyles: function getStyles() { var fillGutter = this.getTheme().handleSize / 2; var disabledGutter = this.getTheme().trackSize + this.getTheme().handleSizeDisabled / 2; var calcDisabledSpacing = this.props.disabled ? ' - ' + disabledGutter + 'px' : ''; var styles = { root: { touchCallout: 'none', userSelect: 'none', cursor: 'default', height: this.getTheme().handleSizeActive, position: 'relative', marginTop: 24, marginBottom: 48 }, track: { position: 'absolute', top: (this.getTheme().handleSizeActive - this.getTheme().trackSize) / 2, left: 0, width: '100%', height: this.getTheme().trackSize }, filledAndRemaining: { position: 'absolute', top: 0, height: '100%', transition: _transitions2.default.easeOut(null, 'margin') }, handle: { boxSizing: 'border-box', position: 'absolute', cursor: 'pointer', pointerEvents: 'inherit', top: 0, left: '0%', zIndex: 1, margin: this.getTheme().trackSize / 2 + 'px 0 0 0', width: this.getTheme().handleSize, height: this.getTheme().handleSize, backgroundColor: this.getTheme().selectionColor, backgroundClip: 'padding-box', border: '0px solid transparent', borderRadius: '50%', transform: 'translate(-50%, -50%)', transition: _transitions2.default.easeOut('450ms', 'background') + ',' + _transitions2.default.easeOut('450ms', 'border-color') + ',' + _transitions2.default.easeOut('450ms', 'width') + ',' + _transitions2.default.easeOut('450ms', 'height'), overflow: 'visible' }, handleWhenDisabled: { boxSizing: 'content-box', cursor: 'not-allowed', backgroundColor: this.getTheme().trackColor, width: this.getTheme().handleSizeDisabled, height: this.getTheme().handleSizeDisabled, border: 'none' }, handleWhenPercentZero: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().handleColorZero, backgroundColor: this.getTheme().handleFillColor, boxShadow: 'none' }, handleWhenPercentZeroAndDisabled: { cursor: 'not-allowed', width: this.getTheme().handleSizeDisabled, height: this.getTheme().handleSizeDisabled }, handleWhenPercentZeroAndFocused: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().trackColorSelected }, handleWhenActive: { width: this.getTheme().handleSizeActive, height: this.getTheme().handleSizeActive }, ripple: { height: this.getTheme().handleSize, width: this.getTheme().handleSize, overflow: 'visible' }, rippleWhenPercentZero: { top: -this.getTheme().trackSize, left: -this.getTheme().trackSize }, rippleInner: { height: '300%', width: '300%', top: -this.getTheme().handleSize, left: -this.getTheme().handleSize } }; styles.filled = this.mergeStyles(styles.filledAndRemaining, { left: 0, backgroundColor: this.props.disabled ? this.getTheme().trackColor : this.getTheme().selectionColor, marginRight: fillGutter, width: 'calc(' + this.state.percent * 100 + '%' + calcDisabledSpacing + ')' }); styles.remaining = this.mergeStyles(styles.filledAndRemaining, { right: 0, backgroundColor: this.getTheme().trackColor, marginLeft: fillGutter, width: 'calc(' + (1 - this.state.percent) * 100 + '%' + calcDisabledSpacing + ')' }); return styles; }, // Needed to prevent text selection when dragging the slider handler. // In the future, we should consider use <input type="range"> to avoid // similar issues. _toggleSelection: function _toggleSelection(value) { var body = document.getElementsByTagName('body')[0]; _autoPrefix2.default.set(body.style, 'userSelect', value, this.state.muiTheme); }, _onHandleTouchStart: function _onHandleTouchStart(e) { if (document) { document.addEventListener('touchmove', this._dragTouchHandler, false); document.addEventListener('touchup', this._dragTouchEndHandler, false); document.addEventListener('touchend', this._dragTouchEndHandler, false); document.addEventListener('touchcancel', this._dragTouchEndHandler, false); } this._onDragStart(e); }, _onHandleMouseDown: function _onHandleMouseDown(e) { if (document) { document.addEventListener('mousemove', this._dragHandler, false); document.addEventListener('mouseup', this._dragEndHandler, false); this._toggleSelection('none'); } this._onDragStart(e); }, _dragHandler: function _dragHandler(e) { var _this = this; if (this._dragRunning) { return; } this._dragRunning = true; requestAnimationFrame(function () { _this._onDragUpdate(e, e.clientX - _this._getTrackLeft()); _this._dragRunning = false; }); }, _dragTouchHandler: function _dragTouchHandler(e) { var _this2 = this; if (this._dragRunning) { return; } this._dragRunning = true; requestAnimationFrame(function () { _this2._onDragUpdate(e, e.touches[0].clientX - _this2._getTrackLeft()); _this2._dragRunning = false; }); }, _dragEndHandler: function _dragEndHandler(e) { if (document) { document.removeEventListener('mousemove', this._dragHandler, false); document.removeEventListener('mouseup', this._dragEndHandler, false); this._toggleSelection(''); } this._onDragStop(e); }, _dragTouchEndHandler: function _dragTouchEndHandler(e) { if (document) { document.removeEventListener('touchmove', this._dragTouchHandler, false); document.removeEventListener('touchup', this._dragTouchEndHandler, false); document.removeEventListener('touchend', this._dragTouchEndHandler, false); document.removeEventListener('touchcancel', this._dragTouchEndHandler, false); } this._onDragStop(e); }, getValue: function getValue() { return this.state.value; }, setValue: function setValue(i) { // calculate percentage var percent = (i - this.props.min) / (this.props.max - this.props.min); if (isNaN(percent)) percent = 0; // update state this.setState({ value: i, percent: percent }); }, getPercent: function getPercent() { return this.state.percent; }, setPercent: function setPercent(percent, callback) { var value = this._alignValue(this._percentToValue(percent)); var _props = this.props; var min = _props.min; var max = _props.max; var alignedPercent = (value - min) / (max - min); if (this.state.value !== value) { this.setState({ value: value, percent: alignedPercent }, callback); } }, clearValue: function clearValue() { this.setValue(this.props.min); }, _alignValue: function _alignValue(val) { var _props2 = this.props; var step = _props2.step; var min = _props2.min; var alignValue = Math.round((val - min) / step) * step + min; return parseFloat(alignValue.toFixed(5)); }, _onFocus: function _onFocus(e) { this.setState({ focused: true }); if (this.props.onFocus) this.props.onFocus(e); }, _onBlur: function _onBlur(e) { this.setState({ focused: false, active: false }); if (this.props.onBlur) this.props.onBlur(e); }, _onMouseDown: function _onMouseDown(e) { if (!this.props.disabled) this._pos = e.clientX; }, _onMouseEnter: function _onMouseEnter() { this.setState({ hovered: true }); }, _onMouseLeave: function _onMouseLeave() { this.setState({ hovered: false }); }, _getTrackLeft: function _getTrackLeft() { return _reactDom2.default.findDOMNode(this.refs.track).getBoundingClientRect().left; }, _onMouseUp: function _onMouseUp(e) { if (!this.props.disabled) this.setState({ active: false }); if (!this.state.dragging && Math.abs(this._pos - e.clientX) < 5) { var pos = e.clientX - this._getTrackLeft(); this._dragX(e, pos); } this._pos = undefined; }, _onDragStart: function _onDragStart(e) { this.setState({ dragging: true, active: true }); if (this.props.onDragStart) this.props.onDragStart(e); }, _onDragStop: function _onDragStop(e) { this.setState({ dragging: false, active: false }); if (this.props.onDragStop) this.props.onDragStop(e); }, _onDragUpdate: function _onDragUpdate(e, pos) { if (!this.state.dragging) return; if (!this.props.disabled) this._dragX(e, pos); }, _dragX: function _dragX(e, pos) { var max = _reactDom2.default.findDOMNode(this.refs.track).clientWidth; if (pos < 0) pos = 0;else if (pos > max) pos = max; this._updateWithChangeEvent(e, pos / max); }, _updateWithChangeEvent: function _updateWithChangeEvent(e, percent) { var _this3 = this; this.setPercent(percent, function () { if (_this3.props.onChange) _this3.props.onChange(e, _this3.state.value); }); }, _percentToValue: function _percentToValue(percent) { return percent * (this.props.max - this.props.min) + this.props.min; }, render: function render() { var others = _objectWithoutProperties(this.props, []); var percent = this.state.percent; if (percent > 1) percent = 1;else if (percent < 0) percent = 0; var styles = this.getStyles(); var sliderStyles = this.mergeStyles(styles.root, this.props.style); var handleStyles = percent === 0 ? this.mergeStyles(styles.handle, styles.handleWhenPercentZero, this.state.active && styles.handleWhenActive, this.state.focused && { outline: 'none' }, (this.state.hovered || this.state.focused) && !this.props.disabled && styles.handleWhenPercentZeroAndFocused, this.props.disabled && styles.handleWhenPercentZeroAndDisabled) : this.mergeStyles(styles.handle, this.state.active && styles.handleWhenActive, this.state.focused && { outline: 'none' }, this.props.disabled && styles.handleWhenDisabled, { left: percent * 100 + '%' }); var rippleStyle = this.mergeStyles(styles.ripple, percent === 0 && styles.rippleWhenPercentZero); var remainingStyles = styles.remaining; if ((this.state.hovered || this.state.focused) && !this.props.disabled) { remainingStyles.backgroundColor = this.getTheme().trackColorSelected; } var rippleShowCondition = (this.state.hovered || this.state.focused) && !this.state.active; var rippleColor = this.state.percent === 0 ? this.getTheme().handleColorZero : this.getTheme().rippleColor; var focusRipple = undefined; if (!this.props.disabled && !this.props.disableFocusRipple) { focusRipple = _react2.default.createElement(_focusRipple2.default, { ref: 'focusRipple', key: 'focusRipple', style: this.mergeStyles(rippleStyle), innerStyle: styles.rippleInner, show: rippleShowCondition, muiTheme: this.state.muiTheme, color: rippleColor }); } var handleDragProps = {}; if (!this.props.disabled) { handleDragProps = { onTouchStart: this._onHandleTouchStart, onMouseDown: this._onHandleMouseDown }; } return _react2.default.createElement( 'div', _extends({}, others, { style: this.prepareStyles(this.props.style) }), _react2.default.createElement( 'span', null, this.props.description ), _react2.default.createElement( 'span', null, this.props.error ), _react2.default.createElement( 'div', { style: this.prepareStyles(sliderStyles), onFocus: this._onFocus, onBlur: this._onBlur, onMouseDown: this._onMouseDown, onMouseEnter: this._onMouseEnter, onMouseLeave: this._onMouseLeave, onMouseUp: this._onMouseUp }, _react2.default.createElement( 'div', { ref: 'track', style: this.prepareStyles(styles.track) }, _react2.default.createElement('div', { style: this.prepareStyles(styles.filled) }), _react2.default.createElement('div', { style: this.prepareStyles(remainingStyles) }), _react2.default.createElement( 'div', _extends({ style: this.prepareStyles(handleStyles), tabIndex: 0 }, handleDragProps), focusRipple ) ) ), _react2.default.createElement('input', { ref: 'input', type: 'hidden', name: this.props.name, value: this.state.value, required: this.props.required, min: this.props.min, max: this.props.max, step: this.props.step }) ); } }); exports.default = Slider; module.exports = exports['default'];