diff --git a/components/vc-slider/index.js b/components/vc-slider/index.js index 1c934e9dc..94a8909e3 100644 --- a/components/vc-slider/index.js +++ b/components/vc-slider/index.js @@ -1,3 +1,3 @@ -// base rc-slider 8.6.1 +// base rc-slider 8.6.3 import Slider from './src/' export default Slider diff --git a/components/vc-slider/src/Handle.jsx b/components/vc-slider/src/Handle.jsx index bce8af59e..a24a900bf 100644 --- a/components/vc-slider/src/Handle.jsx +++ b/components/vc-slider/src/Handle.jsx @@ -1,6 +1,8 @@ +import classNames from 'classnames' import PropTypes from '../../_util/vue-types' import BaseMixin from '../../_util/BaseMixin' import { getOptionProps } from '../../_util/props-util' +import addEventListener from '../../_util/Dom/addEventListener' export default { name: 'Handle', @@ -18,7 +20,42 @@ export default { // handleFocus: PropTypes.func.def(noop), // handleBlur: PropTypes.func.def(noop), }, + data () { + return { + clickFocused: false, + } + }, + mounted () { + this.$nextTick(() => { + // mouseup won't trigger if mouse moved out of handle + // so we listen on document here. + this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp) + }) + }, + beforeDestroy () { + if (this.onMouseUpListener) { + this.onMouseUpListener.remove() + } + }, methods: { + setClickFocus (focused) { + this.setState({ clickFocused: focused }) + }, + handleMouseUp () { + if (document.activeElement === this.$refs.handle) { + this.setClickFocus(true) + } + }, + handleBlur () { + this.setClickFocus(false) + }, + handleKeyDown () { + this.setClickFocus(false) + }, + clickFocus () { + this.setClickFocus(true) + this.focus() + }, focus () { this.$refs.handle.focus() }, @@ -28,23 +65,27 @@ export default { }, render () { const { - className, vertical, offset, disabled, min, max, value, tabIndex, + prefixCls, vertical, offset, disabled, min, max, value, tabIndex, } = getOptionProps(this) + const className = classNames( + this.$props.className, + { + [`${prefixCls}-handle-click-focused`]: this.clickFocused, + } + ) + const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` } const elStyle = { ...postionStyle, } - let ariaProps = {} - if (value !== undefined) { - ariaProps = { - ...ariaProps, - 'aria-valuemin': min, - 'aria-valuemax': max, - 'aria-valuenow': value, - 'aria-disabled': !!disabled, - } + const ariaProps = { + 'aria-valuemin': min, + 'aria-valuemax': max, + 'aria-valuenow': value, + 'aria-disabled': !!disabled, } + const handleProps = { attrs: { role: 'slider', diff --git a/components/vc-slider/src/Range.jsx b/components/vc-slider/src/Range.jsx index 60ed67e05..170bfe447 100644 --- a/components/vc-slider/src/Range.jsx +++ b/components/vc-slider/src/Range.jsx @@ -1,5 +1,4 @@ import classNames from 'classnames' -import warning from 'warning' import PropTypes from '../../_util/vue-types' import BaseMixin from '../../_util/BaseMixin' import { initDefaultProps, hasProp } from '../../_util/props-util' @@ -32,7 +31,7 @@ const Range = { }), data () { const { count, min, max } = this - const initialValue = Array.apply(null, Array(count + 1)) + const initialValue = Array(...Array(count + 1)) .map(() => min) const defaultValue = hasProp(this, 'defaultValue') ? this.defaultValue : initialValue let { value } = this @@ -78,7 +77,10 @@ const Range = { this.setState({ bounds: nextBounds }) if (bounds.some(v => utils.isValueOutOfRange(v, minAmaxProps))) { - this.$emit('change', nextBounds) + const newValues = value.map((v) => { + return utils.ensureValueInRange(v, minAmaxProps) + }) + this.$emit('change', newValues) } }, onChange (state) { @@ -102,48 +104,46 @@ const Range = { this.startPosition = position const closestBound = this.getClosestBound(value) - const boundNeedMoving = this.getBoundNeedMoving(value, closestBound) + this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound) this.setState({ - sHandle: boundNeedMoving, - recent: boundNeedMoving, + sHandle: this.prevMovedHandleIndex, + recent: this.prevMovedHandleIndex, }) - const prevValue = bounds[boundNeedMoving] + const prevValue = bounds[this.prevMovedHandleIndex] if (value === prevValue) return const nextBounds = [...bounds] - nextBounds[boundNeedMoving] = value + nextBounds[this.prevMovedHandleIndex] = value this.onChange({ bounds: nextBounds }) }, onEnd () { - this.setState({ sHandle: null }) + this.setState({ sHandle: null }, this.onBlur) this.removeDocumentEvents() this.$emit('afterChange', this.bounds) }, onMove (e, position) { utils.pauseEvent(e) - const props = this.$props const { bounds, sHandle } = this const value = this.calcValueByPos(position) const oldValue = bounds[sHandle] if (value === oldValue) return - const nextBounds = [...bounds] - nextBounds[sHandle] = value - let nextHandle = sHandle - if (props.pushable !== false) { - this.pushSurroundingHandles(nextBounds, nextHandle) - } else if (props.allowCross) { - nextBounds.sort((a, b) => a - b) - nextHandle = nextBounds.indexOf(value) - } - this.onChange({ - sHandle: nextHandle, - bounds: nextBounds, - }) + this.moveTo(value) }, - onKeyboard () { - warning(true, 'Keyboard support is not yet supported for ranges.') + onKeyboard (e) { + const valueMutator = utils.getKeyboardValueMutator(e) + + if (valueMutator) { + utils.pauseEvent(e) + const { bounds, sHandle } = this + const oldValue = bounds[sHandle] + const mutatedValue = valueMutator(oldValue, this.$props) + const value = this.trimAlignValue(mutatedValue) + if (value === oldValue) return + const isFromKeyboardEvent = true + this.moveTo(value, isFromKeyboardEvent) + } }, getClosestBound (value) { const { bounds } = this @@ -152,7 +152,7 @@ const Range = { if (value > bounds[i]) { closestBound = i } } if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) { - closestBound = closestBound + 1 + closestBound += 1 } return closestBound }, @@ -197,6 +197,33 @@ const Range = { } return this._getPointsCache.points }, + + moveTo (value, isFromKeyboardEvent) { + const nextBounds = [...this.bounds] + const { sHandle } = this + nextBounds[sHandle] = value + let nextHandle = sHandle + if (this.$props.pushable !== false) { + this.pushSurroundingHandles(nextBounds, nextHandle) + } else if (this.$props.allowCross) { + nextBounds.sort((a, b) => a - b) + nextHandle = nextBounds.indexOf(value) + } + this.onChange({ + handle: nextHandle, + bounds: nextBounds, + }) + if (isFromKeyboardEvent) { + // known problem: because setState is async, + // so trigger focus will invoke handler's onEnd and another handler's onStart too early, + // cause onBeforeChange and onAfterChange receive wrong value. + // here use setState callback to hack,but not elegant + this.setState({}, () => { + this.handlesRefs[nextHandle].focus() + }) + } + }, + pushSurroundingHandles (bounds, handle) { const value = bounds[handle] let { pushable: threshold } = this @@ -322,6 +349,7 @@ const Range = { [handleClassName]: true, [`${handleClassName}-${i + 1}`]: true, }), + prefixCls, vertical, offset: offsets[i], value: v, diff --git a/components/vc-slider/src/Slider.jsx b/components/vc-slider/src/Slider.jsx index d2faa6b51..56b467867 100644 --- a/components/vc-slider/src/Slider.jsx +++ b/components/vc-slider/src/Slider.jsx @@ -98,6 +98,7 @@ const Slider = { this.startPosition = position if (value === sValue) return + this.prevMovedHandleIndex = 0 this.onChange({ sValue: value }) }, onEnd () { @@ -170,6 +171,7 @@ const Slider = { const offset = this.calcOffset(sValue) const handle = handleGenerator(this.$createElement, { className: `${prefixCls}-handle`, + prefixCls, vertical, offset, value: sValue, diff --git a/components/vc-slider/src/common/Marks.jsx b/components/vc-slider/src/common/Marks.jsx index 7d37e0d91..897f400be 100644 --- a/components/vc-slider/src/common/Marks.jsx +++ b/components/vc-slider/src/common/Marks.jsx @@ -11,10 +11,10 @@ const Marks = { included, upperBound, lowerBound, - max, min, + max, + min, } = context.props - // antd未开发完成 - // const { clickLabel } = context.listeners + const { clickLabel } = context.listeners const marksKeys = Object.keys(marks) const marksCount = marksKeys.length const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100 @@ -56,6 +56,8 @@ const Marks = { class={markClassName} style={markStyle} key={point} + onMousedown={(e) => clickLabel(e, point)} + onTouchstart={(e) => clickLabel(e, point)} > {markLabel} diff --git a/components/vc-slider/src/common/Steps.jsx b/components/vc-slider/src/common/Steps.jsx index 047a4db62..37cc9a7cd 100644 --- a/components/vc-slider/src/common/Steps.jsx +++ b/components/vc-slider/src/common/Steps.jsx @@ -8,9 +8,10 @@ const calcPoints = (vertical, marks, dots, step, min, max) => { ) const points = Object.keys(marks).map(parseFloat) if (dots) { - for (let i = min; i <= max; i = i + step) { - if (points.indexOf(i) >= 0) continue - points.push(i) + for (let i = min; i <= max; i += step) { + if (points.indexOf(i) === -1) { + points.push(i) + } } } return points diff --git a/components/vc-slider/src/common/createSlider.jsx b/components/vc-slider/src/common/createSlider.jsx index a35fb222d..3cff35115 100644 --- a/components/vc-slider/src/common/createSlider.jsx +++ b/components/vc-slider/src/common/createSlider.jsx @@ -80,12 +80,6 @@ export default function createSlider (Component) { } return {} }, - beforeDestroy () { - this.$nextTick(() => { - // if (super.componentWillUnmount) super.componentWillUnmount() - this.removeDocumentEvents() - }) - }, mounted () { this.$nextTick(() => { // Snapshot testing cannot handle refs, so be sure to null-check this. @@ -93,6 +87,12 @@ export default function createSlider (Component) { // this.setHandleRefs() }) }, + beforeDestroy () { + this.$nextTick(() => { + // if (super.componentWillUnmount) super.componentWillUnmount() + this.removeDocumentEvents() + }) + }, computed: { handlesRefs () { const handlesRefs = [] @@ -152,23 +152,10 @@ export default function createSlider (Component) { this.onEnd(e) this.$emit('blur', e) }, - addDocumentTouchEvents () { - // just work for Chrome iOS Safari and Android Browser - this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove) - this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd) - }, - addDocumentMouseEvents () { - this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove) - this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd) - }, - removeDocumentEvents () { - /* eslint-disable no-unused-expressions */ - this.onTouchMoveListener && this.onTouchMoveListener.remove() - this.onTouchUpListener && this.onTouchUpListener.remove() - - this.onMouseMoveListener && this.onMouseMoveListener.remove() - this.onMouseUpListener && this.onMouseUpListener.remove() - /* eslint-enable no-unused-expressions */ + onMouseUp () { + if (this.handlesRefs[this.prevMovedHandleIndex]) { + this.handlesRefs[this.prevMovedHandleIndex].clickFocus() + } }, onMouseMove (e) { if (!this.$refs.sliderRef) { @@ -192,15 +179,9 @@ export default function createSlider (Component) { this.onKeyboard(e) } }, - focus () { - if (!this.disabled) { - this.handlesRefs[0].focus() - } - }, - blur () { - if (!this.disabled) { - this.handlesRefs[0].blur() - } + onClickMarkLabel (e, value) { + e.stopPropagation() + this.onChange({ value }) }, getSliderStart () { const slider = this.$refs.sliderRef @@ -217,6 +198,38 @@ export default function createSlider (Component) { const coords = slider.getBoundingClientRect() return this.vertical ? coords.height : coords.width }, + addDocumentTouchEvents () { + // just work for Chrome iOS Safari and Android Browser + this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove) + this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd) + }, + addDocumentMouseEvents () { + this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove) + this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd) + }, + removeDocumentEvents () { + /* eslint-disable no-unused-expressions */ + this.onTouchMoveListener && this.onTouchMoveListener.remove() + this.onTouchUpListener && this.onTouchUpListener.remove() + + this.onMouseMoveListener && this.onMouseMoveListener.remove() + this.onMouseUpListener && this.onMouseUpListener.remove() + /* eslint-enable no-unused-expressions */ + }, + focus () { + if (!this.disabled) { + this.handlesRefs[0].focus() + } + }, + blur () { + if (!this.disabled) { + Object.keys(this.handlesRefs).forEach((key) => { + if (this.handlesRefs[key] && this.handlesRefs[key].blur) { + this.handlesRefs[key].blur() + } + }) + } + }, calcValue (offset) { const { vertical, min, max } = this const ratio = Math.abs(Math.max(offset, 0) / this.getSliderLength()) @@ -268,6 +281,9 @@ export default function createSlider (Component) { min, className: `${prefixCls}-mark`, }, + listeners: { + clickLabel: disabled ? noop : this.onClickMarkLabel, + }, } return (
{ return value }), - handleStyle: PropTypes.arrayOf(PropTypes.object), + handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), tipProps: PropTypes.object.def({}), }, data () { @@ -40,8 +40,16 @@ export default function createSliderWithTooltip (Component) { prefixCls = 'rc-slider-tooltip', overlay = tipFormatter(value), placement = 'top', - visible = visible || false, - ...restTooltipProps } = tipProps + visible = false, + ...restTooltipProps + } = tipProps + + let handleStyleWithIndex + if (Array.isArray(handleStyle)) { + handleStyleWithIndex = handleStyle[index] || handleStyle[0] + } else { + handleStyleWithIndex = handleStyle + } const tooltipProps = { props: { @@ -63,7 +71,7 @@ export default function createSliderWithTooltip (Component) { mouseleave: () => this.handleTooltipVisibleChange(index, false), }, style: { - ...handleStyle[0], + ...handleStyleWithIndex, }, }