nw-react-slider--bki
Version:
Slider Component
311 lines (271 loc) • 10 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,
tickShape: React.PropTypes.string,
onChange: React.PropTypes.func,
onDragStart: React.PropTypes.func,
onDragEnd: React.PropTypes.func,
markerLabel: React.PropTypes.array,
markerLabelPosition: React.PropTypes.string,
fill: React.PropTypes.bool
},
getDefaultProps: function getDefaultProps() {
return {
value: 0,
min: 0,
max: 10,
ticks: false
};
},
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);
}
},
componentWillUpdate: function componentWillUpdate(nextProps, nextState) {
if (isUndefined(this.state.position) && !isUndefined(nextState.position)) {
this.props.onChange(nextState.value, nextState.position);
}
},
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 });
if (isFunction(this.props.onChange)) {
this.props.onChange(value, 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);
}
// fire change event if callback exists
if (isFunction(this.props.onChange)) {
var rtposition = position;
if (this.state.dragging) {
rtposition = currentPosition;
}
this.props.onChange(value, rtposition);
}
// Although set state is async, pushing its invocation as late as possible
this.setState({ value: value, position: position });
return 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
};
},
clickOnTrack: function clickOnTrack(event) {
var clickFromLeft = event.clientX - this.cumulativeOffset(event.target).left;
this.updateValueFromPosition(clickFromLeft);
},
handleUp: function handleUp(event, ui) {
var position = this.state.position;
// Do we have a drag end hook ?
if (isFunction(this.props.onDragEnd)) {
this.props.onDragEnd(position);
}
this.setState({ dragging: false });
this.updateValueFromPosition(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;
this.updateValueFromPosition(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 slider__ticks--' + this.props.tickShape, 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 && 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
)
));
}
}
return React.createElement(
'div',
{ key: 'markers', className: 'slider__markers slider__markers--' + this.props.markerLabelPosition, onClick: this.clickOnTrack },
elements
);
},
renderFill: function renderFill() {
if (!this.props.fill) {
return;
}
return React.createElement('div', {
className: 'slider__fill',
style: {
right: this.state.trackWidth - this.state.position + 'px'
} });
},
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(),
this.renderFill()
)
);
}
});
//# sourceMappingURL=slider-core.js.map