UNPKG

react-native-slider

Version:

A pure JavaScript <Slider /> component for react-native

472 lines (316 loc) 13.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;};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 _require= require('react-native');var Animated=_require.Animated;var PropTypes=_require.PropTypes;var StyleSheet=_require.StyleSheet;var PanResponder=_require.PanResponder;var View=_require.View; var shallowCompare=require('react-addons-shallow-compare'); var styleEqual=require('style-equal'); var TRACK_SIZE=4; var THUMB_SIZE=20; function Rect(x,y,width,height){ this.x=x; this.y=y; this.width=width; this.height=height;} Rect.prototype.containsPoint=function(x,y){ return x>=this.x&& y>=this.y&& x<=this.x+this.width&& y<=this.y+this.height;}; var Slider=React.createClass({displayName:'Slider', propTypes:{ /** * Initial value of the slider. The value should be between minimumValue * and maximumValue, which default to 0 and 1 respectively. * Default value is 0. * * *This is not a controlled component*, e.g. if you don't update * the value, the component won't be reset to its inital value. */ value:PropTypes.number, /** * If true the user won't be able to move the slider. * Default value is false. */ disabled:PropTypes.bool, /** * Initial minimum value of the slider. Default value is 0. */ minimumValue:PropTypes.number, /** * Initial maximum value of the slider. Default value is 1. */ maximumValue:PropTypes.number, /** * Step value of the slider. The value should be between 0 and * (maximumValue - minimumValue). Default value is 0. */ step:PropTypes.number, /** * The color used for the track to the left of the button. Overrides the * default blue gradient image. */ minimumTrackTintColor:PropTypes.string, /** * The color used for the track to the right of the button. Overrides the * default blue gradient image. */ maximumTrackTintColor:PropTypes.string, /** * The color used for the thumb. */ thumbTintColor:PropTypes.string, /** * The size of the touch area that allows moving the thumb. * The touch area has the same center has the visible thumb. * This allows to have a visually small thumb while still allowing the user * to move it easily. * The default is {width: 40, height: 40}. */ thumbTouchSize:PropTypes.shape( {width:PropTypes.number,height:PropTypes.number}), /** * Callback continuously called while the user is dragging the slider. */ onValueChange:PropTypes.func, /** * Callback called when the user starts changing the value (e.g. when * the slider is pressed). */ onSlidingStart:PropTypes.func, /** * Callback called when the user finishes changing the value (e.g. when * the slider is released). */ onSlidingComplete:PropTypes.func, /** * The style applied to the slider container. */ style:View.propTypes.style, /** * The style applied to the track. */ trackStyle:View.propTypes.style, /** * The style applied to the thumb. */ thumbStyle:View.propTypes.style, /** * Set this to true to visually see the thumb touch rect in green. */ debugTouchArea:PropTypes.bool}, getInitialState:function getInitialState(){ return { containerSize:{width:0,height:0}, trackSize:{width:0,height:0}, thumbSize:{width:0,height:0}, allMeasured:false, value:new Animated.Value(this.props.value)};}, getDefaultProps:function getDefaultProps(){ return { value:0, minimumValue:0, maximumValue:1, step:0, minimumTrackTintColor:'#3f3f3f', maximumTrackTintColor:'#b3b3b3', thumbTintColor:'#343434', thumbTouchSize:{width:40,height:40}, debugTouchArea:false};}, componentWillMount:function componentWillMount(){ this._panResponder=PanResponder.create({ onStartShouldSetPanResponder:this._handleStartShouldSetPanResponder, onMoveShouldSetPanResponder:this._handleMoveShouldSetPanResponder, onPanResponderGrant:this._handlePanResponderGrant, onPanResponderMove:this._handlePanResponderMove, onPanResponderRelease:this._handlePanResponderEnd, onPanResponderTerminationRequest:this._handlePanResponderRequestEnd, onPanResponderTerminate:this._handlePanResponderEnd});}, componentWillReceiveProps:function componentWillReceiveProps(nextProps){ var oldValue=this.props.value; var newValue=nextProps.value; if(oldValue!==newValue){ this._setCurrentValue(nextProps.value);}}, shouldComponentUpdate:function shouldComponentUpdate(nextProps,nextState){ // We don't want to re-render in the following cases: // - when only the 'value' prop changes as it's already handled with the Animated.Value // - when the event handlers change (rendering doesn't depend on them) // - when the style props haven't actually change return shallowCompare( {props:this._getPropsForComponentUpdate(this.props),state:this.state}, this._getPropsForComponentUpdate(nextProps), nextState)|| !styleEqual(this.props.style,nextProps.style)|| !styleEqual(this.props.trackStyle,nextProps.trackStyle)|| !styleEqual(this.props.thumbStyle,nextProps.thumbStyle);}, render:function render(){var _props= this.props;var minimumValue=_props.minimumValue;var maximumValue=_props.maximumValue;var minimumTrackTintColor=_props.minimumTrackTintColor;var maximumTrackTintColor=_props.maximumTrackTintColor;var thumbTintColor=_props.thumbTintColor;var styles=_props.styles;var style=_props.style;var trackStyle=_props.trackStyle;var thumbStyle=_props.thumbStyle;var debugTouchArea=_props.debugTouchArea;var other=_objectWithoutProperties(_props,['minimumValue','maximumValue','minimumTrackTintColor','maximumTrackTintColor','thumbTintColor','styles','style','trackStyle','thumbStyle','debugTouchArea']);var _state= this.state;var value=_state.value;var containerSize=_state.containerSize;var trackSize=_state.trackSize;var thumbSize=_state.thumbSize;var allMeasured=_state.allMeasured; var mainStyles=styles||defaultStyles; var thumbLeft=value.interpolate({ inputRange:[minimumValue,maximumValue], outputRange:[0,containerSize.width-thumbSize.width]}); //extrapolate: 'clamp', var valueVisibleStyle={}; if(!allMeasured){ valueVisibleStyle.opacity=0;} var minimumTrackStyle=_extends({ position:'absolute', width:Animated.add(thumbLeft,thumbSize.width/2), marginTop:-trackSize.height, backgroundColor:minimumTrackTintColor}, valueVisibleStyle); var touchOverflowStyle=this._getTouchOverflowStyle(); return ( React.createElement(View,_extends({},other,{style:[mainStyles.container,style],onLayout:this._measureContainer}), React.createElement(View,{ style:[{backgroundColor:maximumTrackTintColor},mainStyles.track,trackStyle], onLayout:this._measureTrack}), React.createElement(Animated.View,{style:[mainStyles.track,trackStyle,minimumTrackStyle]}), React.createElement(Animated.View,{ onLayout:this._measureThumb, style:[ {backgroundColor:thumbTintColor,marginTop:-(trackSize.height+thumbSize.height)/2}, mainStyles.thumb,thumbStyle,_extends({left:thumbLeft},valueVisibleStyle)]}), React.createElement(View,_extends({ style:[defaultStyles.touchArea,touchOverflowStyle]}, this._panResponder.panHandlers), debugTouchArea===true&&this._renderDebugThumbTouchRect(thumbLeft))));}, _getPropsForComponentUpdate:function _getPropsForComponentUpdate(props){var value= props.value;var onValueChange=props.onValueChange;var onSlidingStart=props.onSlidingStart;var onSlidingComplete=props.onSlidingComplete;var style=props.style;var trackStyle=props.trackStyle;var thumbStyle=props.thumbStyle;var otherProps=_objectWithoutProperties(props,['value','onValueChange','onSlidingStart','onSlidingComplete','style','trackStyle','thumbStyle']); return otherProps;}, _handleStartShouldSetPanResponder:function _handleStartShouldSetPanResponder(e){ // Should we become active when the user presses down on the thumb? return this._thumbHitTest(e);}, _handleMoveShouldSetPanResponder:function _handleMoveShouldSetPanResponder(){ // Should we become active when the user moves a touch over the thumb? return false;}, _handlePanResponderGrant:function _handlePanResponderGrant() /*e: Object, gestureState: Object*/{ this._previousLeft=this._getThumbLeft(this._getCurrentValue()); this._fireChangeEvent('onSlidingStart');}, _handlePanResponderMove:function _handlePanResponderMove(e,gestureState){ if(this.props.disabled){ return;} this._setCurrentValue(this._getValue(gestureState)); this._fireChangeEvent('onValueChange');}, _handlePanResponderRequestEnd:function _handlePanResponderRequestEnd(e,gestureState){ // Should we allow another component to take over this pan? return false;}, _handlePanResponderEnd:function _handlePanResponderEnd(e,gestureState){ if(this.props.disabled){ return;} this._setCurrentValue(this._getValue(gestureState)); this._fireChangeEvent('onSlidingComplete');}, _measureContainer:function _measureContainer(x){ this._handleMeasure('containerSize',x);}, _measureTrack:function _measureTrack(x){ this._handleMeasure('trackSize',x);}, _measureThumb:function _measureThumb(x){ this._handleMeasure('thumbSize',x);}, _handleMeasure:function _handleMeasure(name,x){var _x$nativeEvent$layout= x.nativeEvent.layout;var width=_x$nativeEvent$layout.width;var height=_x$nativeEvent$layout.height; var size={width:width,height:height}; var storeName='_'+name; var currentSize=this[storeName]; if(currentSize&&width===currentSize.width&&height===currentSize.height){ return;} this[storeName]=size; if(this._containerSize&&this._trackSize&&this._thumbSize){ this.setState({ containerSize:this._containerSize, trackSize:this._trackSize, thumbSize:this._thumbSize, allMeasured:true});}}, _getRatio:function _getRatio(value){ return (value-this.props.minimumValue)/(this.props.maximumValue-this.props.minimumValue);}, _getThumbLeft:function _getThumbLeft(value){ var ratio=this._getRatio(value); return ratio*(this.state.containerSize.width-this.state.thumbSize.width);}, _getValue:function _getValue(gestureState){ var length=this.state.containerSize.width-this.state.thumbSize.width; var thumbLeft=this._previousLeft+gestureState.dx; var ratio=thumbLeft/length; if(this.props.step){ return Math.max(this.props.minimumValue, Math.min(this.props.maximumValue, this.props.minimumValue+Math.round(ratio*(this.props.maximumValue-this.props.minimumValue)/this.props.step)*this.props.step));}else { return Math.max(this.props.minimumValue, Math.min(this.props.maximumValue, ratio*(this.props.maximumValue-this.props.minimumValue)+this.props.minimumValue));}}, _getCurrentValue:function _getCurrentValue(){ return this.state.value.__getValue();}, _setCurrentValue:function _setCurrentValue(value){ this.state.value.setValue(value);}, _fireChangeEvent:function _fireChangeEvent(event){ if(this.props[event]){ this.props[event](this._getCurrentValue());}}, _getTouchOverflowSize:function _getTouchOverflowSize(){ var state=this.state; var props=this.props; var size={}; if(state.allMeasured===true){ size.width=Math.max(0,props.thumbTouchSize.width-state.thumbSize.width); size.height=Math.max(0,props.thumbTouchSize.height-state.containerSize.height);} return size;}, _getTouchOverflowStyle:function _getTouchOverflowStyle(){var _getTouchOverflowSize2= this._getTouchOverflowSize();var width=_getTouchOverflowSize2.width;var height=_getTouchOverflowSize2.height; var touchOverflowStyle={}; if(width!==undefined&&height!==undefined){ var verticalMargin=-height/2; touchOverflowStyle.marginTop=verticalMargin; touchOverflowStyle.marginBottom=verticalMargin; var horizontalMargin=-width/2; touchOverflowStyle.marginLeft=horizontalMargin; touchOverflowStyle.marginRight=horizontalMargin;} if(this.props.debugTouchArea===true){ touchOverflowStyle.backgroundColor='orange'; touchOverflowStyle.opacity=0.5;} return touchOverflowStyle;}, _thumbHitTest:function _thumbHitTest(e){ var nativeEvent=e.nativeEvent; var thumbTouchRect=this._getThumbTouchRect(); return thumbTouchRect.containsPoint(nativeEvent.locationX,nativeEvent.locationY);}, _getThumbTouchRect:function _getThumbTouchRect(){ var state=this.state; var props=this.props; var touchOverflowSize=this._getTouchOverflowSize(); return new Rect( touchOverflowSize.width/2+this._getThumbLeft(this._getCurrentValue())+(state.thumbSize.width-props.thumbTouchSize.width)/2, touchOverflowSize.height/2+(state.containerSize.height-props.thumbTouchSize.height)/2, props.thumbTouchSize.width, props.thumbTouchSize.height);}, _renderDebugThumbTouchRect:function _renderDebugThumbTouchRect(thumbLeft){ var thumbTouchRect=this._getThumbTouchRect(); var positionStyle={ left:thumbLeft, top:thumbTouchRect.y, width:thumbTouchRect.width, height:thumbTouchRect.height}; return ( React.createElement(Animated.View,{ style:[defaultStyles.debugThumbTouchArea,positionStyle], pointerEvents:'none'}));}}); var defaultStyles=StyleSheet.create({ container:{ height:40, justifyContent:'center'}, track:{ height:TRACK_SIZE, borderRadius:TRACK_SIZE/2}, thumb:{ position:'absolute', width:THUMB_SIZE, height:THUMB_SIZE, borderRadius:THUMB_SIZE/2}, touchArea:{ position:'absolute', backgroundColor:'transparent', top:0, left:0, right:0, bottom:0}, debugThumbTouchArea:{ position:'absolute', backgroundColor:'green', opacity:0.5}}); module.exports=Slider;