2020-05-20 18:46:59 +00:00
|
|
|
import * as React from "react";
|
|
|
|
import {ReactElement} from "react";
|
2021-01-10 16:36:57 +00:00
|
|
|
import {Tooltip} from "tc-shared/ui/react-elements/Tooltip";
|
|
|
|
|
2020-05-20 18:46:59 +00:00
|
|
|
const cssStyle = require("./Slider.scss");
|
|
|
|
|
|
|
|
export interface SliderProperties {
|
|
|
|
minValue: number;
|
|
|
|
maxValue: number;
|
|
|
|
stepSize: number;
|
|
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
|
|
disabled?: boolean;
|
|
|
|
className?: string;
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
classNameFiller?: string;
|
|
|
|
inverseFiller?: boolean;
|
|
|
|
|
2020-05-20 18:46:59 +00:00
|
|
|
unit?: string;
|
2020-11-22 12:48:15 +00:00
|
|
|
tooltip?: (value: number) => ReactElement | string | null;
|
2020-05-20 18:46:59 +00:00
|
|
|
|
|
|
|
onInput?: (value: number) => void;
|
|
|
|
onChange?: (value: number) => void;
|
|
|
|
|
|
|
|
children?: never;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface SliderState {
|
|
|
|
value: number;
|
|
|
|
active: boolean;
|
|
|
|
|
|
|
|
disabled?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Slider extends React.Component<SliderProperties, SliderState> {
|
|
|
|
private documentListenersRegistered = false;
|
|
|
|
private lastValue: number;
|
|
|
|
|
|
|
|
private readonly mouseListener;
|
|
|
|
private readonly mouseUpListener;
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
protected readonly refTooltip = React.createRef<Tooltip>();
|
|
|
|
protected readonly refSlider = React.createRef<HTMLDivElement>();
|
2020-05-20 18:46:59 +00:00
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
value: this.props.value,
|
|
|
|
active: false
|
|
|
|
};
|
|
|
|
|
|
|
|
this.mouseListener = (event: MouseEvent | TouchEvent) => {
|
|
|
|
if(!this.refSlider.current) return;
|
|
|
|
|
|
|
|
const container = this.refSlider.current;
|
|
|
|
|
|
|
|
const bounds = container.getBoundingClientRect();
|
|
|
|
const min = bounds.left;
|
|
|
|
const max = bounds.left + container.clientWidth;
|
|
|
|
const current = ('touches' in event && Array.isArray(event.touches) && event.touches.length > 0) ? event.touches[event.touches.length - 1].clientX : (event as MouseEvent).pageX;
|
|
|
|
|
|
|
|
const range = this.props.maxValue - this.props.minValue;
|
|
|
|
let offset = Math.round(((current - min) * (range / this.props.stepSize)) / (max - min)) * this.props.stepSize;
|
2020-12-29 15:53:04 +00:00
|
|
|
if(offset < 0) {
|
2020-05-20 18:46:59 +00:00
|
|
|
offset = 0;
|
2020-12-29 15:53:04 +00:00
|
|
|
} else if(offset > range) {
|
2020-05-20 18:46:59 +00:00
|
|
|
offset = range;
|
2020-12-29 15:53:04 +00:00
|
|
|
}
|
2020-05-20 18:46:59 +00:00
|
|
|
|
2020-11-22 12:48:15 +00:00
|
|
|
this.refTooltip.current?.setState({
|
2020-05-20 18:46:59 +00:00
|
|
|
pageX: bounds.left + offset * bounds.width / range,
|
|
|
|
});
|
|
|
|
|
|
|
|
//console.log("Min: %o | Max: %o | %o (%o)", min, max, current, offset);
|
|
|
|
this.setState({ value: this.lastValue = (this.props.minValue + offset) });
|
|
|
|
if(this.props.onInput) this.props.onInput(this.lastValue);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.mouseUpListener = event => {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
this.setState({ active: false });
|
|
|
|
this.unregisterDocumentListener();
|
|
|
|
if(this.props.onChange) this.props.onChange(this.lastValue);
|
|
|
|
this.refTooltip.current?.setState({ forceShow: false });
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private unregisterDocumentListener() {
|
|
|
|
if(!this.documentListenersRegistered) return;
|
|
|
|
this.documentListenersRegistered = false;
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
document.body.classList.remove(cssStyle.documentClass);
|
2020-05-20 18:46:59 +00:00
|
|
|
document.removeEventListener('mousemove', this.mouseListener);
|
|
|
|
document.removeEventListener('touchmove', this.mouseListener);
|
|
|
|
|
|
|
|
document.removeEventListener('mouseup', this.mouseUpListener);
|
|
|
|
document.removeEventListener('mouseleave', this.mouseUpListener);
|
|
|
|
document.removeEventListener('touchend', this.mouseUpListener);
|
|
|
|
document.removeEventListener('touchcancel', this.mouseUpListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
private registerDocumentListener() {
|
|
|
|
if(this.documentListenersRegistered) return;
|
|
|
|
this.documentListenersRegistered = true;
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
document.body.classList.add(cssStyle.documentClass);
|
2020-05-20 18:46:59 +00:00
|
|
|
document.addEventListener('mousemove', this.mouseListener);
|
|
|
|
document.addEventListener('touchmove', this.mouseListener);
|
|
|
|
|
|
|
|
document.addEventListener('mouseup', this.mouseUpListener);
|
|
|
|
document.addEventListener('mouseleave', this.mouseUpListener);
|
|
|
|
document.addEventListener('touchend', this.mouseUpListener);
|
|
|
|
document.addEventListener('touchcancel', this.mouseUpListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount(): void {
|
|
|
|
this.unregisterDocumentListener();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const disabled = typeof this.state.disabled === "boolean" ? this.state.disabled : this.props.disabled;
|
2020-12-29 15:53:04 +00:00
|
|
|
|
|
|
|
let value = this.state.value;
|
|
|
|
if(value > this.props.maxValue) {
|
|
|
|
value = this.props.maxValue;
|
|
|
|
} else if(value < this.props.minValue) {
|
|
|
|
value = this.props.minValue;
|
|
|
|
}
|
|
|
|
const offset = (value - this.props.minValue) * 100 / (this.props.maxValue - this.props.minValue);
|
2020-05-20 18:46:59 +00:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={cssStyle.container + " " + (this.props.className || " ") + " " + (disabled ? cssStyle.disabled : "")}
|
|
|
|
ref={this.refSlider}
|
|
|
|
|
|
|
|
onMouseDown={e => this.enableSliderMode(e)}
|
|
|
|
onTouchStart={e => this.enableSliderMode(e)}
|
|
|
|
>
|
2020-08-10 22:25:20 +00:00
|
|
|
<div className={cssStyle.filler + " " + (this.props.classNameFiller || "")} style={{
|
|
|
|
right: this.props.inverseFiller ? 0 : (100 - offset) + "%",
|
|
|
|
left: this.props.inverseFiller ? offset + "%" : 0
|
|
|
|
}} />
|
2020-11-22 12:48:15 +00:00
|
|
|
{this.props.tooltip === null ? <div className={cssStyle.thumb} style={{left: offset + "%"}} key={"thumb"} /> :
|
|
|
|
<Tooltip ref={this.refTooltip} tooltip={() => this.props.tooltip ? this.props.tooltip(this.state.value) : this.renderTooltip()} key={"tooltip"}>
|
|
|
|
<div className={cssStyle.thumb} style={{left: offset + "%"}} />
|
|
|
|
</Tooltip>
|
|
|
|
}
|
2020-05-20 18:46:59 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
protected enableSliderMode(event: React.MouseEvent | React.TouchEvent) {
|
2020-05-20 18:46:59 +00:00
|
|
|
this.setState({ active: true });
|
|
|
|
this.registerDocumentListener();
|
|
|
|
this.mouseListener(event);
|
|
|
|
this.refTooltip.current?.setState({ forceShow: true });
|
|
|
|
}
|
|
|
|
|
2020-08-10 22:25:20 +00:00
|
|
|
protected renderTooltip() {
|
2020-05-20 18:46:59 +00:00
|
|
|
return <a>{this.state.value + (this.props.unit || "")}</a>;
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: Readonly<SliderProperties>, prevState: Readonly<SliderState>, snapshot?: any): void {
|
|
|
|
if(this.state.disabled !== prevState.disabled) {
|
|
|
|
if(this.state.disabled) {
|
|
|
|
this.unregisterDocumentListener();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|