import * as React from 'react' import './SliderField.css' export type SliderFieldProps = | { range?: false header: string className?: string min?: number max?: number defaultValue?: number label?: string | React.PureComponent<{ value: number }> onChange?: (ev: React.ChangeEvent, data: number) => void onMouseUp?: (ev: React.MouseEvent, data: number) => void } | { range: true header: string className?: string min?: number max?: number defaultValue?: readonly [number, number] label?: string | React.PureComponent<{ value: readonly [number, number] }> onChange?: ( ev: React.ChangeEvent, data: readonly [number, number] ) => void onMouseUp?: ( ev: React.MouseEvent, data: readonly [number, number] ) => void } 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 } = this.props if (defaultValue === undefined) { this.setState((prevState) => { return { ...prevState, from: !isNaN(min) ? min : prevState.from, to: !isNaN(max) ? max : prevState.to } }) } else { if (Array.isArray(defaultValue)) { this.setState({ from: defaultValue[0], to: defaultValue[1] }) } else { this.setState({ to: Number(defaultValue) }) } } } 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 } } getLabel = () => { const { label, range } = this.props const { from, to } = this.state return label || (range ? `${from} - ${to}` : to) } 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 } = 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 label = this.getLabel() return (
{header}

{label}

{range && ( )}
{range && ( )}
) } }