Port slider to ES6
parent
a89f75ae87
commit
0c48f3d114
|
@ -1,149 +0,0 @@
|
|||
TestUtils = React.addons.TestUtils
|
||||
expect = require('expect')
|
||||
sinon = require('sinon')
|
||||
utils = require('../../utils/testing')
|
||||
ProgressBar = require('../../progress_bar')
|
||||
Input = require('../../input')
|
||||
Slider = require('../index')
|
||||
|
||||
describe 'Slider', ->
|
||||
describe '#positionToValue', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 500, sliderLength: 100 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it 'returns min when position is less than origin', ->
|
||||
expect(@slider.positionToValue({x: 400})).toEqual(-500)
|
||||
|
||||
it 'returns max when position is more and origin plus length', ->
|
||||
expect(@slider.positionToValue({x: 900})).toEqual(500)
|
||||
|
||||
it 'returns the proper position when the position is inside slider', ->
|
||||
expect(@slider.positionToValue({x: 520})).toEqual(-300)
|
||||
|
||||
describe '#endPositionToValue', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 500, sliderLength: 100, startPosition: 520, startValue: -300 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it 'returns the proper value when is moved left', ->
|
||||
expect(@slider.endPositionToValue({x: 510})).toEqual(-400)
|
||||
|
||||
it 'returns the proper value when is moved right', ->
|
||||
expect(@slider.endPositionToValue({x: 570})).toEqual(200)
|
||||
|
||||
it 'returns the proper value when is not moved', ->
|
||||
expect(@slider.endPositionToValue({x: 520})).toEqual(-300)
|
||||
|
||||
describe '#trimValue', ->
|
||||
before ->
|
||||
props = { min: 0, max: 100, step: 0.1 }
|
||||
@slider = utils.renderComponent(Slider, props)
|
||||
|
||||
it 'rounds to the proper number', ->
|
||||
expect(@slider.trimValue(57.16)).toEqual(57.2)
|
||||
expect(@slider.trimValue(57.12)).toEqual(57.10)
|
||||
|
||||
it 'returns min if number is less than min', ->
|
||||
expect(@slider.trimValue(-57.16)).toEqual(0)
|
||||
|
||||
it 'returns max if number is more than max', ->
|
||||
expect(@slider.trimValue(257.16)).toEqual(100)
|
||||
|
||||
describe '#valueForInput', ->
|
||||
before ->
|
||||
props = { min: 0, max: 100, step: 0.01 }
|
||||
@slider = utils.renderComponent(Slider, props)
|
||||
|
||||
it 'returns a fixed number when an integer is given', ->
|
||||
expect(@slider.valueForInput(4)).toEqual('4.00')
|
||||
|
||||
it 'returns a fixed number when a float is given', ->
|
||||
expect(@slider.valueForInput(4.06)).toEqual('4.06')
|
||||
|
||||
describe '#calculateKnobOffset', ->
|
||||
it 'returns the corresponding offset for a given value and slider length/start', ->
|
||||
props = { min: -500, max: 500, value: -250 }
|
||||
state = { sliderStart: 500, sliderLength: 100 }
|
||||
slider = utils.renderComponent(Slider, props, state)
|
||||
expect(slider.calculateKnobOffset()).toEqual(25)
|
||||
|
||||
describe '#getValue', ->
|
||||
it 'retrieves the current value', ->
|
||||
slider = utils.renderComponent(Slider, {value: 10})
|
||||
expect(slider.getValue()).toEqual(slider.state.value)
|
||||
|
||||
describe '#setValue', ->
|
||||
it 'set the current value', ->
|
||||
slider = utils.renderComponent(Slider, {value: 10})
|
||||
slider.setValue(50)
|
||||
expect(slider.state.value).toEqual(50)
|
||||
|
||||
describe '#render', ->
|
||||
it "contains a linear progress bar with proper properties", ->
|
||||
slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140})
|
||||
progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar)
|
||||
expect(progress.props.mode).toEqual('determinate')
|
||||
expect(progress.props.type).toEqual('linear')
|
||||
expect(progress.props.value).toEqual(140)
|
||||
expect(progress.props.min).toEqual(100)
|
||||
expect(progress.props.max).toEqual(1000)
|
||||
|
||||
it "contains an input component if its editable", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 130})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
expect(input.props.value).toEqual(slider.props.value)
|
||||
|
||||
it "contains the proper number of snaps when snapped", ->
|
||||
slider = utils.renderComponent(Slider, {snaps: true, step: 10})
|
||||
snaps = slider.refs.snaps
|
||||
expect(snaps.props.children.length).toEqual(10)
|
||||
|
||||
it "has the proper classes for pinned, editable and ring", ->
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true})
|
||||
expect(slider.props.className).toContain("ring")
|
||||
expect(slider.props.className).toContain("pinned")
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50})
|
||||
expect(slider.props.className).toNotContain("ring")
|
||||
|
||||
describe 'events', ->
|
||||
before ->
|
||||
props = { min: -500, max: 500 }
|
||||
state = { sliderStart: 0, sliderLength: 1000 }
|
||||
@slider = utils.renderComponent(Slider, props, state)
|
||||
|
||||
it "sets pressed state when knob is clicked", ->
|
||||
TestUtils.Simulate.mouseDown(@slider.refs.knob)
|
||||
expect(@slider.state.pressed).toEqual(true)
|
||||
|
||||
it "sets pressed state when knob is touched", ->
|
||||
TestUtils.Simulate.touchStart(@slider.refs.knob, {touches: [{pageX: 200}]})
|
||||
expect(@slider.state.pressed).toEqual(true)
|
||||
|
||||
it "sets a proper value when the slider is clicked", ->
|
||||
TestUtils.Simulate.mouseDown(@slider.refs.slider, { pageX: 200 })
|
||||
expect(@slider.state.value).toEqual(-300)
|
||||
|
||||
it "sets a proper value when the slider is touched", ->
|
||||
TestUtils.Simulate.touchStart(@slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]})
|
||||
expect(@slider.state.value).toEqual(-300)
|
||||
|
||||
it "changes its value when input changes", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 50})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}})
|
||||
expect(slider.state.value).toEqual(80)
|
||||
|
||||
it "changes input value when slider changes", ->
|
||||
slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000})
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input)
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 })
|
||||
expect(input.state.value).toEqual(90)
|
||||
|
||||
it "calls onChange callback when the value is changed", ->
|
||||
onChangeSpy = sinon.spy()
|
||||
slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000})
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 })
|
||||
expect(onChangeSpy.called).toEqual(true)
|
|
@ -0,0 +1,187 @@
|
|||
const React = window.React;
|
||||
const TestUtils = React.addons.TestUtils;
|
||||
const expect = require('expect');
|
||||
const sinon = require('sinon');
|
||||
const utils = require('../../utils/testing');
|
||||
|
||||
const ProgressBar = require('../../progress_bar');
|
||||
const Input = require('../../input');
|
||||
const Slider = require('../index');
|
||||
|
||||
describe('Slider', function () {
|
||||
let props, state, slider, progress, input;
|
||||
|
||||
describe('#positionToValue', function () {
|
||||
before(function () {
|
||||
props = { min: -500, max: 500 };
|
||||
state = { sliderStart: 500, sliderLength: 100 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
});
|
||||
|
||||
it('returns min when position is less than origin', function () {
|
||||
expect(slider.positionToValue({x: 400})).toEqual(-500);
|
||||
});
|
||||
|
||||
it('returns max when position is more and origin plus length', function () {
|
||||
expect(slider.positionToValue({x: 900})).toEqual(500);
|
||||
});
|
||||
|
||||
it('returns the proper position when the position is inside slider', function () {
|
||||
expect(slider.positionToValue({x: 520})).toEqual(-300);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#endPositionToValue', function () {
|
||||
before(function () {
|
||||
props = { min: -500, max: 500 };
|
||||
state = { sliderStart: 500, sliderLength: 100, startPosition: 520, startValue: -300 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
});
|
||||
|
||||
it('returns the proper value when is moved left', function () {
|
||||
expect(slider.endPositionToValue({x: 510})).toEqual(-400);
|
||||
});
|
||||
|
||||
it('returns the proper value when is moved right', function () {
|
||||
expect(slider.endPositionToValue({x: 570})).toEqual(200);
|
||||
});
|
||||
|
||||
it('returns the proper value when is not moved', function () {
|
||||
expect(slider.endPositionToValue({x: 520})).toEqual(-300);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trimValue', function () {
|
||||
before(function () {
|
||||
props = { min: 0, max: 100, step: 0.1 };
|
||||
slider = utils.renderComponent(Slider, props);
|
||||
});
|
||||
|
||||
it('rounds to the proper number', function () {
|
||||
expect(slider.trimValue(57.16)).toEqual(57.2);
|
||||
expect(slider.trimValue(57.12)).toEqual(57.10);
|
||||
});
|
||||
|
||||
it('returns min if number is less than min', function () {
|
||||
expect(slider.trimValue(-57.16)).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns max if number is more than max', function () {
|
||||
expect(slider.trimValue(257.16)).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#valueForInput', function () {
|
||||
before(function () {
|
||||
props = { min: 0, max: 100, step: 0.01 };
|
||||
slider = utils.renderComponent(Slider, props);
|
||||
});
|
||||
|
||||
it('returns a fixed number when an integer is given', function () {
|
||||
expect(slider.valueForInput(4)).toEqual('4.00');
|
||||
});
|
||||
|
||||
it('returns a fixed number when a float is given', function () {
|
||||
expect(slider.valueForInput(4.06)).toEqual('4.06');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calculateKnobOffset', function () {
|
||||
it('returns the corresponding offset for a given value and slider length/start', function () {
|
||||
props = { min: -500, max: 500, value: -250 };
|
||||
state = { sliderStart: 500, sliderLength: 100 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
expect(slider.calculateKnobOffset()).toEqual(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getValue', function () {
|
||||
it('retrieves the current value', function () {
|
||||
slider = utils.renderComponent(Slider, {value: 10});
|
||||
expect(slider.getValue()).toEqual(slider.state.value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setValue', function () {
|
||||
it('set the current value', function () {
|
||||
slider = utils.renderComponent(Slider, {value: 10});
|
||||
slider.setValue(50);
|
||||
expect(slider.state.value).toEqual(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#render', function () {
|
||||
it('contains a linear progress bar with proper properties', function () {
|
||||
slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140});
|
||||
progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar);
|
||||
expect(progress.props.mode).toEqual('determinate');
|
||||
expect(progress.props.type).toEqual('linear');
|
||||
expect(progress.props.value).toEqual(140);
|
||||
expect(progress.props.min).toEqual(100);
|
||||
expect(progress.props.max).toEqual(1000);
|
||||
});
|
||||
|
||||
it('contains an input component if its editable', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 130});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
expect(input.props.value).toEqual(slider.props.value);
|
||||
});
|
||||
|
||||
it('contains the proper number of snaps when snapped', function () {
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true});
|
||||
expect(slider.props.className).toContain('ring');
|
||||
expect(slider.props.className).toContain('pinned');
|
||||
slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50});
|
||||
expect(slider.props.className).toNotContain('ring');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#events', function () {
|
||||
before(function () {
|
||||
props = { min: -500, max: 500 };
|
||||
state = { sliderStart: 0, sliderLength: 1000 };
|
||||
slider = utils.renderComponent(Slider, props, state);
|
||||
});
|
||||
|
||||
it('sets pressed state when knob is clicked', function () {
|
||||
TestUtils.Simulate.mouseDown(slider.refs.knob);
|
||||
expect(slider.state.pressed).toEqual(true);
|
||||
});
|
||||
|
||||
it('sets pressed state when knob is touched', function () {
|
||||
TestUtils.Simulate.touchStart(slider.refs.knob, {touches: [{pageX: 200}]});
|
||||
expect(slider.state.pressed).toEqual(true);
|
||||
});
|
||||
|
||||
it('sets a proper value when the slider is clicked', function () {
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 });
|
||||
expect(slider.state.value).toEqual(-300);
|
||||
});
|
||||
|
||||
it('sets a proper value when the slider is touched', function () {
|
||||
TestUtils.Simulate.touchStart(slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]});
|
||||
expect(slider.state.value).toEqual(-300);
|
||||
});
|
||||
|
||||
it('changes its value when input changes', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true, value: 50});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}});
|
||||
expect(slider.state.value).toEqual(80);
|
||||
});
|
||||
|
||||
it('changes input value when slider changes', function () {
|
||||
slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000});
|
||||
input = TestUtils.findRenderedComponentWithType(slider, Input);
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
|
||||
expect(input.state.value).toEqual(90);
|
||||
});
|
||||
|
||||
it('calls onChange callback when the value is changed', function () {
|
||||
let onChangeSpy = sinon.spy();
|
||||
slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000});
|
||||
TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 });
|
||||
expect(onChangeSpy.called).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,245 +0,0 @@
|
|||
localCSS = require './style'
|
||||
prefixer = require "../utils/prefixer"
|
||||
ProgressBar = require "../progress_bar"
|
||||
Input = require "../input"
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
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
|
||||
|
||||
getDefaultProps: ->
|
||||
className : ""
|
||||
editable : false
|
||||
max : 100
|
||||
min : 0
|
||||
pinned : false
|
||||
snaps : false
|
||||
step : 0.01
|
||||
value : 0
|
||||
|
||||
getInitialState: ->
|
||||
sliderStart : 0
|
||||
sliderLength : 0
|
||||
value : @props.value
|
||||
|
||||
# -- Lifecycle
|
||||
componentDidMount: ->
|
||||
@onResize()
|
||||
window.addEventListener('resize', @onResize)
|
||||
|
||||
componentWillUnmount: ->
|
||||
window.removeEventListener('resize', @onResize)
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
if prevState.value != @state.value
|
||||
@props.onChange? @
|
||||
if @state.value != parseFloat(@refs.input?.getValue())
|
||||
@refs.input?.setValue(@valueForInput(@state.value))
|
||||
|
||||
# -- Events
|
||||
onResize: (event) ->
|
||||
sliderBounds = @refs.progressbar.getDOMNode().getBoundingClientRect()
|
||||
@setState
|
||||
sliderStart: sliderBounds['left'],
|
||||
sliderLength: (sliderBounds['right'] - sliderBounds['left'])
|
||||
|
||||
onSliderMouseDown: (event) ->
|
||||
position = _getMousePosition(event)
|
||||
value = @positionToValue(position)
|
||||
@setState value: value, =>
|
||||
@start(position)
|
||||
_addEventsToDocument(@getMouseEventMap())
|
||||
_pauseEvent(event)
|
||||
|
||||
onSliderTouchStart: (event) ->
|
||||
position = _getTouchPosition(event)
|
||||
value = @positionToValue(position)
|
||||
@setState value: value, =>
|
||||
@start(position)
|
||||
_addEventsToDocument(@getTouchEventMap())
|
||||
_pauseEvent(event)
|
||||
|
||||
onSliderFocus: (event) ->
|
||||
_addEventsToDocument(@getKeyboardEvents())
|
||||
|
||||
onSliderBlur: (event) ->
|
||||
_removeEventsFromDocument(@getKeyboardEvents())
|
||||
|
||||
onInputChange: (event) ->
|
||||
@setState value: @trimValue(event.target.value)
|
||||
|
||||
onKeyDown: (event) ->
|
||||
event.stopPropagation()
|
||||
@getDOMNode().blur() if event.keyCode in [13, 27]
|
||||
@addToValue(@props.step) if event.keyCode == 38
|
||||
@addToValue(-@props.step) if event.keyCode == 40
|
||||
|
||||
onMouseDown: (event) ->
|
||||
@start(_getMousePosition(event))
|
||||
_addEventsToDocument(@getMouseEventMap())
|
||||
|
||||
onTouchStart: (event) ->
|
||||
event.stopPropagation()
|
||||
@start(_getTouchPosition(event))
|
||||
_addEventsToDocument(@getTouchEventMap())
|
||||
|
||||
onMouseMove: (event) ->
|
||||
_pauseEvent(event)
|
||||
@move(_getMousePosition(event))
|
||||
|
||||
onTouchMove: (event) ->
|
||||
@move(_getTouchPosition(event))
|
||||
|
||||
onMouseUp: ->
|
||||
@end(@getMouseEventMap())
|
||||
|
||||
onTouchEnd: ->
|
||||
@end(@getTouchEventMap())
|
||||
|
||||
# -- Internal methods
|
||||
getMouseEventMap: ->
|
||||
mousemove: @onMouseMove
|
||||
mouseup: @onMouseUp
|
||||
|
||||
getTouchEventMap: ->
|
||||
touchmove: @onTouchMove
|
||||
touchend: @onTouchEnd
|
||||
|
||||
getKeyboardEvents: ->
|
||||
keydown: @onKeyDown
|
||||
|
||||
positionToValue: (position) ->
|
||||
offset = position.x - @state.sliderStart
|
||||
@trimValue(offset / @state.sliderLength * (@props.max - @props.min) + @props.min)
|
||||
|
||||
start: (position) ->
|
||||
@setState
|
||||
pressed: true
|
||||
startPosition: position.x
|
||||
startValue: @state.value
|
||||
|
||||
move: (position) ->
|
||||
value = @endPositionToValue(position)
|
||||
@setState value: value
|
||||
|
||||
end: (events) ->
|
||||
_removeEventsFromDocument(events)
|
||||
@setState pressed: false
|
||||
|
||||
endPositionToValue: (position) ->
|
||||
offset = position.x - @state.startPosition
|
||||
diffValue = offset / @state.sliderLength * (@props.max - @props.min)
|
||||
@trimValue(diffValue + @state.startValue)
|
||||
|
||||
trimValue: (value) ->
|
||||
value = @props.min if (value < @props.min)
|
||||
value = @props.max if (value > @props.max)
|
||||
_round(value, @stepDecimals())
|
||||
|
||||
stepDecimals: ->
|
||||
(@props.step.toString().split('.')[1] || []).length
|
||||
|
||||
addToValue: (value) ->
|
||||
@setState value: @trimValue(@state.value + value)
|
||||
|
||||
valueForInput: (value) ->
|
||||
decimals = @stepDecimals()
|
||||
if decimals > 0 then value.toFixed(decimals) else value.toString()
|
||||
|
||||
calculateKnobOffset: ->
|
||||
@state.sliderLength * (@state.value - @props.min) / (@props.max - @props.min)
|
||||
|
||||
render: ->
|
||||
className = @props.className
|
||||
className += " editable" if @props.editable
|
||||
className += " pinned" if @props.pinned
|
||||
className += " pressed" if @state.pressed
|
||||
className += " ring" if @state.value == @props.min
|
||||
knobStyles = prefixer(transform: "translateX(#{@calculateKnobOffset()}px)")
|
||||
|
||||
<div className={localCSS.root + className}
|
||||
tabIndex="0"
|
||||
onFocus={@onSliderFocus}
|
||||
onBlur={@onSliderBlur} >
|
||||
|
||||
<div ref="slider"
|
||||
className={localCSS.container}
|
||||
onTouchStart={@onSliderTouchStart}
|
||||
onMouseDown={@onSliderMouseDown} >
|
||||
|
||||
<div ref="knob" className={localCSS.knob} style={knobStyles}
|
||||
onMouseDown={@onMouseDown}
|
||||
onTouchStart={@onTouchStart} >
|
||||
<div className={localCSS.knobInner} data-value={parseInt(@state.value)}></div>
|
||||
</div>
|
||||
|
||||
<div className={localCSS.progress} >
|
||||
<ProgressBar ref="progressbar" mode="determinate"
|
||||
className={localCSS.progressInner}
|
||||
value={@state.value}
|
||||
max={@props.max}
|
||||
min={@props.min}/>
|
||||
{
|
||||
if @props.snaps
|
||||
<div ref="snaps" className={localCSS.snaps}>
|
||||
{
|
||||
for i in [1..((@props.max - @props.min) / @props.step)]
|
||||
<div key="span-#{i}" className={localCSS.snap}></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
if @props.editable
|
||||
<Input ref="input" className={localCSS.input}
|
||||
onChange={@onInputChange}
|
||||
value={@valueForInput(@state.value)} />
|
||||
}
|
||||
</div>
|
||||
|
||||
# -- Extends
|
||||
getValue: ->
|
||||
@state.value
|
||||
|
||||
setValue: (value) ->
|
||||
@setState value: value
|
||||
|
||||
# -- Private methods
|
||||
_pauseEvent = (event) ->
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.returnValue = false
|
||||
event.cancelBubble = true
|
||||
return null
|
||||
|
||||
_getMousePosition = (event) ->
|
||||
x: event.pageX
|
||||
y: event.pageY
|
||||
|
||||
_getTouchPosition = (event) ->
|
||||
x: event.touches[0]['pageX']
|
||||
y: event.touches[0]['pageY']
|
||||
|
||||
_addEventsToDocument = (events) ->
|
||||
document.addEventListener(key, events[key], false) for key of events
|
||||
|
||||
_removeEventsFromDocument = (events) ->
|
||||
document.removeEventListener(key, events[key], false) for key of events
|
||||
|
||||
_round = (n, decimals) ->
|
||||
if (!isNaN(parseFloat(n)) && isFinite(n))
|
||||
decimalPower = Math.pow(10, decimals)
|
||||
return Math.round(parseFloat(n) * decimalPower) / decimalPower
|
||||
return NaN
|
|
@ -0,0 +1,295 @@
|
|||
const React = window.React;
|
||||
const css = require('./style');
|
||||
const utils = require('../utils');
|
||||
|
||||
const ProgressBar = require('../progress_bar');
|
||||
const Input = require('../input');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Slider',
|
||||
|
||||
propTypes: {
|
||||
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
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
className: '',
|
||||
editable: false,
|
||||
max: 100,
|
||||
min: 0,
|
||||
pinned: false,
|
||||
snaps: false,
|
||||
step: 0.01,
|
||||
value: 0
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
sliderStart: 0,
|
||||
sliderLength: 0,
|
||||
value: this.props.value
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
},
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
},
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.value !== this.state.value && this.props.onChange) {
|
||||
this.props.onChange(this);
|
||||
}
|
||||
|
||||
if (this.refs.input) {
|
||||
const inputValue = parseFloat(this.refs.input.getValue());
|
||||
if (this.state.value !== inputValue) {
|
||||
this.refs.input.setValue(this.valueForInput(this.state.value));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onResize () {
|
||||
const bounds = this.refs.progressbar.getDOMNode().getBoundingClientRect();
|
||||
this.setState({
|
||||
sliderStart: bounds.left,
|
||||
sliderLength: bounds.right - bounds.left
|
||||
});
|
||||
},
|
||||
|
||||
onSliderMouseDown (event) {
|
||||
const position = utils.events.getMousePosition(event);
|
||||
const value = this.positionToValue(position);
|
||||
this.setState({value: value}, (function () {
|
||||
this.start(position);
|
||||
utils.events.addEventsToDocument(this.getTouchEventMap());
|
||||
}).bind(this));
|
||||
utils.events.pauseEvent(event);
|
||||
},
|
||||
|
||||
onSliderTouchStart (event) {
|
||||
const position = utils.events.getTouchPosition(event);
|
||||
const value = this.positionToValue(position);
|
||||
this.setState({value: value}, (function () {
|
||||
this.start(position);
|
||||
utils.events.addEventsToDocument(this.getTouchEventMap());
|
||||
}).bind(this));
|
||||
utils.events.pauseEvent(event);
|
||||
},
|
||||
|
||||
onSliderFocus () {
|
||||
utils.events.addEventsToDocument(this.getKeyboardEvents());
|
||||
},
|
||||
|
||||
onSliderBlur () {
|
||||
utils.events.removeEventsFromDocument(this.getKeyboardEvents());
|
||||
},
|
||||
|
||||
onInputChange (event) {
|
||||
this.setState({value: this.trimValue(event.target.value) });
|
||||
},
|
||||
|
||||
onKeyDown (event) {
|
||||
event.stopPropagation();
|
||||
if (event.keyCode in [13, 27]) this.getDOMNode().blur();
|
||||
if (event.keyCode === 38) this.addToValue(this.props.step);
|
||||
if (event.keyCode === 40) this.addToValue(-this.props.step);
|
||||
},
|
||||
|
||||
onMouseDown (event) {
|
||||
this.start(utils.events.getMousePosition(event));
|
||||
utils.events.addEventsToDocument(this.getMouseEventMap());
|
||||
},
|
||||
|
||||
onTouchStart (event) {
|
||||
event.stopPropagation();
|
||||
this.start(utils.events.getTouchPosition(event));
|
||||
utils.events.addEventsToDocument(this.getTouchEventMap());
|
||||
},
|
||||
|
||||
onMouseMove (event) {
|
||||
utils.events.pauseEvent(event);
|
||||
this.move(utils.events.getMousePosition(event));
|
||||
},
|
||||
|
||||
onTouchMove (event) {
|
||||
this.move(utils.events.getTouchPosition(event));
|
||||
},
|
||||
|
||||
onMouseUp () {
|
||||
this.end(this.getMouseEventMap());
|
||||
},
|
||||
|
||||
onTouchEnd () {
|
||||
this.end(this.getTouchEventMap());
|
||||
},
|
||||
|
||||
getMouseEventMap () {
|
||||
return {
|
||||
mousemove: this.onMouseMove,
|
||||
mouseup: this.onMouseUp
|
||||
};
|
||||
},
|
||||
|
||||
getTouchEventMap () {
|
||||
return {
|
||||
touchmove: this.onTouchMove,
|
||||
touchend: this.onTouchEnd
|
||||
};
|
||||
},
|
||||
|
||||
getKeyboardEvents () {
|
||||
return {
|
||||
keydown: this.onKeyDown
|
||||
};
|
||||
},
|
||||
|
||||
positionToValue (position) {
|
||||
const offset = position.x - this.state.sliderStart;
|
||||
return this.trimValue(offset / this.state.sliderLength * (this.props.max - this.props.min) + this.props.min);
|
||||
},
|
||||
|
||||
start (position) {
|
||||
this.setState({
|
||||
pressed: true,
|
||||
startPosition: position.x,
|
||||
startValue: this.state.value
|
||||
});
|
||||
},
|
||||
|
||||
move (position) {
|
||||
const value = this.endPositionToValue(position);
|
||||
this.setState({value: value});
|
||||
},
|
||||
|
||||
end (revents) {
|
||||
utils.events.removeEventsFromDocument(revents);
|
||||
this.setState({pressed: false});
|
||||
},
|
||||
|
||||
endPositionToValue (position) {
|
||||
const offset = position.x - this.state.startPosition;
|
||||
const diffValue = offset / this.state.sliderLength * (this.props.max - this.props.min);
|
||||
return this.trimValue(diffValue + this.state.startValue);
|
||||
},
|
||||
|
||||
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());
|
||||
},
|
||||
|
||||
stepDecimals () {
|
||||
return (this.props.step.toString().split('.')[1] || []).length;
|
||||
},
|
||||
|
||||
addToValue (value) {
|
||||
this.setState({
|
||||
value: this.trimValue(this.state.value + value)
|
||||
});
|
||||
},
|
||||
|
||||
valueForInput (value) {
|
||||
const decimals = this.stepDecimals();
|
||||
return decimals > 0 ? value.toFixed(decimals) : value.toString();
|
||||
},
|
||||
|
||||
calculateKnobOffset () {
|
||||
return this.state.sliderLength * (this.state.value - this.props.min) / (this.props.max - this.props.min);
|
||||
},
|
||||
|
||||
renderSnaps () {
|
||||
if (this.props.snaps) {
|
||||
return (
|
||||
<div ref='snaps' className={css.snaps}>
|
||||
{
|
||||
utils.range(0, (this.props.max - this.props.min) / this.props.step).map(i => {
|
||||
return (<div key={`span-${i}`} className={css.snap}></div>);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderInput () {
|
||||
if (this.props.editable) {
|
||||
return (
|
||||
<Input
|
||||
ref='input'
|
||||
className={css.input}
|
||||
onChange={this.onInputChange}
|
||||
value={this.valueForInput(this.state.value)} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let knobStyles = utils.prefixer({transform: `translateX(${this.calculateKnobOffset()}px)`});
|
||||
let className = this.props.className;
|
||||
if (this.props.editable) className += ' editable';
|
||||
if (this.props.pinned) className += ' pinned';
|
||||
if (this.state.pressed) className += ' pressed';
|
||||
if (this.state.value === this.props.min) className += ' ring';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css.root + className}
|
||||
tabIndex='0'
|
||||
onFocus={this.onSliderFocus}
|
||||
onBlur={this.onSliderBlur} >
|
||||
|
||||
<div
|
||||
ref='slider'
|
||||
className={css.container}
|
||||
onTouchStart={this.onSliderTouchStart}
|
||||
onMouseDown={this.onSliderMouseDown} >
|
||||
|
||||
<div
|
||||
ref='knob'
|
||||
className={css.knob}
|
||||
style={knobStyles}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onTouchStart={this.onTouchStart} >
|
||||
<div className={css.knobInner} data-value={parseInt(this.state.value)}></div>
|
||||
</div>
|
||||
|
||||
<div className={css.progress}>
|
||||
<ProgressBar
|
||||
ref='progressbar'
|
||||
mode='determinate'
|
||||
className={css.progressInner}
|
||||
value={this.state.value}
|
||||
max={this.props.max}
|
||||
min={this.props.min}/>
|
||||
{ this.renderSnaps() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ this.renderInput() }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
getValue () {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
setValue (value) {
|
||||
this.setState({value: value});
|
||||
}
|
||||
});
|
|
@ -22,6 +22,14 @@ module.exports = {
|
|||
return range;
|
||||
},
|
||||
|
||||
round (number, decimals) {
|
||||
if (!isNaN(parseFloat(number)) && isFinite(number)) {
|
||||
let decimalPower = Math.pow(10, decimals);
|
||||
return Math.round(parseFloat(number) * decimalPower) / decimalPower;
|
||||
}
|
||||
return NaN;
|
||||
},
|
||||
|
||||
events: require('./events'),
|
||||
prefixer: require('./prefixer'),
|
||||
time: require('./time'),
|
||||
|
|
Loading…
Reference in New Issue