import * as React from 'react' import './SliderField.css' interface SliderBaseProps { header: string className?: string valueFrom?: number valueTo?: number min?: number max?: number labelFrom?: string | JSX.Element labelTo?: string | JSX.Element } interface RangeSliderProps extends SliderBaseProps { range: true step?: number defaultValue?: readonly [number, number] label?: React.ReactNode onChange?: ( ev: React.ChangeEvent, data: readonly [number, number] ) => void onMouseUp?: ( ev: React.MouseEvent, data: readonly [number, number] ) => void } interface SimpleSliderProps extends SliderBaseProps { range?: false step?: undefined defaultValue?: number label?: React.ReactNode onChange?: (ev: React.ChangeEvent, data: number) => void onMouseUp?: (ev: React.MouseEvent, data: number) => void } export type SliderFieldProps = SimpleSliderProps | RangeSliderProps export enum SliderLastInteraction { 'from', 'to' } export const SliderDefault = { MIN: 0, MAX: 100, FROM: 0, TO: 100 } export type SliderFieldState = { from: number to: number lastInteraction: SliderLastInteraction } export type SliderFieldLeftRightStyle = { left: string | number right: string } export class SliderField extends React.PureComponent< SliderFieldProps, SliderFieldState > { state = { from: SliderDefault.FROM, to: SliderDefault.TO, lastInteraction: SliderLastInteraction.from } componentDidMount(): void { const { defaultValue, min, max, valueFrom, valueTo } = this.props if (defaultValue === undefined) { const minValue = !isNaN(min) ? min : SliderDefault.FROM const maxValue = !isNaN(max) ? max : SliderDefault.TO this.setState((prevState) => { return { ...prevState, from: valueFrom ? valueFrom : minValue, to: valueTo ? valueTo : maxValue } }) } else { if (Array.isArray(defaultValue)) { this.setState({ from: defaultValue[0], to: defaultValue[1] }) } else { this.setState({ to: Number(defaultValue) }) } } } componentDidUpdate(prevProps: Readonly): void { const { valueFrom, valueTo } = this.props if (valueFrom !== prevProps.valueFrom || valueTo !== prevProps.valueTo) { this.setState({ from: valueFrom ?? prevProps.valueFrom, to: valueTo ?? prevProps.valueTo }) } } handleChangeFrom = (e: React.ChangeEvent) => { const input = e.target const { onChange, range } = this.props if (!range) { return } this.setState( (prevState) => { return { ...prevState, from: Math.min(Number(input.value), prevState.to), lastInteraction: SliderLastInteraction.from } }, () => { if (onChange) { onChange(e, [this.state.from, this.state.to] as const) } } ) } handleChangeTo = (e: React.ChangeEvent) => { const input = e.target const { onChange, range } = this.props this.setState( (prevState) => { return { ...prevState, to: Math.max(Number(input.value), prevState.from), lastInteraction: SliderLastInteraction.to } }, () => { if (onChange) { onChange( e, (range ? [this.state.from, this.state.to] : this.state.to) as number & readonly [number, number] ) } } ) } handleMouseUp = (e: React.MouseEvent) => { const { onMouseUp, range } = this.props if (onMouseUp) { onMouseUp( e, (range ? [this.state.from, this.state.to] : this.state.to) as number & readonly [number, number] ) } } getTrackStyles = (min: number, max: number): SliderFieldLeftRightStyle => { const { range } = this.props const { from, to } = this.state const diffMaxMin = max - min const trackLeft = range ? `${((from - min) * 100) / diffMaxMin}%` : 0 const trackRight = range ? `${((max - to) * 100) / diffMaxMin}%` : `${100 - ((to - min) * 100) / diffMaxMin}%` return { left: trackLeft, right: trackRight } } getMarkStyle = (min: number, max: number): SliderFieldLeftRightStyle => { const { from, to } = this.state const diffMaxMin = max - min const leftStyle = `${((from - min) * 100) / diffMaxMin}%` const rightStyle = `${((to - min) * 100) / diffMaxMin}%` return { left: leftStyle, right: rightStyle } } render(): JSX.Element { const { header, className, range, step, valueFrom, valueTo, label, labelFrom, labelTo } = this.props const min = this.props.min || SliderDefault.MIN const max = this.props.max || SliderDefault.MAX const { from, to } = this.state const classes = ['dcl', 'sliderfield'] if (className) { classes.push(className) } const trackStyle = this.getTrackStyles(min, max) const markStyle = this.getMarkStyle(min, max) const fromText = labelFrom ?? valueFrom ?? from const toText = labelTo ?? valueTo ?? to return (
{header &&
{header}
} {label &&

{label}

}
{range && ( )}
{range && fromText !== toText && ( {fromText} )} {toText}
{range && ( )}
) } }