nw-react-slider
Version:
Slider Component
317 lines (272 loc) • 10.2 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; };
var React = require('react');
var ReactDOM = require('react-dom');
var isFunction = require('lodash/isFunction');
var Draggable = require('react-draggable');
var isUndefined = require('lodash/isUndefined');
var throttle = require('lodash/throttle');
module.exports = React.createClass({
displayName: 'core-slider',
propTypes: {
value: React.PropTypes.number,
min: React.PropTypes.number,
max: React.PropTypes.number,
ticks: React.PropTypes.bool,
triggerOnChangeWhileDragging: React.PropTypes.bool,
onChange: React.PropTypes.func,
onDragStart: React.PropTypes.func,
onDragEnd: React.PropTypes.func,
markerLabel: React.PropTypes.array
},
getDefaultProps: function getDefaultProps() {
return {
value: 0,
min: 0,
max: 10,
ticks: false,
triggerOnChangeWhileDragging: true
};
},
getInitialState: function getInitialState() {
return {
position: undefined,
value: this.props.value,
dragging: false
};
},
componentWillReceiveProps: function componentWillReceiveProps(nextProps, nextState) {
var newValue;
// keep state up to date with passed in props
if (this.state.value !== nextProps.value) {
newValue = this.getBoundValue(nextProps, nextProps.value);
this.setState({ value: newValue });
this.setHandlePosition(nextProps, newValue);
}
// if min or max changes, have to reposition the handle
if (this.props.min !== nextProps.min || this.props.max !== nextProps.max) {
newValue = this.getBoundValue(nextProps, newValue || this.state.value);
this.setState({ value: newValue });
this.setHandlePosition(nextProps, newValue);
}
},
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
// Don't alter the component while dragging is occurring
return !nextState.dragging;
},
componentDidMount: function componentDidMount() {
this.updateTrackWidth();
this.updateTrackWidth = throttle(this.updateTrackWidth, 100, { leading: false });
window.addEventListener('resize', this.updateTrackWidth);
},
componentWillUnmount: function componentWillUnmount() {
window.removeEventListener('resize', this.updateTrackWidth);
},
getBoundValue: function getBoundValue(props, value) {
var newValue = value;
if (newValue < props.min) {
newValue = props.min;
} else if (newValue > props.max) {
newValue = props.max;
}
return newValue;
},
updateTrackWidth: function updateTrackWidth() {
var track = ReactDOM.findDOMNode(this.refs.track);
if (!track) {
return;
}
var trackWidth = track.offsetWidth;
this.setState({ trackWidth: trackWidth }, this.setHandlePosition);
},
componentDidUpdate: function componentDidUpdate() {
// after a render, ensure that draggable is in correct position
this.refs.drag && this.refs.drag.setState({ clientX: this.state.position });
},
setHandlePosition: function setHandlePosition() {
var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];
var value = arguments.length <= 1 || arguments[1] === undefined ? this.state.value : arguments[1];
var position = this.state.trackWidth / (props.max - props.min) * (value - props.min);
this.setState({ position: position });
},
updateValueFromPosition: function updateValueFromPosition(newPosition) {
var currentPosition = newPosition;
var value, position;
if (this.props.max === this.props.min) {
value = this.props.min;
position = this.state.trackWidth / 2;
} else {
// find the two closest ticks to the current position
var currentPercent = currentPosition / this.state.trackWidth * 100;
var percentStep = 100 / (this.props.max - this.props.min);
var closestSmallerValue = Math.floor(currentPercent / percentStep);
var closestLargerValue = closestSmallerValue + 1;
var bestMatchPercent, bestMatchTick;
// determine which of the two values is closest
if (currentPercent - closestSmallerValue * percentStep <= closestLargerValue * percentStep - currentPercent) {
bestMatchTick = closestSmallerValue;
bestMatchPercent = bestMatchTick * percentStep;
} else {
bestMatchTick = closestLargerValue;
bestMatchPercent = bestMatchTick * percentStep;
}
// update the value and position
value = this.props.min + bestMatchTick;
position = this.state.trackWidth * (bestMatchPercent / 100);
}
// Although set state is async, pushing its invocation as late as possible
this.setState({ value: value, position: position });
return {
value: value,
position: position
};
},
cumulativeOffset: function cumulativeOffset(element) {
// determine the overall offset of the element by crawling up the DOM, borrowed from Prototype.js
var top = 0;
var left = 0;
do {
top += element.offsetTop || 0;
left += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return {
top: top,
left: left
};
},
triggerOnChange: function triggerOnChange(pos) {
var _updateValueFromPosit = this.updateValueFromPosition(pos);
var value = _updateValueFromPosit.value;
var position = _updateValueFromPosit.position;
if (isFunction(this.props.onChange)) {
this.props.onChange(value, position);
}
},
clickOnTrack: function clickOnTrack(event) {
var clickFromLeft = event.clientX - this.cumulativeOffset(event.target).left;
this.triggerOnChange(clickFromLeft);
},
handleUp: function handleUp(event, ui) {
var pos = this.refs.drag.state.clientX || 0;
var _updateValueFromPosit2 = this.updateValueFromPosition(pos);
var position = _updateValueFromPosit2.position;
// Do we have a drag end hook ?
if (isFunction(this.props.onDragEnd)) {
this.props.onDragEnd(position);
}
this.setState({ dragging: false });
this.triggerOnChange(position);
},
handleDown: function handleDown(event, ui) {
// Do we have a drag start hook ?
if (isFunction(this.props.onDragStart)) {
this.props.onDragStart(this.state.position);
}
this.setState({ dragging: true });
},
dragging: function dragging(event, ui) {
var pos = this.refs.drag.state.clientX || 0;
// Do we want to trigger change handlers while dragging ?
if (this.props.triggerOnChangeWhileDragging) {
this.triggerOnChange(pos);
}
event.preventDefault();
},
renderTicks: function renderTicks() {
if (!this.props.ticks) return React.createElement('span', null);
var elements = [];
var min = this.props.min;
var max = this.props.max;
var percentStep = 100 / (max - min);
// Don't render ticks if it is too high. Will crash the browser and the ticks become useless
if (max - min < 200) {
for (var i = min + 1; i < max; i++) {
var style = {
left: percentStep * (i - min) + '%'
};
elements.push(React.createElement('span', { key: 'tick' + i, className: 'slider__tick', style: style }));
}
}
return React.createElement(
'div',
{ key: 'ticks', className: 'slider__ticks', onClick: this.clickOnTrack },
elements
);
},
renderMarkers: function renderMarkers() {
if (!this.props.markerLabel) return React.createElement('span', null);
var elements = [];
var _props = this.props;
var min = _props.min;
var max = _props.max;
var markers = _props.markerLabel;
var percentStep = 100 / (max - min);
for (var i in markers) {
var style = {
left: percentStep * (markers[i].value - min) + '%'
};
if (markers[i].value <= max && markers[i].value >= min) {
if (this.props.ticks && max - min < 200) {
// don't render a tick for this marker if ticks are already being rendered
elements.push(React.createElement(
'div',
{ key: 'marker' + i, className: 'slider__marker marker', style: style },
React.createElement(
'p',
{ className: 'marker__label' },
markers[i].label
)
));
} else {
elements.push(React.createElement(
'div',
{ key: 'marker' + i, className: 'slider__marker marker', style: style },
React.createElement(
'p',
{ className: 'marker__label' },
markers[i].label
),
React.createElement('span', { key: 'marker' + markers[i].value, className: 'slider__tick slider__tick--marker' })
));
}
}
}
return React.createElement(
'div',
{ key: 'markers', className: 'slider__markers', onClick: this.clickOnTrack },
elements
);
},
render: function render() {
var draggableProps, draggable;
if (!isUndefined(this.state.position)) {
draggableProps = {
axis: 'x',
handle: '.slider__handle',
bounds: { left: 0, right: this.state.trackWidth },
start: { x: this.state.position, y: 0 },
onStop: this.handleUp,
onStart: this.handleDown,
onDrag: this.dragging
};
draggable = React.createElement(
Draggable,
_extends({ ref: 'drag', key: 'draggable' }, draggableProps),
React.createElement('span', { ref: 'handle', className: 'slider__handle' })
);
}
return React.createElement(
'div',
{ ref: 'slider', className: 'slider' },
draggable,
React.createElement(
'div',
{ ref: 'track', className: 'slider__track', onClick: this.clickOnTrack },
this.renderTicks(),
this.renderMarkers()
)
);
}
});
//# sourceMappingURL=slider-core.js.map