feat(ui-toolkit): remove slider
BREAKING
This commit is contained in:
parent
0be8553e29
commit
48b9aef8cb
@ -16,7 +16,6 @@ export { default as Divider } from './divider';
|
|||||||
export { default as Editor } from './editor';
|
export { default as Editor } from './editor';
|
||||||
export { default as IconButton } from './icon-button';
|
export { default as IconButton } from './icon-button';
|
||||||
export { default as StatusLoader } from './status-loader';
|
export { default as StatusLoader } from './status-loader';
|
||||||
export { default as Slider } from './slider';
|
|
||||||
|
|
||||||
export { default as Breadcrumb, Item as BreadcrumbItem } from './breadcrumb';
|
export { default as Breadcrumb, Item as BreadcrumbItem } from './breadcrumb';
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
### Double Range Slider
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
const React = require('react');
|
|
||||||
const { default: Slider } = require('./slider');
|
|
||||||
|
|
||||||
<Slider
|
|
||||||
minValue={0.25}
|
|
||||||
maxValue={8}
|
|
||||||
step={0.25}
|
|
||||||
value={{ min: 4, max: 4 }}
|
|
||||||
onChangeComplete={value => console.log(value)}
|
|
||||||
onChange={value => console.log(value)}
|
|
||||||
>
|
|
||||||
vCPUs
|
|
||||||
</Slider>;
|
|
||||||
```
|
|
@ -1,77 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import InputRange from './react-input-range';
|
|
||||||
import remcalc from 'remcalc';
|
|
||||||
|
|
||||||
import FormLabel from '../form/label';
|
|
||||||
|
|
||||||
const Label = styled(FormLabel)`
|
|
||||||
margin-bottom: ${remcalc(10)};
|
|
||||||
margin-top: ${remcalc(12)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
class Slider extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
minValue: this.props.minValue,
|
|
||||||
maxValue: this.props.maxValue,
|
|
||||||
value: this.props.value
|
|
||||||
};
|
|
||||||
|
|
||||||
this.changeValue = this.changeValue.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeValue(value) {
|
|
||||||
this.setState({ value }, () => this.props.onChange(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { minValue, maxValue, value } = this.state;
|
|
||||||
const { children, ...rest } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Label>{children}</Label>
|
|
||||||
<InputRange
|
|
||||||
{...rest}
|
|
||||||
minValue={minValue}
|
|
||||||
maxValue={maxValue}
|
|
||||||
value={value}
|
|
||||||
onChange={value => this.changeValue(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider.propTypes = {
|
|
||||||
minValue: PropTypes.number,
|
|
||||||
maxValue: PropTypes.number,
|
|
||||||
step: PropTypes.number,
|
|
||||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.shape()]),
|
|
||||||
onChangeComplete: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
formatLabel: PropTypes.func,
|
|
||||||
ariaLabelledby: PropTypes.string,
|
|
||||||
ariaControls: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
draggableTrack: PropTypes.bool,
|
|
||||||
onChangeStart: PropTypes.func,
|
|
||||||
children: PropTypes.node,
|
|
||||||
greyed: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
Slider.defaultProps = {
|
|
||||||
onChangeComplete: () => {},
|
|
||||||
onChange: () => {},
|
|
||||||
formatLabel: value =>
|
|
||||||
(value.toString().split('.')[1] || []).length > 3
|
|
||||||
? Math.round(value).toFixed(3)
|
|
||||||
: value,
|
|
||||||
onChangeStart: () => {},
|
|
||||||
step: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Slider;
|
|
@ -1,3 +0,0 @@
|
|||||||
import InputRange from './input-range/input-range';
|
|
||||||
|
|
||||||
export default InputRange;
|
|
@ -1,730 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as valueTransformer from './value-transformer';
|
|
||||||
import rangePropType from './range-prop-type';
|
|
||||||
import valuePropType from './value-prop-type';
|
|
||||||
import Slider from './slider';
|
|
||||||
import Track from './track';
|
|
||||||
import { captialize, distanceTo, isDefined, isObject, length } from '../utils';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import remcalc from 'remcalc';
|
|
||||||
|
|
||||||
export const RangeStyled = styled.div`
|
|
||||||
position: relative;
|
|
||||||
min-height: ${remcalc(10)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A React component that allows users to input numeric values within a range
|
|
||||||
* by dragging its sliders.
|
|
||||||
*/
|
|
||||||
export default class InputRange extends Component {
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @override
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
ariaLabelledby: PropTypes.string,
|
|
||||||
ariaControls: PropTypes.string,
|
|
||||||
classNames: PropTypes.objectOf(PropTypes.string),
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
draggableTrack: PropTypes.bool,
|
|
||||||
formatLabel: PropTypes.func,
|
|
||||||
maxValue: rangePropType,
|
|
||||||
minValue: rangePropType,
|
|
||||||
name: PropTypes.string,
|
|
||||||
onChangeStart: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
onChangeComplete: PropTypes.func,
|
|
||||||
step: PropTypes.number,
|
|
||||||
value: valuePropType
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @override
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
static get defaultProps() {
|
|
||||||
return {
|
|
||||||
disabled: false,
|
|
||||||
maxValue: 10,
|
|
||||||
minValue: 0,
|
|
||||||
step: 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {string} [props.ariaLabelledby]
|
|
||||||
* @param {string} [props.ariaControls]
|
|
||||||
* @param {InputRangeClassNames} [props.classNames]
|
|
||||||
* @param {boolean} [props.disabled = false]
|
|
||||||
* @param {Function} [props.formatLabel]
|
|
||||||
* @param {number|Range} [props.maxValue = 10]
|
|
||||||
* @param {number|Range} [props.minValue = 0]
|
|
||||||
* @param {string} [props.name]
|
|
||||||
* @param {string} props.onChange
|
|
||||||
* @param {Function} [props.onChangeComplete]
|
|
||||||
* @param {Function} [props.onChangeStart]
|
|
||||||
* @param {number} [props.step = 1]
|
|
||||||
* @param {number|Range} props.value
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?number}
|
|
||||||
*/
|
|
||||||
this.startValue = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?Component}
|
|
||||||
*/
|
|
||||||
this.node = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?Component}
|
|
||||||
*/
|
|
||||||
this.trackNode = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {bool}
|
|
||||||
*/
|
|
||||||
this.isSliderDragging = false;
|
|
||||||
|
|
||||||
this.handleSliderDrag = this.handleSliderDrag.bind(this);
|
|
||||||
this.handleTrackDrag = this.handleTrackDrag.bind(this);
|
|
||||||
this.handleTrackMouseDown = this.handleTrackMouseDown.bind(this);
|
|
||||||
this.handleInteractionStart = this.handleInteractionStart.bind(this);
|
|
||||||
this.handleInteractionEnd = this.handleInteractionEnd.bind(this);
|
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
||||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
||||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
||||||
this.handleKeyUp = this.handleKeyUp.bind(this);
|
|
||||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
|
||||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
value: this.props.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @override
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the bounding rect of the track
|
|
||||||
* @private
|
|
||||||
* @return {ClientRect}
|
|
||||||
*/
|
|
||||||
getTrackClientRect() {
|
|
||||||
return this.trackNode && this.trackNode.getClientRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the slider key closest to a point
|
|
||||||
* @private
|
|
||||||
* @param {Point} position
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getKeyByPosition(position) {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const positions = valueTransformer.getPositionsFromValues(
|
|
||||||
values,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isMultiValue()) {
|
|
||||||
const distanceToMin = distanceTo(position, positions.min);
|
|
||||||
const distanceToMax = distanceTo(position, positions.max);
|
|
||||||
|
|
||||||
if (distanceToMin < distanceToMax) {
|
|
||||||
return 'min';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'max';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all the slider keys
|
|
||||||
* @private
|
|
||||||
* @return {string[]}
|
|
||||||
*/
|
|
||||||
getKeys() {
|
|
||||||
if (this.isMultiValue()) {
|
|
||||||
return ['min', 'max'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['max'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the difference between the new and the current value is
|
|
||||||
* greater or equal to the step amount of the component
|
|
||||||
* @private
|
|
||||||
* @param {Range} values
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
hasStepDifference(values) {
|
|
||||||
const currentValues = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
length(values.min, currentValues.min) >= this.props.step ||
|
|
||||||
length(values.max, currentValues.max) >= this.props.step
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the component accepts a min and max value
|
|
||||||
* @private
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isMultiValue() {
|
|
||||||
return isObject(this.props.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the range is within the max and min value of the component
|
|
||||||
* @private
|
|
||||||
* @param {Range} values
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isWithinRange(values) {
|
|
||||||
if (this.isMultiValue()) {
|
|
||||||
return (
|
|
||||||
values.min >= this.props.minValue &&
|
|
||||||
values.max <= this.props.maxValue &&
|
|
||||||
values.min < values.max
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
values.max >= this.props.minValue && values.max <= this.props.maxValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the new value should trigger a render
|
|
||||||
* @private
|
|
||||||
* @param {Range} values
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
shouldUpdate(values) {
|
|
||||||
return this.isWithinRange(values) && this.hasStepDifference(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the position of a slider
|
|
||||||
* @private
|
|
||||||
* @param {string} key
|
|
||||||
* @param {Point} position
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
updatePosition(key, position) {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const positions = valueTransformer.getPositionsFromValues(
|
|
||||||
values,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
|
|
||||||
positions[key] = position;
|
|
||||||
|
|
||||||
this.updatePositions(positions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the positions of multiple sliders
|
|
||||||
* @private
|
|
||||||
* @param {Object} positions
|
|
||||||
* @param {Point} positions.min
|
|
||||||
* @param {Point} positions.max
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
updatePositions(positions) {
|
|
||||||
const values = {
|
|
||||||
min: valueTransformer.getValueFromPosition(
|
|
||||||
positions.min,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
),
|
|
||||||
max: valueTransformer.getValueFromPosition(
|
|
||||||
positions.max,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
const transformedValues = {
|
|
||||||
min: valueTransformer.getStepValueFromValue(values.min, this.props.step),
|
|
||||||
max: valueTransformer.getStepValueFromValue(values.max, this.props.step)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateValues(transformedValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the value of a slider
|
|
||||||
* @private
|
|
||||||
* @param {string} key
|
|
||||||
* @param {number} value
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
updateValue(key, value) {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
|
|
||||||
values[key] = value;
|
|
||||||
|
|
||||||
this.updateValues(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the values of multiple sliders
|
|
||||||
* @private
|
|
||||||
* @param {Range|number} values
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
updateValues(values) {
|
|
||||||
if (!this.shouldUpdate(values)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange(this.isMultiValue() ? values : values.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the value of a slider by key name
|
|
||||||
* @private
|
|
||||||
* @param {string} key
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
incrementValue(key) {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const value = values[key] + this.props.step;
|
|
||||||
|
|
||||||
this.updateValue(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrement the value of a slider by key name
|
|
||||||
* @private
|
|
||||||
* @param {string} key
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
decrementValue(key) {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const value = values[key] - this.props.step;
|
|
||||||
|
|
||||||
this.updateValue(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to mouseup event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentMouseUpListener() {
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.node.ownerDocument.addEventListener('mouseup', this.handleMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to touchend event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentTouchEndListener() {
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
this.node.ownerDocument.addEventListener('touchend', this.handleTouchEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop listening to mouseup event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentMouseUpListener() {
|
|
||||||
this.node && this.node.ownerDocument.removeEventListener('mouseup', this.handleMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop listening to touchend event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentTouchEndListener() {
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'touchend',
|
|
||||||
this.handleTouchEnd
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "mousemove" event received by the slider
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @param {string} key
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleSliderDrag(event, key) {
|
|
||||||
if (this.props.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = valueTransformer.getPositionFromEvent(
|
|
||||||
event,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
this.isSliderDragging = true;
|
|
||||||
requestAnimationFrame(() => this.updatePosition(key, position));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "mousemove" event received by the track
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleTrackDrag(event, prevEvent) {
|
|
||||||
if (
|
|
||||||
this.props.disabled ||
|
|
||||||
!this.props.draggableTrack ||
|
|
||||||
this.isSliderDragging
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { maxValue, minValue, value: { max, min } } = this.props;
|
|
||||||
|
|
||||||
const position = valueTransformer.getPositionFromEvent(
|
|
||||||
event,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
const value = valueTransformer.getValueFromPosition(
|
|
||||||
position,
|
|
||||||
minValue,
|
|
||||||
maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
const stepValue = valueTransformer.getStepValueFromValue(
|
|
||||||
value,
|
|
||||||
this.props.step
|
|
||||||
);
|
|
||||||
|
|
||||||
const prevPosition = valueTransformer.getPositionFromEvent(
|
|
||||||
prevEvent,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
const prevValue = valueTransformer.getValueFromPosition(
|
|
||||||
prevPosition,
|
|
||||||
minValue,
|
|
||||||
maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
const prevStepValue = valueTransformer.getStepValueFromValue(
|
|
||||||
prevValue,
|
|
||||||
this.props.step
|
|
||||||
);
|
|
||||||
|
|
||||||
const offset = prevStepValue - stepValue;
|
|
||||||
|
|
||||||
const transformedValues = {
|
|
||||||
min: min - offset,
|
|
||||||
max: max - offset
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateValues(transformedValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "mousedown" event received by the track
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @param {Point} position
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleTrackMouseDown(event, position) {
|
|
||||||
if (this.props.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { maxValue, minValue, value: { max, min } } = this.props;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const value = valueTransformer.getValueFromPosition(
|
|
||||||
position,
|
|
||||||
minValue,
|
|
||||||
maxValue,
|
|
||||||
this.getTrackClientRect()
|
|
||||||
);
|
|
||||||
const stepValue = valueTransformer.getStepValueFromValue(
|
|
||||||
value,
|
|
||||||
this.props.step
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.props.draggableTrack || stepValue > max || stepValue < min) {
|
|
||||||
this.updatePosition(this.getKeyByPosition(position), position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the start of any mouse/touch event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleInteractionStart() {
|
|
||||||
if (this.props.onChangeStart) {
|
|
||||||
this.props.onChangeStart(this.props.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.onChangeComplete && !isDefined(this.startValue)) {
|
|
||||||
this.startValue = this.props.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the end of any mouse/touch event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleInteractionEnd() {
|
|
||||||
if (this.isSliderDragging) {
|
|
||||||
this.isSliderDragging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.props.onChangeComplete || !isDefined(this.startValue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.startValue !== this.props.value) {
|
|
||||||
this.props.onChangeComplete(this.props.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startValue = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "keydown" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleKeyDown(event) {
|
|
||||||
this.handleInteractionStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "keyup" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleKeyUp(event) {
|
|
||||||
this.handleInteractionEnd(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "mousedown" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleMouseDown(event) {
|
|
||||||
this.handleInteractionStart(event);
|
|
||||||
this.addDocumentMouseUpListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "mouseup" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleMouseUp(event) {
|
|
||||||
this.handleInteractionEnd(event);
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "touchstart" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
handleTouchStart(event) {
|
|
||||||
this.handleInteractionStart(event);
|
|
||||||
this.addDocumentTouchEndListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any "touchend" event received by the component
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
*/
|
|
||||||
handleTouchEnd(event) {
|
|
||||||
this.handleInteractionEnd(event);
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return JSX of sliders
|
|
||||||
* @private
|
|
||||||
* @return {JSX.Element}
|
|
||||||
*/
|
|
||||||
renderSliders() {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const percentages = valueTransformer.getPercentagesFromValues(
|
|
||||||
values,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.getKeys().map(key => {
|
|
||||||
const value = values[key];
|
|
||||||
const percentage = percentages[key];
|
|
||||||
|
|
||||||
let { maxValue, minValue } = this.props;
|
|
||||||
|
|
||||||
if (key === 'min') {
|
|
||||||
maxValue = values.max;
|
|
||||||
} else {
|
|
||||||
minValue = values.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slider = (
|
|
||||||
<Slider
|
|
||||||
ariaLabelledby={this.props.ariaLabelledby}
|
|
||||||
ariaControls={this.props.ariaControls}
|
|
||||||
classNames={this.props.classNames}
|
|
||||||
formatLabel={this.props.formatLabel}
|
|
||||||
key={key}
|
|
||||||
greyed={this.props.greyed}
|
|
||||||
maxValue={maxValue}
|
|
||||||
minValue={minValue}
|
|
||||||
onSliderDrag={this.handleSliderDrag}
|
|
||||||
percentage={percentage}
|
|
||||||
type={key}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return slider;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return JSX of hidden inputs
|
|
||||||
* @private
|
|
||||||
* @return {JSX.Element}
|
|
||||||
*/
|
|
||||||
renderHiddenInputs() {
|
|
||||||
if (!this.props.name) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMultiValue = this.isMultiValue();
|
|
||||||
const values = valueTransformer.getValueFromProps(this.props, isMultiValue);
|
|
||||||
|
|
||||||
return this.getKeys().map(key => {
|
|
||||||
const value = values[key];
|
|
||||||
const name = isMultiValue
|
|
||||||
? `${this.props.name}${captialize(key)}`
|
|
||||||
: this.props.name;
|
|
||||||
|
|
||||||
return <input key={key} type="hidden" name={name} value={value} />;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @override
|
|
||||||
* @return {JSX.Element}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const values = valueTransformer.getValueFromProps(
|
|
||||||
this.props,
|
|
||||||
this.isMultiValue()
|
|
||||||
);
|
|
||||||
const percentages = valueTransformer.getPercentagesFromValues(
|
|
||||||
values,
|
|
||||||
this.props.minValue,
|
|
||||||
this.props.maxValue
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RangeStyled
|
|
||||||
aria-disabled={this.props.disabled}
|
|
||||||
innerRef={node => {
|
|
||||||
this.node = node;
|
|
||||||
}}
|
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
onKeyUp={this.handleKeyUp}
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onTouchStart={this.handleTouchStart}
|
|
||||||
>
|
|
||||||
<Track
|
|
||||||
classNames={this.props.classNames}
|
|
||||||
draggableTrack={this.props.draggableTrack}
|
|
||||||
ref={trackNode => {
|
|
||||||
this.trackNode = trackNode;
|
|
||||||
}}
|
|
||||||
percentages={percentages}
|
|
||||||
onTrackDrag={this.handleTrackDrag}
|
|
||||||
onTrackMouseDown={this.handleTrackMouseDown}
|
|
||||||
>
|
|
||||||
{this.renderSliders()}
|
|
||||||
</Track>
|
|
||||||
|
|
||||||
{this.renderHiddenInputs()}
|
|
||||||
</RangeStyled>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import remcalc from 'remcalc';
|
|
||||||
|
|
||||||
import theme from '../../../theme';
|
|
||||||
|
|
||||||
const Span = styled.span`
|
|
||||||
font-size: ${remcalc(13)};
|
|
||||||
position: absolute;
|
|
||||||
top: ${remcalc(14)};
|
|
||||||
right: ${props => (props.type === 'max' ? remcalc(1) : 'auto')};
|
|
||||||
color: ${props => (props.greyed ? theme.grey : theme.secondary)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {InputRangeClassNames} props.classNames
|
|
||||||
* @param {Function} props.formatLabel
|
|
||||||
* @param {string} props.type
|
|
||||||
*/
|
|
||||||
export default function Label(props) {
|
|
||||||
const labelValue = props.formatLabel
|
|
||||||
? props.formatLabel(props.children, props.type)
|
|
||||||
: props.children;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Span greyed={props.greyed} type={props.type}>
|
|
||||||
{labelValue}
|
|
||||||
</Span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Object}
|
|
||||||
* @property {Function} children
|
|
||||||
* @property {Function} classNames
|
|
||||||
* @property {Function} formatLabel
|
|
||||||
* @property {Function} type
|
|
||||||
*/
|
|
||||||
Label.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
classNames: PropTypes.objectOf(PropTypes.string),
|
|
||||||
formatLabel: PropTypes.func,
|
|
||||||
type: PropTypes.string,
|
|
||||||
greyed: PropTypes.bool
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { isNumber } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @param {Object} props - React component props
|
|
||||||
* @return {?Error} Return Error if validation fails
|
|
||||||
*/
|
|
||||||
export default function rangePropType(props) {
|
|
||||||
const { maxValue, minValue } = props;
|
|
||||||
|
|
||||||
if (!isNumber(minValue) || !isNumber(maxValue)) {
|
|
||||||
return new Error('"minValue" and "maxValue" must be a number');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minValue >= maxValue) {
|
|
||||||
return new Error('"minValue" must be smaller than "maxValue"');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,315 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Label from './label';
|
|
||||||
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import remcalc from 'remcalc';
|
|
||||||
import theme from '../../../theme';
|
|
||||||
|
|
||||||
export const SliderStyled = styled.div`
|
|
||||||
appearance: none;
|
|
||||||
background: ${theme.white};
|
|
||||||
border: ${remcalc(2)} solid ${theme.grey};
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
height: ${remcalc(14)};
|
|
||||||
width: ${remcalc(14)};
|
|
||||||
transform: translateY(-50%);
|
|
||||||
outline: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
margin-top: ${remcalc(2)};
|
|
||||||
|
|
||||||
&::active {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::focus {
|
|
||||||
box-shadow: 0 0 0 ${remcalc(5)} rgba(63, 81, 181, 0.2);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export default class Slider extends Component {
|
|
||||||
/**
|
|
||||||
* Accepted propTypes of Slider
|
|
||||||
* @override
|
|
||||||
* @return {Object}
|
|
||||||
* @property {Function} ariaLabelledby
|
|
||||||
* @property {Function} ariaControls
|
|
||||||
* @property {Function} className
|
|
||||||
* @property {Function} formatLabel
|
|
||||||
* @property {Function} maxValue
|
|
||||||
* @property {Function} minValue
|
|
||||||
* @property {Function} onSliderDrag
|
|
||||||
* @property {Function} onSliderKeyDown
|
|
||||||
* @property {Function} percentage
|
|
||||||
* @property {Function} type
|
|
||||||
* @property {Function} value
|
|
||||||
*/
|
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
ariaLabelledby: PropTypes.string,
|
|
||||||
ariaControls: PropTypes.string,
|
|
||||||
classNames: PropTypes.objectOf(PropTypes.string),
|
|
||||||
formatLabel: PropTypes.func,
|
|
||||||
maxValue: PropTypes.number,
|
|
||||||
minValue: PropTypes.number,
|
|
||||||
onSliderDrag: PropTypes.func,
|
|
||||||
onSliderKeyDown: PropTypes.func,
|
|
||||||
percentage: PropTypes.number,
|
|
||||||
type: PropTypes.string,
|
|
||||||
value: PropTypes.number
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {string} [props.ariaLabelledby]
|
|
||||||
* @param {string} [props.ariaControls]
|
|
||||||
* @param {InputRangeClassNames} props.classNames
|
|
||||||
* @param {Function} [props.formatLabel]
|
|
||||||
* @param {number} [props.maxValue]
|
|
||||||
* @param {number} [props.minValue]
|
|
||||||
* @param {Function} props.onSliderKeyDown
|
|
||||||
* @param {Function} props.onSliderDrag
|
|
||||||
* @param {number} props.percentage
|
|
||||||
* @param {number} props.type
|
|
||||||
* @param {number} props.value
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?Component}
|
|
||||||
*/
|
|
||||||
this.node = null;
|
|
||||||
|
|
||||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
||||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
||||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
||||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
|
||||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
|
||||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @override
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.removeDocumentMouseMoveListener();
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
this.removeDocumentTouchMoveListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
getStyle() {
|
|
||||||
const percentage = (this.props.percentage || 0) * 100;
|
|
||||||
const style = {
|
|
||||||
position: 'absolute',
|
|
||||||
left: `${percentage > 94 ? 94 : percentage}%`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to mousemove event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentMouseMoveListener() {
|
|
||||||
this.removeDocumentMouseMoveListener();
|
|
||||||
this.node.ownerDocument.addEventListener('mousemove', this.handleMouseMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to mouseup event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentMouseUpListener() {
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.node.ownerDocument.addEventListener('mouseup', this.handleMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to touchmove event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentTouchMoveListener() {
|
|
||||||
this.removeDocumentTouchMoveListener();
|
|
||||||
this.node.ownerDocument.addEventListener('touchmove', this.handleTouchMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to touchend event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentTouchEndListener() {
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
this.node.ownerDocument.addEventListener('touchend', this.handleTouchEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentMouseMoveListener() {
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'mousemove',
|
|
||||||
this.handleMouseMove
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentMouseUpListener() {
|
|
||||||
this.node &&
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'mouseup',
|
|
||||||
this.handleMouseUp
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentTouchMoveListener() {
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'touchmove',
|
|
||||||
this.handleTouchMove
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentTouchEndListener() {
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'touchend',
|
|
||||||
this.handleTouchEnd
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleMouseDown() {
|
|
||||||
this.addDocumentMouseMoveListener();
|
|
||||||
this.addDocumentMouseUpListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleMouseUp() {
|
|
||||||
this.removeDocumentMouseMoveListener();
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleMouseMove(event) {
|
|
||||||
this.props.onSliderDrag(event, this.props.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleTouchStart() {
|
|
||||||
this.addDocumentTouchEndListener();
|
|
||||||
this.addDocumentTouchMoveListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleTouchMove(event) {
|
|
||||||
this.props.onSliderDrag(event, this.props.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleTouchEnd() {
|
|
||||||
this.removeDocumentTouchMoveListener();
|
|
||||||
this.removeDocumentTouchEndListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleKeyDown(event) {
|
|
||||||
this.props.onSliderKeyDown(event, this.props.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @return {JSX.Element}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const style = this.getStyle();
|
|
||||||
const props = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
ref={node => {
|
|
||||||
this.node = node;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label
|
|
||||||
greyed={props.greyed}
|
|
||||||
formatLabel={props.formatLabel}
|
|
||||||
type={props.type}
|
|
||||||
>
|
|
||||||
{props.value}
|
|
||||||
</Label>
|
|
||||||
<SliderStyled
|
|
||||||
type={props.type}
|
|
||||||
percentage={props.percentage}
|
|
||||||
style={style}
|
|
||||||
aria-labelledby={props.ariaLabelledby}
|
|
||||||
aria-controls={props.ariaControls}
|
|
||||||
aria-valuemax={props.maxValue}
|
|
||||||
aria-valuemin={props.minValue}
|
|
||||||
aria-valuenow={props.value}
|
|
||||||
draggable="false"
|
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onTouchStart={this.handleTouchStart}
|
|
||||||
role="slider"
|
|
||||||
tabIndex="0"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import remcalc from 'remcalc';
|
|
||||||
import theme from '../../../theme';
|
|
||||||
|
|
||||||
export const TrackStyled = styled.div`
|
|
||||||
background: ${theme.grey};
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
height: ${remcalc(4)};
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ActiveTrack = styled.div`
|
|
||||||
background: ${theme.primary};
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export default class Track extends Component {
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @return {Object}
|
|
||||||
* @property {Function} children
|
|
||||||
* @property {Function} classNames
|
|
||||||
* @property {Boolean} draggableTrack
|
|
||||||
* @property {Function} onTrackDrag
|
|
||||||
* @property {Function} onTrackMouseDown
|
|
||||||
* @property {Function} percentages
|
|
||||||
*/
|
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
classNames: PropTypes.objectOf(PropTypes.string),
|
|
||||||
draggableTrack: PropTypes.bool,
|
|
||||||
onTrackDrag: PropTypes.func,
|
|
||||||
onTrackMouseDown: PropTypes.func,
|
|
||||||
percentages: PropTypes.objectOf(PropTypes.number)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {InputRangeClassNames} props.classNames
|
|
||||||
* @param {Boolean} props.draggableTrack
|
|
||||||
* @param {Function} props.onTrackDrag
|
|
||||||
* @param {Function} props.onTrackMouseDown
|
|
||||||
* @param {number} props.percentages
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?Component}
|
|
||||||
*/
|
|
||||||
this.node = null;
|
|
||||||
this.trackDragEvent = null;
|
|
||||||
|
|
||||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
||||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
||||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
||||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {ClientRect}
|
|
||||||
*/
|
|
||||||
getClientRect() {
|
|
||||||
return this.node.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {Object} CSS styles
|
|
||||||
*/
|
|
||||||
getActiveTrackStyle() {
|
|
||||||
const width = `${(this.props.percentages.max - this.props.percentages.min) *
|
|
||||||
100}%`;
|
|
||||||
const left = `${this.props.percentages.min * 100}%`;
|
|
||||||
|
|
||||||
return { left, width };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to mousemove event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentMouseMoveListener() {
|
|
||||||
this.removeDocumentMouseMoveListener();
|
|
||||||
this.node.ownerDocument.addEventListener('mousemove', this.handleMouseMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to mouseup event
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
addDocumentMouseUpListener() {
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.node.ownerDocument.addEventListener('mouseup', this.handleMouseUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentMouseMoveListener() {
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'mousemove',
|
|
||||||
this.handleMouseMove
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
removeDocumentMouseUpListener() {
|
|
||||||
this.node &&
|
|
||||||
this.node.ownerDocument.removeEventListener(
|
|
||||||
'mouseup',
|
|
||||||
this.handleMouseUp
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleMouseMove(event) {
|
|
||||||
if (!this.props.draggableTrack) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.trackDragEvent !== null) {
|
|
||||||
this.props.onTrackDrag(event, this.trackDragEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.trackDragEvent = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
handleMouseUp() {
|
|
||||||
if (!this.props.draggableTrack) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeDocumentMouseMoveListener();
|
|
||||||
this.removeDocumentMouseUpListener();
|
|
||||||
this.trackDragEvent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event - User event
|
|
||||||
*/
|
|
||||||
handleMouseDown(event) {
|
|
||||||
const clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
|
||||||
const trackClientRect = this.getClientRect();
|
|
||||||
const position = {
|
|
||||||
x: clientX - trackClientRect.left,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.props.onTrackMouseDown(event, position);
|
|
||||||
|
|
||||||
if (this.props.draggableTrack) {
|
|
||||||
this.addDocumentMouseMoveListener();
|
|
||||||
this.addDocumentMouseUpListener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {SyntheticEvent} event - User event
|
|
||||||
*/
|
|
||||||
handleTouchStart(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.handleMouseDown(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @return {JSX.Element}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
const activeTrackStyle = this.getActiveTrackStyle();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TrackStyled
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onTouchStart={this.handleTouchStart}
|
|
||||||
innerRef={node => {
|
|
||||||
this.node = node;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActiveTrack style={activeTrackStyle} />
|
|
||||||
{this.props.children}
|
|
||||||
</TrackStyled>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
import { isNumber, isObject } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
* @param {Object} props
|
|
||||||
* @return {?Error} Return Error if validation fails
|
|
||||||
*/
|
|
||||||
export default function valuePropType(props, propName) {
|
|
||||||
const { maxValue, minValue } = props;
|
|
||||||
const value = props[propName];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isNumber(value) &&
|
|
||||||
(!isObject(value) || !isNumber(value.min) || !isNumber(value.max))
|
|
||||||
) {
|
|
||||||
return new Error(`"${propName}" must be a number or a range object`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNumber(value) && (value < minValue || value > maxValue)) {
|
|
||||||
return new Error(
|
|
||||||
`"${propName}" must be in between "minValue" and "maxValue"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isObject(value) &&
|
|
||||||
(value.min < minValue ||
|
|
||||||
value.min > maxValue ||
|
|
||||||
value.max < minValue ||
|
|
||||||
value.max > maxValue)
|
|
||||||
) {
|
|
||||||
return new Error(
|
|
||||||
`"${propName}" must be in between "minValue" and "maxValue"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
import { clamp } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a point into a percentage value
|
|
||||||
* @ignore
|
|
||||||
* @param {Point} position
|
|
||||||
* @param {ClientRect} clientRect
|
|
||||||
* @return {number} Percentage value
|
|
||||||
*/
|
|
||||||
export function getPercentageFromPosition(position, clientRect) {
|
|
||||||
const length = clientRect.width;
|
|
||||||
const sizePerc = position.x / length;
|
|
||||||
|
|
||||||
return sizePerc || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a point into a model value
|
|
||||||
* @ignore
|
|
||||||
* @param {Point} position
|
|
||||||
* @param {number} minValue
|
|
||||||
* @param {number} maxValue
|
|
||||||
* @param {ClientRect} clientRect
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export function getValueFromPosition(position, minValue, maxValue, clientRect) {
|
|
||||||
const sizePerc = getPercentageFromPosition(position, clientRect);
|
|
||||||
const valueDiff = maxValue - minValue;
|
|
||||||
|
|
||||||
return minValue + valueDiff * sizePerc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert props into a range value
|
|
||||||
* @ignore
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {boolean} isMultiValue
|
|
||||||
* @return {Range}
|
|
||||||
*/
|
|
||||||
export function getValueFromProps(props, isMultiValue) {
|
|
||||||
if (isMultiValue) {
|
|
||||||
return { ...props.value };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
min: props.minValue,
|
|
||||||
max: props.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a model value into a percentage value
|
|
||||||
* @ignore
|
|
||||||
* @param {number} value
|
|
||||||
* @param {number} minValue
|
|
||||||
* @param {number} maxValue
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export function getPercentageFromValue(value, minValue, maxValue) {
|
|
||||||
const validValue = clamp(value, minValue, maxValue);
|
|
||||||
const valueDiff = maxValue - minValue;
|
|
||||||
const valuePerc = (validValue - minValue) / valueDiff;
|
|
||||||
|
|
||||||
return valuePerc || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert model values into percentage values
|
|
||||||
* @ignore
|
|
||||||
* @param {Range} values
|
|
||||||
* @param {number} minValue
|
|
||||||
* @param {number} maxValue
|
|
||||||
* @return {Range}
|
|
||||||
*/
|
|
||||||
export function getPercentagesFromValues(values, minValue, maxValue) {
|
|
||||||
return {
|
|
||||||
min: getPercentageFromValue(values.min, minValue, maxValue),
|
|
||||||
max: getPercentageFromValue(values.max, minValue, maxValue)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a value into a point
|
|
||||||
* @ignore
|
|
||||||
* @param {number} value
|
|
||||||
* @param {number} minValue
|
|
||||||
* @param {number} maxValue
|
|
||||||
* @param {ClientRect} clientRect
|
|
||||||
* @return {Point} Position
|
|
||||||
*/
|
|
||||||
export function getPositionFromValue(value, minValue, maxValue, clientRect) {
|
|
||||||
const length = clientRect.width;
|
|
||||||
const valuePerc = getPercentageFromValue(value, minValue, maxValue);
|
|
||||||
const positionValue = valuePerc * length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: positionValue,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a range of values into points
|
|
||||||
* @ignore
|
|
||||||
* @param {Range} values
|
|
||||||
* @param {number} minValue
|
|
||||||
* @param {number} maxValue
|
|
||||||
* @param {ClientRect} clientRect
|
|
||||||
* @return {Range}
|
|
||||||
*/
|
|
||||||
export function getPositionsFromValues(values, minValue, maxValue, clientRect) {
|
|
||||||
return {
|
|
||||||
min: getPositionFromValue(values.min, minValue, maxValue, clientRect),
|
|
||||||
max: getPositionFromValue(values.max, minValue, maxValue, clientRect)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert an event into a point
|
|
||||||
* @ignore
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {ClientRect} clientRect
|
|
||||||
* @return {Point}
|
|
||||||
*/
|
|
||||||
export function getPositionFromEvent(event, clientRect) {
|
|
||||||
const length = clientRect.width;
|
|
||||||
const { clientX } = event.touches ? event.touches[0] : event;
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: clamp(clientX - clientRect.left, 0, length),
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a value into a step value
|
|
||||||
* @ignore
|
|
||||||
* @param {number} value
|
|
||||||
* @param {number} valuePerStep
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export function getStepValueFromValue(value, valuePerStep) {
|
|
||||||
return Math.round(value / valuePerStep) * valuePerStep;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Captialize a string
|
|
||||||
* @ignore
|
|
||||||
* @param {string} string
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export default function captialize(string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Clamp a value between a min and max value
|
|
||||||
* @ignore
|
|
||||||
* @param {number} value
|
|
||||||
* @param {number} min
|
|
||||||
* @param {number} max
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export default function clamp(value, min, max) {
|
|
||||||
return Math.min(Math.max(value, min), max);
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Calculate the distance between pointA and pointB
|
|
||||||
* @ignore
|
|
||||||
* @param {Point} pointA
|
|
||||||
* @param {Point} pointB
|
|
||||||
* @return {number} Distance
|
|
||||||
*/
|
|
||||||
export default function distanceTo(pointA, pointB) {
|
|
||||||
const xDiff = (pointB.x - pointA.x) ** 2;
|
|
||||||
const yDiff = (pointB.y - pointA.y) ** 2;
|
|
||||||
|
|
||||||
return Math.sqrt(xDiff + yDiff);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
export { default as captialize } from './captialize';
|
|
||||||
export { default as clamp } from './clamp';
|
|
||||||
export { default as distanceTo } from './distance-to';
|
|
||||||
export { default as isDefined } from './is-defined';
|
|
||||||
export { default as isNumber } from './is-number';
|
|
||||||
export { default as isObject } from './is-object';
|
|
||||||
export { default as length } from './length';
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Check if a value is defined
|
|
||||||
* @ignore
|
|
||||||
* @param {*} value
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
export default function isDefined(value) {
|
|
||||||
return value !== undefined && value !== null;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Check if a value is a number
|
|
||||||
* @ignore
|
|
||||||
* @param {*} value
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
export default function isNumber(value) {
|
|
||||||
return typeof value === 'number';
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Check if a value is an object
|
|
||||||
* @ignore
|
|
||||||
* @param {*} value
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
export default function isObject(value) {
|
|
||||||
return value !== null && typeof value === 'object';
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Calculate the absolute difference between two numbers
|
|
||||||
* @ignore
|
|
||||||
* @param {number} numA
|
|
||||||
* @param {number} numB
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
export default function length(numA, numB) {
|
|
||||||
return Math.abs(numA - numB);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user