uui-components/src/inputs/Slider/RangeSlider.tsx (137 lines of code) (raw):

import * as React from 'react'; import { uuiMod, cx } from '@epam/uui-core'; import { SliderBase, uuiSlider, SliderBaseState, SliderBaseProps } from './SliderBase'; import css from './SliderBase.module.scss'; import { SliderHandle } from './SliderHandle'; import { RangeSliderScale } from './RangeSliderScale'; interface RangeSliderValue { from: number; to: number; } interface RangeSliderState extends SliderBaseState { activeHandle: string | null; } export interface RangeSliderProps extends SliderBaseProps<RangeSliderValue> {} export class RangeSlider extends SliderBase<RangeSliderValue, RangeSliderState> { state = { isActive: false, valueWidth: 0, activeHandle: '', }; normalize(value: number) { if (!value && value !== 0) { return this.props.min; } return value > this.props.max ? this.props.max : value < this.props.min ? this.props.min : value; } onHandleValueChange = (mouseX: number, handleType: string, valueWidth?: number) => { if (!this.state.activeHandle) { this.setState({ activeHandle: handleType }); } if (this.getValue(mouseX, valueWidth) > this.props.value.to && this.state.activeHandle === 'from') { this.props.onValueChange({ from: this.props.value.to, to: this.props.value.to }); this.setState({ activeHandle: 'to' }); } else if (this.props.value.from > this.getValue(mouseX, valueWidth) && this.state.activeHandle === 'to') { this.props.onValueChange({ from: this.props.value.from, to: this.props.value.from }); this.setState({ activeHandle: 'from' }); } switch (this.state.activeHandle) { case 'from': this.props.onValueChange({ from: this.getValue(mouseX, valueWidth), to: (this.props.value && this.props.value.to) || this.props.min }); break; case 'to': this.props.onValueChange({ from: (this.props.value && this.props.value.from) || this.props.min, to: this.getValue(mouseX, valueWidth) }); break; } }; handleMouseClick = (e: React.MouseEvent<HTMLDivElement>) => { e.preventDefault(); if (this.props.value.to - this.getValue(e.clientX, this.getValueWidth()) > this.getValue(e.clientX, this.getValueWidth()) - this.props.value.from) { this.props.onValueChange({ from: this.getValue(e.clientX, this.getValueWidth()), to: this.props.value.to }); } else { this.props.onValueChange({ from: this.props.value.from, to: this.getValue(e.clientX, this.getValueWidth()) }); } }; getValueWidth() { return this.slider ? this.slider.offsetWidth / (this.props.max - this.props.min) : 0; } handleKeyDown(type: 'left' | 'right', normValue: RangeSliderValue) { const { step } = this.props; const { from, to } = normValue; const { activeHandle } = this.state; if (type === 'left') { this.props.onValueChange({ from: activeHandle === 'from' ? this.normalize(from - step) : from, to: activeHandle === 'to' ? this.normalize(to - step) : to, }); } else if (type === 'right') { this.props.onValueChange({ from: activeHandle === 'from' ? this.normalize(from + step) : from, to: activeHandle === 'to' ? this.normalize(to + step) : to, }); } } render() { const from = this.props.value && this.props.value.from != null ? this.props.value.from : this.props.min; const to = this.props.value && this.props.value.to != null ? this.props.value.to : this.props.max; const normValueFrom = this.roundToStep(this.normalize(from), this.props.step); const normValueTo = this.roundToStep(this.normalize(to), this.props.step); const valueWidth = this.getValueWidth(); const fromHandleOffset = (normValueFrom - this.props.min) * valueWidth; const toHandleOffset = (normValueTo - this.props.min) * valueWidth; return ( <div className={ cx(uuiSlider.container, css.root, this.props.isDisabled && uuiMod.disabled, this.props.cx) } onClick={ this.handleMouseClick } ref={ this.props.forwardedRef } { ...this.props.rawProps } > <div ref={ (slider) => (this.slider = slider) } className={ cx(uuiSlider.slider, this.state.activeHandle && uuiMod.active) } /> <div className={ uuiSlider.filled } style={ { width: (normValueFrom < normValueTo ? normValueTo - normValueFrom : normValueFrom - normValueTo) * valueWidth, left: (normValueFrom < normValueTo ? normValueFrom - this.props.min : normValueTo - this.props.min) * valueWidth, } } /> <RangeSliderScale handleOffset={ { from: fromHandleOffset, to: toHandleOffset } } slider={ this.slider } min={ this.props.min } max={ this.props.max } splitAt={ this.props.splitAt } valueWidth={ valueWidth } /> <SliderHandle cx={ this.props.cx } isActive={ this.state.activeHandle === 'from' } offset={ fromHandleOffset } tooltipContent={ normValueFrom } onUpdate={ (mouseX) => this.onHandleValueChange(mouseX, 'from', valueWidth) } onKeyDownUpdate={ (type) => this.handleKeyDown(type, { from: normValueFrom, to: normValueTo }) } handleActiveState={ (isActive) => this.setState({ activeHandle: isActive ? 'from' : null }) } rawProps={ { 'aria-label': this.props.rawProps && this.props.rawProps['aria-label'] ? this.props.rawProps['aria-label'] : 'From', 'aria-valuenow': this.props.value?.from, 'aria-valuemax': this.props.max, 'aria-valuemin': this.props.min, role: 'slider', } } /> <SliderHandle cx={ this.props.cx } isActive={ this.state.activeHandle === 'to' } offset={ toHandleOffset } tooltipContent={ normValueTo } onUpdate={ (mouseX: number) => this.onHandleValueChange(mouseX, 'to', valueWidth) } handleActiveState={ (isActive) => this.setState({ activeHandle: isActive ? 'to' : null }) } onKeyDownUpdate={ (type) => this.handleKeyDown(type, { from: normValueFrom, to: normValueTo }) } rawProps={ { 'aria-label': this.props.rawProps && this.props.rawProps['aria-label'] ? this.props.rawProps['aria-label'] : 'To', 'aria-valuenow': this.props.value?.to, 'aria-valuemax': this.props.max, 'aria-valuemin': this.props.min, role: 'slider', } } /> </div> ); } }