UNPKG

material-ui

Version:

Material Design UI components built with React

462 lines (405 loc) 15.5 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; }; 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; } var React = require('react'); var StylePropable = require('./mixins/style-propable'); var Draggable = require('react-draggable2'); var Transitions = require('./styles/transitions'); var FocusRipple = require('./ripples/focus-ripple'); /** * 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 = React.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 = React.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 = React.createClass({ displayName: 'Slider', mixins: [StylePropable], contextTypes: { muiTheme: React.PropTypes.object }, propTypes: { name: React.PropTypes.string.isRequired, defaultValue: valueInRangePropType, description: React.PropTypes.string, disabled: React.PropTypes.bool, error: React.PropTypes.string, max: minMaxPropType, min: minMaxPropType, required: React.PropTypes.bool, step: React.PropTypes.number, onBlur: React.PropTypes.func, onChange: React.PropTypes.func, onDragStart: React.PropTypes.func, onDragStop: React.PropTypes.func, onFocus: React.PropTypes.func, value: valueInRangePropType }, getDefaultProps: function getDefaultProps() { return { defaultValue: 0, disabled: false, max: 1, min: 0, required: true, step: 0.01 }; }, getInitialState: function getInitialState() { var value = this.props.value; if (value === undefined) { value = this.props.defaultValue; } 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 }; }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { if (nextProps.value !== undefined) { this.setValue(nextProps.value); } }, getTheme: function getTheme() { return this.context.muiTheme.component.slider; }, getStyles: function getStyles() { var size = this.getTheme().handleSize + this.getTheme().trackSize; var gutter = (this.getTheme().handleSizeDisabled + this.getTheme().trackSize) / 2; var fillGutter = this.getTheme().handleSizeDisabled - this.getTheme().trackSize; 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: Transitions.easeOut(null, 'margin') }, percentZeroRemaining: { left: 1, marginLeft: gutter }, handle: { boxSizing: 'border-box', position: 'absolute', cursor: 'pointer', pointerEvents: 'inherit', top: (this.getTheme().handleSizeActive - this.getTheme().trackSize) / 2 + 'px', 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: Transitions.easeOut('450ms', 'border') + ',' + Transitions.easeOut('450ms', 'width') + ',' + Transitions.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: '2px solid white' }, handleWhenPercentZero: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().trackColor, backgroundColor: this.getTheme().handleFillColor, boxShadow: 'none' }, handleWhenActive: { borderColor: this.getTheme().trackColorSelected, width: this.getTheme().handleSizeActive, height: this.getTheme().handleSizeActive, transition: Transitions.easeOut('450ms', 'backgroundColor') + ',' + Transitions.easeOut('450ms', 'width') + ',' + Transitions.easeOut('450ms', 'height') }, ripples: { height: '300%', width: '300%', top: '-12px', left: '-12px' }, handleWhenDisabledAndZero: { width: size / 2 + 'px', height: size / 2 + 'px' }, handleWhenPercentZeroAndHovered: { border: this.getTheme().trackSize + 'px solid ' + this.getTheme().handleColorZero, width: size + 'px', height: size + 'px' } }; styles.filled = this.mergeAndPrefix(styles.filledAndRemaining, { left: 0, backgroundColor: this.props.disabled ? this.getTheme().trackColor : this.getTheme().selectionColor, marginRight: fillGutter, width: this.state.percent * 100 + (this.props.disabled ? -1 : 0) + '%' }); styles.remaining = this.mergeAndPrefix(styles.filledAndRemaining, { right: 0, backgroundColor: this.getTheme().trackColor, marginLeft: fillGutter, width: (1 - this.state.percent) * 100 + (this.props.disabled ? -1 : 0) + '%' }); styles.percentZeroRemaining.width = styles.remaining.width - styles.percentZeroRemaining.left; return styles; }, 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 gutter = (this.getTheme().handleSizeDisabled + this.getTheme().trackSize) / 2; var fillGutter = this.getTheme().handleSizeDisabled - this.getTheme().trackSize; var styles = this.getStyles(); var sliderStyles = this.mergeAndPrefix(styles.root, this.props.style); var trackStyles = styles.track; var filledStyles = styles.filled; var remainingStyles = this.mergeAndPrefix(styles.remaining, percent === 0 && styles.percentZeroRemaining); var handleStyles = percent === 0 ? this.mergeAndPrefix(styles.handle, styles.handleWhenPercentZero, this.state.active && styles.handleWhenActive, this.state.focused && { outline: 'none' }, this.state.hovered && styles.handleWhenPercentZeroAndHovered, this.props.disabled && styles.handleWhenDisabledAndZero) : this.mergeAndPrefix(styles.handle, this.state.active && styles.handleWhenActive, this.state.focused && { outline: 'none' }, this.props.disabled && styles.handleWhenDisabled); var rippleStyle = { height: '12px', width: '12px' }; if ((this.state.hovered || this.state.focused) && !this.props.disabled) { remainingStyles.backgroundColor = this.getTheme().trackColorSelected; } if (percent === 0) filledStyles.marginRight = gutter; if (this.state.percent === 0 && this.state.active) remainingStyles.marginLeft = fillGutter; var rippleShowCondition = (this.state.hovered || this.state.focused) && !this.state.active && this.state.percent !== 0; var rippleColor = this.state.percent === 0 ? this.getTheme().handleColorZero : this.getTheme().rippleColor; var focusRipple = undefined; if (!this.props.disabled && !this.props.disableFocusRipple) { focusRipple = React.createElement(FocusRipple, { ref: 'focusRipple', key: 'focusRipple', style: rippleStyle, innerStyle: styles.ripples, show: rippleShowCondition, color: rippleColor }); } return React.createElement( 'div', _extends({}, others, { style: this.props.style }), React.createElement('span', { className: 'mui-input-highlight' }), React.createElement('span', { className: 'mui-input-bar' }), React.createElement( 'span', { className: 'mui-input-description' }, this.props.description ), React.createElement( 'span', { className: 'mui-input-error' }, this.props.error ), React.createElement( 'div', { style: sliderStyles, onFocus: this._onFocus, onBlur: this._onBlur, onMouseDown: this._onMouseDown, onMouseEnter: this._onMouseEnter, onMouseLeave: this._onMouseLeave, onMouseUp: this._onMouseUp }, React.createElement( 'div', { ref: 'track', style: trackStyles }, React.createElement('div', { style: filledStyles }), React.createElement('div', { style: remainingStyles }), React.createElement( Draggable, { axis: 'x', bound: 'point', cancel: this.props.disabled ? '*' : null, start: { x: percent * 100 + '%' }, constrain: this._constrain(), onStart: this._onDragStart, onStop: this._onDragStop, onDrag: this._onDragUpdate, onMouseDown: this._onMouseDownKnob }, React.createElement( 'div', { style: handleStyles, tabIndex: 0 }, focusRipple ) ) ) ), React.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 }) ); }, 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) { var value = this._alignValue(this._percentToValue(percent)); this.setState({ value: value, percent: percent }); }, clearValue: function clearValue() { this.setValue(this.props.min); }, _alignValue: function _alignValue(val) { var _props = this.props; var step = _props.step; var min = _props.min; var valModStep = (val - min) % step; var alignValue = val - valModStep; if (Math.abs(valModStep) * 2 >= step) { alignValue += valModStep > 0 ? step : -step; } return parseFloat(alignValue.toFixed(5)); }, _constrain: function _constrain() { var _this = this; var _props2 = this.props; var min = _props2.min; var max = _props2.max; var step = _props2.step; return function (pos) { var pixelMax = React.findDOMNode(_this.refs.track).clientWidth; var pixelStep = pixelMax / ((max - min) / step); var cursor = min; var i = undefined; for (i = 0; i < (max - min) / step; i++) { var distance = pos.left - cursor; var nextDistance = cursor + pixelStep - pos.left; if (Math.abs(distance) > Math.abs(nextDistance)) { cursor += pixelStep; } else { break; } } return { left: cursor }; }; }, _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) { this._pos = e.clientX; }, _onMouseEnter: function _onMouseEnter() { this.setState({ hovered: true }); }, _onMouseLeave: function _onMouseLeave() { this.setState({ hovered: false }); }, _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 - React.findDOMNode(this).getBoundingClientRect().left; this._dragX(e, pos); } this._pos = undefined; }, _onMouseDownKnob: function _onMouseDownKnob() { if (!this.props.disabled) this.setState({ active: true }); }, _onDragStart: function _onDragStart(e, ui) { this.setState({ dragging: true, active: true }); if (this.props.onDragStart) this.props.onDragStart(e, ui); }, _onDragStop: function _onDragStop(e, ui) { this.setState({ dragging: false, active: false }); if (this.props.onDragStop) this.props.onDragStop(e, ui); }, _onDragUpdate: function _onDragUpdate(e, ui) { if (!this.state.dragging) return; if (!this.props.disabled) this._dragX(e, ui.position.left); }, _dragX: function _dragX(e, pos) { var max = React.findDOMNode(this.refs.track).clientWidth; if (pos < 0) pos = 0;else if (pos > max) pos = max; if (pos === this.props.min) { return this._updateWithChangeEvent(e, 0); } this._updateWithChangeEvent(e, pos / max); }, _updateWithChangeEvent: function _updateWithChangeEvent(e, percent) { if (this.state.percent === percent) return; this.setPercent(percent); var value = this._alignValue(this._percentToValue(percent)); if (this.props.onChange) this.props.onChange(e, value); }, _percentToValue: function _percentToValue(percent) { return percent * (this.props.max - this.props.min) + this.props.min; } }); module.exports = Slider;