react-native-slider
Version:
A pure JavaScript <Slider /> component for react-native
472 lines (316 loc) • 13.6 kB
JavaScript
'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;