/*jshint browser:true */ /*globals React, App */ (function () { 'use strict'; /** * Display a scrollbar * * @prop {current: int} Current position * @prop {max: int} Maximum number we can reach * @prop {onChange: function} Called on scrolling attempt * @prop {onDragStart: function} Called on start of scrolling * @prop {onDragEnd: function} Called on end of scrolling */ App.components.create('UI', { displayName: 'Scroll', mixins: [React.addons.PureRenderMixin], position: null, dragging: false, propTypes: { current: React.PropTypes.number, max: React.PropTypes.number, onChange: React.PropTypes.func, onDragStart: React.PropTypes.func, onDragEnd: React.PropTypes.func }, getDefaultProps: function () { return { current: 0, max: 0, onChange: function () {}, onDragStart: function () {}, onDragEnd: function () {} }; }, componentDidMount: function () { window.addEventListener('mousemove', this.mouseMove); window.addEventListener('mouseup', this.mouseUp); }, componentWillUnmount: function () { window.removeEventListener('mousemove', this.mouseMove); window.removeEventListener('mouseup', this.mouseUp); }, /** * Convert distance to value */ convert: function (input) { return input * this.props.max / this.getDOMNode().clientHeight; }, /** * Convert value to distance */ reverseConvert: function (output) { return output * this.getDOMNode().clientHeight / this.props.max; }, /** * Scroll by given position * * @param {position: int} Current position */ scrollBy: function (position) { var current = this.props.max - this.props.current, delta = this.convert(position - this.reverseConvert(current)); this.props.onChange(delta); }, /** * Start scroll dragging */ mouseDown: function (e) { if (e.button !== 0) { return; } this.position = e.clientY; this.props.onDragStart(); }, /** * Move scroll */ mouseMove: function (e) { if (this.position === null) { return; } if (Math.abs(e.clientY - this.position) >= 3) { this.dragging = true; } if (!this.dragging) { return; } this.scrollBy(e.clientY); }, /** * Mouse release */ mouseUp: function (e) { if (this.dragging) { this.props.onDragEnd(); this.dragging = false; this.position = null; } }, /** * Click on track */ click: function (e) { if (e.button === 0) { this.scrollBy(e.clientY); } }, /** * Render scroll */ render: function () { var bottom; if (this.props.max === 0) { bottom = '-100%'; } else { bottom = 'calc(100% / ' + this.props.max + ' * ' + this.props.current + ')'; } return React.DOM.div({ className: 'scroll', onClick: this.click }, [ React.DOM.span({ key: 'position', className: 'position', onMouseDown: this.mouseDown, style: { bottom: bottom } }) ]); } }); }());