import {h} from 'preact'; import { alignValue, clampValue, isValueOutOfRange, } from '../utils'; import AbstractSlider, { AbstractSliderProps, AbstractSliderState, } from './AbstractSlider'; import Handle from './Handle'; import Track from './Track'; /** * Component Properties. */ export interface SliderProps extends AbstractSliderProps { /** Initial value of handle. */ defaultValue: number; /** Current value of handle */ value: number; /** Triggered before value is start to change (on mouse down, etc) */ onBeforeChange( value: number ): void; /** Triggered while the value of Slider changing */ onChange( value: number ): void; /** Triggered after slider changes stop (on mouse up, etc) */ onAfterChange( value: number ): void; } /** * Component State. */ export interface SliderState extends AbstractSliderState { } /** * Slider with single handle. */ class Slider extends AbstractSlider { /** * Slider with single handle. */ /** * When component recieve properties. */ public static getDerivedStateFromProps = ( props: P, state: S, ): Partial => { const nextProps = props as unknown as SliderProps; const prevState = state as unknown as SliderState; if ( !( ( 'value' in nextProps ) || ( 'min' in nextProps ) || ( 'max' in nextProps ) ) ) { return {}; } const prevValue = prevState.value; const value = ( ( nextProps.value == null ) ? prevValue : nextProps.value ); const nextValue = alignValue( clampValue( value, nextProps ), nextProps, ); if ( nextValue === prevValue ) { return {}; } if ( isValueOutOfRange( value, nextProps ) ) { nextProps.onChange( nextValue ); } return {value: nextValue} as unknown as Partial; } public constructor( props: SliderProps ) { super( props ); const value = ( ( props.value != null ) ? props.value : ( ( props.defaultValue != null ) ? props.defaultValue : props.min ) ); this.state = { dragging: false, value: this.clampAlignValue( value ), toolTipDisplay: false, toolTipValue: this.clampAlignValue( value ), }; } /** * Render component. */ public render( { min, max, vertical, included, disabled, classesPrefix, }: SliderProps, {value, dragging}: SliderState, ): JSX.Element { const offset = this.calcOffset( value ); const handle = ( ); const track = ( ); return this.renderBase( track, handle ); } /** * Get current value. */ protected getValue(): number { return this.state.value; } /** * Get lower bound of current interval. */ protected getLowerBound(): number { // tslint:disable-next-line:no-non-null-assertion return this.props.min!; } /** * Get upper bound of current interval. */ protected getUpperBound(): number { return this.state.value; } /** * When value changed. */ protected onChange( state: Pick, ): void { const props = this.props as SliderProps; const isControlled = ('value' in props); const hasValue = ( s: Partial, ): s is Pick => typeof s.value !== 'undefined'; if ( isControlled && hasValue(state)) { props.onChange( state.value ); return; } this.setState( state, () => props.onChange( this.state.value ) ); } /** * On mouse/touch start. */ protected onStart( position: number ): void { this.setState( {dragging: true} ); const prevValue = this.getValue(); (this.props as SliderProps).onBeforeChange( prevValue ); const value = this.calcValueByPos( position ); if ( value === prevValue ) { return; } this.setState({ toolTipDisplay: true, toolTipValue: value, }); this.onChange( {value} ); } /** * On mouse/touch move. */ protected onMove( position: number ): void { const value = this.calcValueByPos( position ); const oldValue = this.state.value; if ( value === oldValue ) { return; } this.setState({ toolTipDisplay: true, toolTipValue: value, }); this.onChange( {value} ); } /** * On mouse/touch move. */ protected onHover( position: number ): void { const value = this.calcValueByPos( position ); const oldValue = this.state.value; if ( value === oldValue ) { return; } this.setState({ toolTipDisplay: true, toolTipValue: value, }); if (this.state.dragging) { this.onChange( {value} ); } } /** * On mouse/touch end. */ protected onEnd(): void { this.setState({ toolTipDisplay: false, dragging: false, }); } /** * Clamp current value to min-max interval on align to available values * using step and marks. */ protected clampAlignValue( value: number, nextProps: Partial = {}, ): number { const mergedProps = {...this.props, ...nextProps} as SliderProps; return alignValue( clampValue( value, mergedProps ), mergedProps, ); } } /** * Module. */ export { Slider as default, // SliderProps, // SliderState, };