Skip to content

Commit

Permalink
[Slider] Add keyboard support
Browse files Browse the repository at this point in the history
  • Loading branch information
felipethome committed Feb 8, 2016
1 parent 484f038 commit 1065f9f
Showing 1 changed file with 99 additions and 23 deletions.
122 changes: 99 additions & 23 deletions src/slider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import StylePropable from './mixins/style-propable';
import Transitions from './styles/transitions';
import FocusRipple from './ripples/focus-ripple';
import getMuiTheme from './styles/getMuiTheme';
import autoPrefix from './styles/auto-prefix';

/**
* Verifies min/max range.
Expand Down Expand Up @@ -307,14 +306,6 @@ const Slider = React.createClass({
},


// 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(value) {
let body = document.getElementsByTagName('body')[0];
autoPrefix.set(body.style, 'userSelect', value, this.state.muiTheme);
},

_onHandleTouchStart(e) {
if (document) {
document.addEventListener('touchmove', this._dragTouchHandler, false);
Expand All @@ -323,17 +314,84 @@ const Slider = React.createClass({
document.addEventListener('touchcancel', this._dragTouchEndHandler, false);
}
this._onDragStart(e);

// Cancel scroll and context menu
e.preventDefault();
},

_onHandleMouseDown(e) {
if (document) {
document.addEventListener('mousemove', this._dragHandler, false);
document.addEventListener('mouseup', this._dragEndHandler, false);
this._toggleSelection('none');

// Cancel text selection
e.preventDefault();

// Set focus manually since we called preventDefault()
ReactDOM.findDOMNode(this.refs.handle).focus();
}
this._onDragStart(e);
},

_onHandleKeyDown(e) {
const pageDown = 34;
const pageUp = 33;
const home = 36;
const end = 35;
const leftArrow = 37;
const rightArrow = 39;
const downArrow = 40;
const upArrow = 38;

const decrease =
e.keyCode === pageDown ||
e.keyCode === home ||
e.keyCode === leftArrow ||
e.keyCode === downArrow;

const increase =
e.keyCode === pageUp ||
e.keyCode === end ||
e.keyCode === rightArrow ||
e.keyCode === upArrow;

const {min, max, step} = this.props;

if (increase || decrease) {
let newValue;
let newPercent;

// Cancel scroll
e.preventDefault();

// When pressing home or end the handle should be taken to the
// beginning or end of the track respectively
if (e.keyCode === home) {
newValue = min;
newPercent = 0;
} else if (e.keyCode === end) {
newValue = max;
newPercent = 1;
} else {
newValue = decrease
? Math.max(min, this.state.value - step)
: Math.min(max, this.state.value + step);
newPercent = (newValue - min) / (max - min);
}

// We need to use toFixed() because of float point errors.
// For example, 0.01 + 0.06 = 0.06999999999999999
if (this.state.value !== newValue) {
this.setState({
percent: newPercent,
value: parseFloat(newValue.toFixed(5)),
}, () => {
if (this.props.onChange) this.props.onChange(e, this.state.value);
});
}
}
},

_dragHandler(e) {
if (this._dragRunning) {
return;
Expand All @@ -360,7 +418,6 @@ const Slider = React.createClass({
if (document) {
document.removeEventListener('mousemove', this._dragHandler, false);
document.removeEventListener('mouseup', this._dragEndHandler, false);
this._toggleSelection('');
}

this._onDragStop(e);
Expand Down Expand Up @@ -415,6 +472,17 @@ const Slider = React.createClass({
return parseFloat(alignValue.toFixed(5));
},

_onTouchStart(e) {
if (!this.props.disabled && !this.state.dragging) {
let pos = e.touches[0].clientX - this._getTrackLeft();
this._dragX(e, pos);

// Since the touch event fired for the track and handle is child of
// track, we need to manually propagate the event to the handle.
this._onHandleTouchStart(e);
}
},

_onFocus(e) {
this.setState({focused: true});
if (this.props.onFocus) this.props.onFocus(e);
Expand All @@ -426,7 +494,18 @@ const Slider = React.createClass({
},

_onMouseDown(e) {
if (!this.props.disabled) this._pos = e.clientX;
if (!this.props.disabled && !this.state.dragging) {
let pos = e.clientX - this._getTrackLeft();
this._dragX(e, pos);

// Since the click event fired for the track and handle is child of
// track, we need to manually propagate the event to the handle.
this._onHandleMouseDown(e);
}
},

_onMouseUp() {
if (!this.props.disabled) this.setState({active: false});
},

_onMouseEnter() {
Expand All @@ -441,16 +520,6 @@ const Slider = React.createClass({
return ReactDOM.findDOMNode(this.refs.track).getBoundingClientRect().left;
},

_onMouseUp(e) {
if (!this.props.disabled) this.setState({active: false});
if (!this.state.dragging && Math.abs(this._pos - e.clientX) < 5) {
let pos = e.clientX - this._getTrackLeft();
this._dragX(e, pos);
}

this._pos = undefined;
},

_onDragStart(e) {
this.setState({
dragging: true,
Expand Down Expand Up @@ -544,6 +613,7 @@ const Slider = React.createClass({
handleDragProps = {
onTouchStart: this._onHandleTouchStart,
onMouseDown: this._onHandleMouseDown,
onKeyDown: this._onHandleKeyDown,
};
}

Expand All @@ -559,11 +629,17 @@ const Slider = React.createClass({
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
onMouseUp={this._onMouseUp}
onTouchStart={this._onTouchStart}
>
<div ref="track" style={this.prepareStyles(styles.track)}>
<div style={this.prepareStyles(styles.filled)}></div>
<div style={this.prepareStyles(remainingStyles)}></div>
<div style={this.prepareStyles(handleStyles)} tabIndex={0} {...handleDragProps}>
<div
ref="handle"
style={this.prepareStyles(handleStyles)}
tabIndex={0}
{...handleDragProps}
>
{focusRipple}
</div>
</div>
Expand Down

0 comments on commit 1065f9f

Please sign in to comment.