import React, { Component, HTMLAttributes, RefCallback } from 'react';
import Touch, { TouchEventHandler, TouchEvent } from '../Touch/Touch';
import { classNames } from '../../lib/classNames';
import { getClassName } from '../../helpers/getClassName';
import { HasPlatform, HasRootRef } from '../../types';
import { withPlatform } from '../../hoc/withPlatform';
import { canUseDOM } from '../../lib/dom';
import { setRef } from '../../lib/utils';
import { withAdaptivity, AdaptivityProps } from '../../hoc/withAdaptivity';
import { precisionRound } from '../../helpers/math';
export interface SliderProps extends
HasRootRef,
HasPlatform,
Omit, 'defaultValue' | 'onChange'>,
AdaptivityProps {
min?: number;
max?: number;
value?: number;
step?: number;
disabled?: boolean;
onChange?(value: number, e: TouchEvent): void;
defaultValue?: number;
}
export interface SliderState {
startX: number;
percentPosition: number;
containerWidth: number;
}
class Slider extends Component {
constructor(props: SliderProps) {
super(props);
this.state = {
startX: 0,
percentPosition: 0,
containerWidth: 0,
};
this.isControlledOutside = this.props.hasOwnProperty('value');
}
static defaultProps: SliderProps = {
min: 0,
max: 100,
step: 0,
};
isControlledOutside: boolean;
container: HTMLDivElement;
onStart: TouchEventHandler = (e: TouchEvent) => {
if (this.props.disabled) {
return;
}
const boundingRect = this.container.getBoundingClientRect();
this.setState({
containerWidth: boundingRect.width,
}, () => {
const absolutePosition = this.validateAbsolute(e.startX - boundingRect.left);
const percentPosition = this.absoluteToPecent(absolutePosition);
this.onChange(this.percentToValue(percentPosition), e);
if (this.isControlledOutside) {
this.setState({ startX: absolutePosition });
} else {
this.setState({
startX: absolutePosition,
percentPosition,
});
}
});
};
onMoveX: TouchEventHandler = (e: TouchEvent) => {
if (this.props.disabled) {
return;
}
const absolutePosition = this.validateAbsolute(this.state.startX + (e.shiftX || 0));
const percentPosition = this.absoluteToPecent(absolutePosition);
this.onChange(this.percentToValue(percentPosition), e);
if (!this.isControlledOutside) {
this.setState({ percentPosition });
}
e.originalEvent.preventDefault();
};
onChange(value: number, e: TouchEvent) {
if (this.props.disabled) {
return;
}
this.props.onChange && this.props.onChange(value, e);
}
validateAbsolute(absolute: number) {
let res = Math.max(0, Math.min(absolute, this.state.containerWidth));
if (this.props.step > 0) {
const stepCount = (this.props.max - this.props.min) / this.props.step;
const absStep = this.state.containerWidth / stepCount;
res = Math.round(res / absStep) * absStep;
}
return res;
}
validatePercent(percent: number) {
return Math.max(0, Math.min(percent, 100));
}
absoluteToPecent(absolute: number) {
return absolute * 100 / this.state.containerWidth;
}
percentToValue(percent: number) {
const res = percent * (this.props.max - this.props.min) / 100 + this.props.min;
if (this.props.step > 0) {
const stepFloatPart = `${this.props.step}`.split('.')[1] || '';
return precisionRound(res, stepFloatPart.length);
}
return res;
}
valueToPercent(value: number) {
return (value - this.props.min) * 100 / (this.props.max - this.props.min);
}
get value() {
if (this.isControlledOutside) {
return this.props.value;
} else if (this.props.hasOwnProperty('defaultValue')) {
return this.props.defaultValue;
} else {
return this.props.min;
}
}
componentDidMount() {
if (canUseDOM) {
const boundingRect = this.container.getBoundingClientRect();
this.setState({
containerWidth: boundingRect.width,
}, () => {
this.setState({ percentPosition: this.validatePercent(this.valueToPercent(this.value)) });
});
}
}
componentDidUpdate(prevProps: SliderProps) {
if (this.isControlledOutside && this.props.value !== prevProps.value) {
this.setState({ percentPosition: this.validatePercent(this.valueToPercent(this.props.value)) });
}
}
getRef: RefCallback = (container) => {
this.container = container;
setRef(container, this.props.getRootRef);
};
render() {
const { className, min, max, step, value, defaultValue,
onChange, getRootRef, platform, sizeY, disabled, ...restProps } = this.props;
return (
);
}
}
export default withAdaptivity(withPlatform(Slider), {
sizeY: true,
});