react-toolbox/components/slider/index.jsx

278 lines
7.3 KiB
React
Raw Normal View History

import React from 'react';
import ReactDOM from 'react-dom';
2015-10-10 12:25:47 +03:00
import style from './style';
2015-09-09 11:23:19 +03:00
import utils from '../utils';
import ProgressBar from '../progress_bar';
import Input from '../input';
2015-09-09 03:16:05 +03:00
2015-10-22 02:31:17 +03:00
class Slider extends React.Component {
static propTypes = {
2015-09-09 03:16:05 +03:00
className: React.PropTypes.string,
editable: React.PropTypes.bool,
max: React.PropTypes.number,
min: React.PropTypes.number,
onChange: React.PropTypes.func,
pinned: React.PropTypes.bool,
snaps: React.PropTypes.bool,
step: React.PropTypes.number,
value: React.PropTypes.number
};
static defaultProps = {
className: '',
editable: false,
max: 100,
min: 0,
pinned: false,
snaps: false,
step: 0.01,
value: 0
};
state = {
inputFocused: false,
inputValue: null,
sliderLength: 0,
sliderStart: 0
};
2015-09-09 03:16:05 +03:00
shouldComponentUpdate (nextProps, nextState) {
if (!this.state.inputFocused && nextState.inputFocused) return false;
if (this.state.inputFocused && this.props.value !== nextProps.value) {
this.setState({inputValue: this.valueForInput(nextProps.value)});
return false;
2015-09-09 03:16:05 +03:00
}
2015-11-09 04:28:01 +03:00
return true;
}
componentDidMount () {
window.addEventListener('resize', this.handleResize);
this.handleResize();
}
2015-09-09 03:16:05 +03:00
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
2015-09-09 03:16:05 +03:00
handleInputFocus = () => {
this.setState({
inputFocused: true,
inputValue: this.valueForInput(this.props.value)
});
};
handleInputChange = (event) => {
this.setState({inputValue: event.target.value});
};
2015-09-09 03:16:05 +03:00
2015-11-02 18:38:35 +03:00
handleInputBlur = () => {
const value = this.state.inputValue || 0;
this.setState({inputFocused: false, inputValue: null}, () => {
this.props.onChange(this.trimValue(value));
});
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
2015-10-22 02:31:17 +03:00
handleKeyDown = (event) => {
2015-11-02 18:38:35 +03:00
if ([13, 27].indexOf(event.keyCode) !== -1) {
this.refs.input.blur();
ReactDOM.findDOMNode(this).blur();
}
2015-09-09 03:16:05 +03:00
if (event.keyCode === 38) this.addToValue(this.props.step);
if (event.keyCode === 40) this.addToValue(-this.props.step);
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
2015-10-22 02:31:17 +03:00
handleMouseDown = (event) => {
if (this.state.inputFocused) this.refs.input.blur();
2015-09-09 03:16:05 +03:00
utils.events.addEventsToDocument(this.getMouseEventMap());
2015-09-09 03:57:53 +03:00
this.start(utils.events.getMousePosition(event));
utils.events.pauseEvent(event);
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
2015-10-22 02:31:17 +03:00
handleMouseMove = (event) => {
2015-09-09 03:16:05 +03:00
utils.events.pauseEvent(event);
this.move(utils.events.getMousePosition(event));
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
2015-10-22 02:31:17 +03:00
handleMouseUp = () => {
2015-09-09 03:16:05 +03:00
this.end(this.getMouseEventMap());
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
handleResize = (event, callback) => {
const {left, right} = ReactDOM.findDOMNode(this.refs.progressbar).getBoundingClientRect();
const cb = callback || () => {};
this.setState({sliderStart: left, sliderLength: right - left}, cb);
};
handleSliderBlur = () => {
utils.events.removeEventsFromDocument(this.getKeyboardEvents());
};
handleSliderFocus = () => {
utils.events.addEventsToDocument(this.getKeyboardEvents());
};
2015-10-22 02:31:17 +03:00
handleTouchEnd = () => {
2015-09-09 03:16:05 +03:00
this.end(this.getTouchEventMap());
2015-10-22 02:31:17 +03:00
};
2015-09-09 03:16:05 +03:00
handleTouchMove = (event) => {
this.move(utils.events.getTouchPosition(event));
};
handleTouchStart = (event) => {
if (this.state.inputFocused) this.refs.input.blur();
this.start(utils.events.getTouchPosition(event));
utils.events.addEventsToDocument(this.getTouchEventMap());
utils.events.pauseEvent(event);
};
addToValue (increment) {
2015-11-09 04:28:01 +03:00
let value = this.state.inputFocused ? parseFloat(this.state.inputValue) : this.props.value;
value = this.trimValue(value + increment);
if (value !== this.props.value) this.props.onChange(value);
}
getKeyboardEvents () {
return {
keydown: this.handleKeyDown
};
}
2015-09-09 03:16:05 +03:00
getMouseEventMap () {
return {
2015-10-22 02:31:17 +03:00
mousemove: this.handleMouseMove,
mouseup: this.handleMouseUp
2015-09-09 03:16:05 +03:00
};
}
2015-09-09 03:16:05 +03:00
getTouchEventMap () {
return {
2015-10-22 02:31:17 +03:00
touchmove: this.handleTouchMove,
touchend: this.handleTouchEnd
2015-09-09 03:16:05 +03:00
};
}
2015-09-09 03:16:05 +03:00
end (revents) {
utils.events.removeEventsFromDocument(revents);
2015-11-09 04:28:01 +03:00
this.setState({ pressed: false });
}
2015-09-09 03:16:05 +03:00
knobOffset () {
const { max, min } = this.props;
return this.state.sliderLength * (this.props.value - min) / (max - min);
}
2015-09-09 03:16:05 +03:00
move (position) {
const newValue = this.positionToValue(position);
if (newValue !== this.props.value) this.props.onChange(newValue);
}
2015-09-09 03:16:05 +03:00
2015-09-09 03:57:53 +03:00
positionToValue (position) {
const { sliderStart: start, sliderLength: length } = this.state;
const { max, min } = this.props;
2015-09-09 03:57:53 +03:00
return this.trimValue((position.x - start) / length * (max - min) + min);
}
2015-09-09 03:16:05 +03:00
start (position) {
this.handleResize(null, () => {
this.setState({pressed: true});
this.props.onChange(this.positionToValue(position));
});
}
2015-09-09 03:16:05 +03:00
stepDecimals () {
return (this.props.step.toString().split('.')[1] || []).length;
}
2015-09-09 03:16:05 +03:00
trimValue (value) {
if (value < this.props.min) return this.props.min;
if (value > this.props.max) return this.props.max;
return utils.round(value, this.stepDecimals());
}
2015-09-09 03:16:05 +03:00
valueForInput (value) {
const decimals = this.stepDecimals();
return decimals > 0 ? value.toFixed(decimals) : value.toString();
}
2015-09-09 03:16:05 +03:00
renderSnaps () {
if (this.props.snaps) {
return (
2015-10-10 12:25:47 +03:00
<div ref='snaps' className={style.snaps}>
2015-11-09 04:28:01 +03:00
{ utils.range(0, (this.props.max - this.props.min) / this.props.step).map(i => {
return <div key={`span-${i}`} className={style.snap}></div>;
}) }
2015-09-09 03:16:05 +03:00
</div>
);
}
}
2015-09-09 03:16:05 +03:00
renderInput () {
if (this.props.editable) {
2015-11-09 04:28:01 +03:00
const value = this.state.inputFocused ? this.state.inputValue : this.valueForInput(this.props.value);
2015-09-09 03:16:05 +03:00
return (
<Input
ref='input'
2015-10-10 12:25:47 +03:00
className={style.input}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
2015-11-02 18:38:35 +03:00
onBlur={this.handleInputBlur}
2015-11-09 04:28:01 +03:00
value={value}
/>
2015-09-09 03:16:05 +03:00
);
}
}
2015-09-09 03:16:05 +03:00
render () {
const knobStyles = utils.prefixer({transform: `translateX(${this.knobOffset()}px)`});
2015-09-09 03:16:05 +03:00
let className = this.props.className;
2015-10-10 12:25:47 +03:00
if (this.props.editable) className += ` ${style.editable}`;
if (this.props.pinned) className += ` ${style.pinned}`;
if (this.state.pressed) className += ` ${style.pressed}`;
if (this.props.value === this.props.min) className += ` ${style.ring}`;
2015-09-09 03:16:05 +03:00
return (
<div
2015-10-10 12:25:47 +03:00
className={style.root + className}
data-react-toolbox='slider'
onBlur={this.handleSliderBlur}
2015-10-22 02:31:17 +03:00
onFocus={this.handleSliderFocus}
tabIndex='0'
>
2015-09-09 03:16:05 +03:00
<div
ref='slider'
2015-10-10 12:25:47 +03:00
className={style.container}
onMouseDown={this.handleMouseDown}
2015-10-22 02:31:17 +03:00
onTouchStart={this.handleTouchStart}
>
2015-09-09 03:16:05 +03:00
<div
ref='knob'
2015-10-10 12:25:47 +03:00
className={style.knob}
2015-10-22 02:31:17 +03:00
onMouseDown={this.handleMouseDown}
onTouchStart={this.handleTouchStart}
style={knobStyles}
>
<div className={style.innerknob} data-value={parseInt(this.props.value)}></div>
2015-09-09 03:16:05 +03:00
</div>
2015-10-10 12:25:47 +03:00
<div className={style.progress}>
2015-09-09 03:16:05 +03:00
<ProgressBar
ref='progressbar'
2015-10-10 12:25:47 +03:00
className={style.innerprogress}
2015-09-09 03:16:05 +03:00
max={this.props.max}
min={this.props.min}
mode='determinate'
value={this.props.value}
/>
2015-09-09 03:16:05 +03:00
{ this.renderSnaps() }
</div>
</div>
{ this.renderInput() }
</div>
);
}
2015-10-21 13:25:07 +03:00
}
2015-10-22 02:31:17 +03:00
export default Slider;