diff --git a/site/docs/en-US/slider.md b/site/docs/en-US/slider.md index 2d9a350f7..5ca4c2af8 100644 --- a/site/docs/en-US/slider.md +++ b/site/docs/en-US/slider.md @@ -121,15 +121,15 @@ Selecting a range of values is supported. constructor(props) { super(props); - this.state = { - value: [4, 8] - } + this.onChange = (value) => { + this.setState({ value }); + }; } render() { return (
- +
) } @@ -165,11 +165,12 @@ render() { | max | maximum value | number | — | 100 | | disabled | whether Slider is disabled | boolean | — | false | | step | step size | number | — | 1 | -| show-input | whether to display an input box, works when `range` is false | boolean | — | false | -| show-input-controls | whether to display control buttons when `show-input` is true | boolean | — | true | -| show-stops | whether to display breakpoints | boolean | — | false | -| show-tooltip | whether to display tooltip value | boolean | — | true | +| showInput | whether to display an input box, works when `range` is false | boolean | — | false | +| showInputControls | whether to display control buttons when `show-input` is true | boolean | — | true | +| showStops | whether to display breakpoints | boolean | — | false | +| showTooltip | whether to display tooltip value | boolean | — | true | | range | whether to select a range | boolean | — | false | +| triggerOnDragging | trigger `onChange` when mouse dragging | boolean | — | false | ## Events | Event Name | Description | Parameters | diff --git a/site/docs/zh-CN/slider.md b/site/docs/zh-CN/slider.md index 1fed43ec8..81550356b 100644 --- a/site/docs/zh-CN/slider.md +++ b/site/docs/zh-CN/slider.md @@ -121,12 +121,16 @@ constructor(props) { this.state = { value: [4, 8] } + + this.onChange = (value) => { + this.setState({ value }); + }; } render() { return (
- +
) } @@ -165,7 +169,9 @@ render() { | showInput | 是否显示输入框,仅在非范围选择时有效 | boolean | — | false | | showInputControls | 在显示输入框的情况下,是否显示输入框的控制按钮 | boolean | — | true| | showStops | 是否显示间断点 | boolean | — | false | +| showTooltip | 是否显示值提示 | boolean | — | true | | range | 是否为范围选择 | boolean | — | false | +| triggerOnDragging | 是否在拖动过程中触发 `onChange` | boolean | — | false | ### Events | 事件名称 | 说明 | 回调参数 | diff --git a/src/slider/Button.jsx b/src/slider/Button.jsx index ed718df9c..78401a991 100644 --- a/src/slider/Button.jsx +++ b/src/slider/Button.jsx @@ -10,8 +10,6 @@ type State = { dragging: boolean, startX: number, startY: number, - currentX: number, - currentY: number, startPosition: number, newPosition: number } @@ -31,8 +29,6 @@ export default class SliderButton extends Component { dragging: false, startX: 0, startY: 0, - currentX: 0, - currentY: 0, startPosition: 0, newPosition: 0 } @@ -42,58 +38,59 @@ export default class SliderButton extends Component { return this.context.component; } - handleMouseEnter(): void { + handleMouseEnter = (): void => { this.setState({ hovering: true }); } - handleMouseLeave(): void { + handleMouseLeave = (): void => { this.setState({ hovering: false }); } - onButtonDown(event: SyntheticMouseEvent) { + onDragStart = (event: SyntheticMouseEvent) => { if (this.disabled()) return; - this.onDragStart(event); + const position = parseInt(this.currentPosition(), 10); - window.addEventListener('mousemove', this.onDragging.bind(this)); - window.addEventListener('mouseup', this.onDragEnd.bind(this)); - window.addEventListener('contextmenu', this.onDragEnd.bind(this)); - } - - onDragStart(event: SyntheticMouseEvent) { this.setState({ dragging: true, startX: event.clientX, startY: event.clientY, - startPosition: parseInt(this.currentPosition(), 10) + startPosition: position, + newPosition: position }); - } - onDragging(event: SyntheticMouseEvent) { - const { dragging, startY, currentY, currentX, startX, startPosition, newPosition } = this.state; + window.addEventListener('mousemove', this.onDragging); + window.addEventListener('mouseup', this.onDragEnd); + window.addEventListener('contextmenu', this.onDragEnd); + }; + + onDragging = (event: SyntheticMouseEvent) => { + const { dragging, startY, startX, startPosition } = this.state; const { vertical } = this.props; - if (dragging) { - this.setState({ - currentX: event.clientX, - currentY: event.clientY, - }, () => { - let diff; - if (vertical) { - diff = (startY - currentY) / this.parent().sliderSize() * 100; - } else { - diff = (currentX - startX) / this.parent().sliderSize() * 100; - } - this.state.newPosition = startPosition + diff; - this.setPosition(newPosition); - }); + if (!dragging) { + return; } - } + const diff = vertical + ? (startY - event.clientY) + : (event.clientX - startX); - onDragEnd() { + if (!diff) { + return; + } + + const newPosition = startPosition + diff / this.parent().sliderSize() * 100; + this.setState({ + newPosition + }); + + this.setPosition(newPosition); + }; + + onDragEnd = () => { const { dragging, newPosition } = this.state; if (dragging) { /* @@ -108,13 +105,14 @@ export default class SliderButton extends Component { }); }, 0); - window.removeEventListener('mousemove', this.onDragging.bind(this)); - window.removeEventListener('mouseup', this.onDragEnd.bind(this)); - window.removeEventListener('contextmenu', this.onDragEnd.bind(this)); + window.removeEventListener('mousemove', this.onDragging); + window.removeEventListener('mouseup', this.onDragEnd); + window.removeEventListener('contextmenu', this.onDragEnd); } - } + }; setPosition(newPosition: number) { + const { onChange } = this.props; if (newPosition < 0) { newPosition = 0; } else if (newPosition > 100) { @@ -125,7 +123,7 @@ export default class SliderButton extends Component { const steps = Math.round(newPosition / lengthPerStep); const value = steps * lengthPerStep * (this.max() - this.min()) * 0.01 + this.min(); - this.props.onChange(parseFloat(value.toFixed(this.precision()))); + onChange(parseFloat(value.toFixed(this.precision()))); } /* Computed Methods */ @@ -178,9 +176,9 @@ export default class SliderButton extends Component { 'dragging': dragging })} style={this.wrapperStyle()} - onMouseEnter={this.handleMouseEnter.bind(this)} - onMouseLeave={this.handleMouseLeave.bind(this)} - onMouseDown={this.onButtonDown.bind(this)}> + onMouseEnter={this.handleMouseEnter} + onMouseLeave={this.handleMouseLeave} + onMouseDown={this.onDragStart}> {this.formatValue()}} diff --git a/src/slider/Slider.jsx b/src/slider/Slider.jsx index fdbe39e85..90bea1f63 100644 --- a/src/slider/Slider.jsx +++ b/src/slider/Slider.jsx @@ -6,152 +6,117 @@ import { Component, PropTypes } from '../../libs'; import InputNumber from '../input-number'; import SliderButton from './Button'; +type Value = number | Array; + type State = { firstValue: number, secondValue: number, - oldValue: number | Array, + draggingValue: Value, precision: number, - inputValue: number, - dragging: boolean, + inputValue: number } export default class Slider extends Component { - state: State; - - constructor(props: Object) { - super(props); - this.slider = React.createRef(); - this.button1 = React.createRef(); - this.button2 = React.createRef(); - this.state = { - firstValue: 0, - secondValue: 0, - oldValue: 0, - precision: 0, - inputValue: 0, - dragging: false - } - } - - getChildContext(): Object { - return { - component: this - }; - } - - get initValue() { - const { value, min, max } = this.props; - let initValue = value; - if (typeof value !== 'number' || isNaN(value)) { - initValue = min; - } else { - initValue = Math.min(max, Math.max(min, value)); - } - return initValue; - } + static getDerivedStateFromProps(props: Object, state: State): State { + const { range, value, min, max, step } = props; - componentWillMount(): void { - const { range, value, min, max, step } = this.props; - let { firstValue, secondValue, oldValue, inputValue, precision } = this.state; + const nextValue = state.draggingValue; if (range) { - if (Array.isArray(value)) { - firstValue = Math.max(min, value[0]); - secondValue = Math.min(max, value[1]); + if (Array.isArray(nextValue)) { + state.firstValue = Math.max(min, nextValue[0]); + state.secondValue = Math.min(max, nextValue[1]); } else { - firstValue = min; - secondValue = max; + state.firstValue = min; + state.secondValue = max; } - oldValue = [firstValue, secondValue]; + state.draggingValue = [state.firstValue, state.secondValue]; } else { - firstValue = this.initValue; - oldValue = firstValue; + state.firstValue = Slider.getInitValue(nextValue, props); + state.draggingValue = state.firstValue; } - let precisions = [min, max, step].map(item => { - let decimal = ('' + item).split('.')[1]; + const precisions = [min, max, step].map(item => { + const decimal = ('' + item).split('.')[1]; return decimal ? decimal.length : 0; }); - precision = Math.max.apply(null, precisions); - inputValue = inputValue || firstValue; - - this.setState({ firstValue, secondValue, oldValue, inputValue, precision }); - } - - componentWillUpdate(props: Object): void { - const { min, max, value, range } = this.props; - const { dragging } = this.state; - if (props.min != min || props.max != max) { - this.setValues(); - } - - if (props.value != value) { - const { oldValue } = this.state; + state.precision = Math.max.apply(null, precisions); + state.inputValue = state.inputValue || state.firstValue; - if (dragging || Array.isArray(value) && Array.isArray(props.value) && Array.isArray(oldValue) && value.every((item, index) => item === oldValue[index])) { - return; - } else if (!range && typeof props.value === 'number' && !isNaN(props.value)) { - this.setState({ firstValue: props.value }) - } - this.setValues(); - } + return state; } - valueChanged(): boolean { - const { range } = this.props; - const { firstValue, oldValue } = this.state; - if (range && Array.isArray(oldValue)) { - return ![this.minValue(), this.maxValue()].every((item, index) => item === oldValue[index]); + static getInitValue(value: Value, { min, max }: Object): number { + let initValue = value; + if (typeof value !== 'number' || isNaN(value)) { + initValue = min; } else { - return firstValue !== oldValue; + initValue = Math.min(max, Math.max(min, value)); } + return initValue; } + state: State = { + firstValue: 0, + secondValue: 0, + draggingValue: this.props.value, + precision: 0, + inputValue: 0 + }; + + slider: Function = React.createRef(); + button1: Function = React.createRef(); + button2: Function = React.createRef(); + + get dragging(): boolean { + return this.button1.current && this.button1.current.state.dragging + || this.button2.current && this.button2.current.state.dragging; + } + getChildContext(): Object { + return { + component: this + }; + } - setValues(): void { - const { range, value, min, max } = this.props; - let { firstValue, secondValue, oldValue, inputValue } = this.state; + setValues(state: Object): void { + const { range, min, max, triggerOnDragging = false, onChange } = this.props; + const states = { ...this.state, ...state }; + const { firstValue, secondValue } = states; + let { inputValue } = states; + let changes; - if (range && Array.isArray(value)) { - if (value[1] < min) { - inputValue = [min, min]; - } else if (value[0] > max) { - inputValue = [max, max]; - } else if (value[0] < min) { - inputValue = [min, value[1]]; - } else if (value[1] > max) { - inputValue = [value[0], max]; + if (range) { + if (secondValue < min) { + changes = [min, min]; + } else if (firstValue > max) { + changes = [max, max]; + } else if (firstValue < min) { + changes = [min, secondValue]; + } else if (secondValue > max) { + changes = [firstValue, max]; } else { - firstValue = value[0]; - secondValue = value[1]; - - if (this.valueChanged()) { - this.onValueChanged([this.minValue(), this.maxValue()]); - - oldValue = value.slice(); - } + changes = [Math.min(firstValue, secondValue), Math.max(firstValue, secondValue)]; } - } else if (!range && typeof value === 'number' && !isNaN(value)) { - if (this.initValue < min) { - inputValue = min; - } else if (this.initValue > max) { - inputValue = max; + } else if (typeof firstValue === 'number' && !isNaN(firstValue)) { + const initValue = Slider.getInitValue(firstValue, this.props); + if (initValue < min) { + changes = min; + } else if (initValue > max) { + changes = max; } else { - inputValue = firstValue; - - this.setState({ firstValue }, () => { - if (this.valueChanged()) { - this.onValueChanged(firstValue); - this.setState({ oldValue: firstValue }); - } - }); + changes = firstValue; + inputValue = changes; } } - this.setState({ firstValue, secondValue, inputValue }); + this.setState({ firstValue, secondValue, inputValue, draggingValue: changes }); + + if (typeof changes !== 'undefined' && onChange && !(Number(triggerOnDragging) ^ Number(this.dragging))) { + onChange(changes); + } } setPosition(percent: number): void { @@ -161,24 +126,24 @@ export default class Slider extends Component { const targetValue = min + percent * (max - min) / 100; if (!range) { - this.button1.current.setPosition(percent); + this.button1 && this.button1.current.setPosition(percent); return; } let button; - if (Math.abs(this.minValue() - targetValue) < Math.abs(this.maxValue() - targetValue)) { + if (Math.abs(Math.min(firstValue, secondValue) - targetValue) < Math.abs(Math.max(firstValue, secondValue) - targetValue)) { button = firstValue < secondValue ? 'button1' : 'button2'; } else { button = firstValue > secondValue ? 'button1' : 'button2'; } - this[button].current.setPosition(percent); + this[button] && this[button].current.setPosition(percent); } onSliderClick(event: SyntheticMouseEvent): void { - const { disabled, dragging, vertical } = this.props; - if (disabled || dragging) return; + const { disabled, vertical } = this.props; + if (disabled || this.dragging || !this.slider.current) return; if (vertical) { const sliderOffsetBottom = this.slider.current.getBoundingClientRect().bottom; @@ -188,48 +153,27 @@ export default class Slider extends Component { this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize() * 100); } - this.setValues(); + this.setValues(this.state); } - /* Watched Methods */ - onValueChanged(val: number | Array): void { - const { onChange } = this.props; - if (onChange) onChange(val); - } - - onInputValueChanged(e: number): void { - this.setState({ - inputValue: e || 0, - firstValue: e || 0 - }, () => { - this.setValues(); + onInputValueChanged(value: number): void { + this.setValues({ + inputValue: value || 0, + firstValue: value || 0 }); } - onFirstValueChange(value: number): void { - const { firstValue } = this.state; - if (firstValue !== value) { - this.setState({ firstValue: value }, () => this.setValues()); - } - } - - onSecondValueChange(value: number): void { - const { secondValue } = this.state; - if (secondValue !== value) { - this.setState({ secondValue: value }, () => this.setValues()); - } - } - /* Computed Methods */ sliderSize(): number { const {vertical} = this.props; + if (!this.slider.current) return 0; return parseInt(vertical ? this.slider.current.offsetHeight : this.slider.current.offsetWidth, 10); } stops(): Array { const { range, min, max, step } = this.props; - const { firstValue } = this.state; + const { firstValue, secondValue } = this.state; const stopCount = (max - min) / step; const stepWidth = 100 * step / (max - min); @@ -241,24 +185,14 @@ export default class Slider extends Component { if (range) { return result.filter(step => { - return step < 100 * (this.minValue() - min) / (max - min) || - step > 100 * (this.maxValue() - min) / (max - min); + return step < 100 * (Math.min(firstValue, secondValue) - min) / (max - min) || + step > 100 * (Math.max(firstValue, secondValue) - min) / (max - min); }); } else { return result.filter(step => step > 100 * (firstValue - min) / (max - min)); } } - minValue(): number { - const { firstValue, secondValue } = this.state; - return Math.min(firstValue, secondValue); - } - - maxValue(): number { - const { firstValue, secondValue } = this.state; - return Math.max(firstValue, secondValue); - } - runwayStyle(): Object { const { vertical, height } = this.props; return vertical ? { height } : {}; @@ -278,17 +212,18 @@ export default class Slider extends Component { } barSize(): string { - const { firstValue } = this.state; + const { firstValue, secondValue} = this.state; const { range, max, min } = this.props; return range - ? `${100 * (this.maxValue() - this.minValue()) / (max - min)}%` + ? `${100 * (Math.max(firstValue, secondValue) - Math.min(firstValue, secondValue)) / (max - min)}%` : `${100 * (firstValue - min) / (max - min)}%`; } barStart(): string { + const { firstValue, secondValue} = this.state; const { range, max, min } = this.props; return range - ? `${100 * (this.minValue() - min) / (max - min)}%` + ? `${100 * (Math.min(firstValue, secondValue) - min) / (max - min)}%` : '0%'; } @@ -334,14 +269,14 @@ export default class Slider extends Component { this.setValues({ firstValue: v })} /> { range && ( this.setValues({ secondValue: v })} /> ) } @@ -378,8 +313,9 @@ Slider.propTypes = { vertical: PropTypes.bool, height: PropTypes.string, formatTooltip: PropTypes.func, - onChange: PropTypes.func -} + onChange: PropTypes.func, + triggerOnDragging: PropTypes.bool +}; Slider.defaultProps = { showTooltip: true, @@ -387,5 +323,6 @@ Slider.defaultProps = { min: 0, max: 100, step: 1, - value: 0 -} + value: 0, + triggerOnDragging: false +};