import * as React from "react"; import { CircleSliderHelper } from "./helpers/circle-slider-helper"; import { MouseHelper } from "./helpers/mouse-helper"; import { pathGenerator } from "./helpers/path-generator"; interface IProps { size?: number; circleWidth?: number; progressWidth?: number; knobRadius?: number; value?: number; stepSize?: number; min?: number; max?: number; circleColor?: string; progressColor?: string; gradientColorFrom?: string; gradientColorTo?: string; knobColor?: string; onChange: (value?: number) => void; disabled?: boolean; shadow?: boolean; showTooltip?: boolean; showPercentage?: boolean; tooltipSize?: number; tooltipColor?: string; } interface IPoint { x: number; y: number; } interface IState { angle: number; currentStepValue: number; isMouseMove: boolean; } export class CircleSlider extends React.Component { public static defaultProps: Partial = { circleColor: "#e9eaee", size: 180, value: 0, progressColor: "#007aff", knobColor: "#fff", circleWidth: 5, progressWidth: 20, knobRadius: 20, stepSize: 1, min: 0, max: 100, disabled: false, shadow: true, showTooltip: false, showPercentage: false, tooltipSize: 32, tooltipColor: "#333", onChange: () => ({}), }; private maxLineWidth: number; private radius: number; private countSteps: number; private stepsArray: number[]; private circleSliderHelper: CircleSliderHelper; private mouseHelper!: MouseHelper; private svg: any; constructor(props: IProps) { super(props); this.state = { angle: 0, currentStepValue: 0, isMouseMove: false, }; const { min, max, stepSize, value, circleWidth, progressWidth, knobRadius, } = this.props; this.maxLineWidth = Math.max(circleWidth!, progressWidth!); this.radius = this.getCenter() - Math.max(this.maxLineWidth, knobRadius! * 2) / 2; this.countSteps = 1 + (max! - min!) / stepSize!; this.stepsArray = this.getStepsArray(min!, stepSize!); this.circleSliderHelper = new CircleSliderHelper( this.stepsArray, value, ); } public componentDidMount() { this.mouseHelper = new MouseHelper(this.svg); this.setState({ angle: this.circleSliderHelper.getAngle(), currentStepValue: this.circleSliderHelper.getCurrentStep(), }); } public componentWillReceiveProps(nextProps: any) { if (this.props.value !== nextProps.value && !this.state.isMouseMove) { this.updateSliderFromProps(nextProps.value); } } public updateAngle = (angle: number): void => { this.circleSliderHelper.updateStepIndexFromAngle(angle); const currentStep = this.circleSliderHelper.getCurrentStep(); this.setState({ angle, currentStepValue: currentStep, }); this.props.onChange(currentStep); }; public updateSlider = (): void => { const angle = this.mouseHelper.getNewSliderAngle(); if (Math.abs(angle - this.state.angle) < Math.PI) { this.updateAngle(angle); } }; public updateSliderFromProps = (valueFromProps: number): void => { const { stepSize } = this.props; const newValue = Math.round(valueFromProps / stepSize!) * stepSize!; this.circleSliderHelper.updateStepIndexFromValue(newValue); this.setState({ angle: this.circleSliderHelper.getAngle(), currentStepValue: newValue, }); }; public getCenter = (): number => { return this.props.size! / 2; }; public getAngle = (): number => { return this.state.angle + Math.PI / 2; }; public getPointPosition = (): IPoint => { const center = this.getCenter(); const angle = this.getAngle(); return { x: center + this.radius * Math.cos(angle), y: center + this.radius * Math.sin(angle), }; }; public getStepsArray = (min: number, stepSize: number): number[] => { const stepArray = []; for (let i = 0; i < this.countSteps; i++) { stepArray.push(min + i * stepSize); } return stepArray; }; public getPath = (): string => { const center = this.getCenter(); const direction = this.getAngle() < 1.5 * Math.PI ? 0 : 1; const { x, y } = this.getPointPosition(); const path = pathGenerator(center, this.radius, direction, x, y); return path; }; public handleMouseMove = (event: Event): void => { event.preventDefault(); this.setState({ isMouseMove: true, }); this.mouseHelper.setPosition(event); this.updateSlider(); }; public handleMouseUp = (event: Event): void => { event.preventDefault(); this.setState({ isMouseMove: false, }); window.removeEventListener("mousemove", this.handleMouseMove); window.removeEventListener("mouseup", this.handleMouseUp); }; public handleMouseDown = (event: React.MouseEvent): void => { if (!this.props.disabled) { event.preventDefault(); window.addEventListener("mousemove", this.handleMouseMove); window.addEventListener("mouseup", this.handleMouseUp); } }; public handleTouchMove: any = ( event: React.TouchEvent, ): void => { const targetTouches = event.targetTouches; const countTouches = targetTouches.length; const currentTouch: React.Touch = targetTouches.item(countTouches - 1)!; this.mouseHelper.setPosition(currentTouch); this.updateSlider(); }; public handleTouchUp = (): void => { window.removeEventListener("touchmove", this.handleTouchMove); window.removeEventListener("touchend", this.handleTouchUp); }; public handleTouchStart = (): void => { if (!this.props.disabled) { window.addEventListener("touchmove", this.handleTouchMove); window.addEventListener("touchend", this.handleTouchUp); } }; public render() { const { size, progressColor, gradientColorFrom, gradientColorTo, knobColor, circleColor, disabled, shadow, circleWidth, progressWidth, knobRadius, showTooltip, showPercentage, tooltipSize, tooltipColor, } = this.props; const { currentStepValue } = this.state; const offset = shadow ? "5px" : "0px"; const { x, y } = this.getPointPosition(); const center = this.getCenter(); const isAllGradientColorsAvailable = gradientColorFrom && gradientColorTo; return ( (this.svg = svg)} width={`${size}px`} height={`${size}px`} viewBox={`0 0 ${size} ${size}`} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart} style={{ padding: offset, boxSizing: "border-box", }} > {isAllGradientColorsAvailable && ( )} {shadow && ( )} {showTooltip && ( {showPercentage ? `${( Math.round(currentStepValue) / 10 ).toFixed(1)}°C` : currentStepValue} )} ); } }