From 62964ceb6fd1c31b1a21f8fd87ce459826c844e2 Mon Sep 17 00:00:00 2001 From: wangxueliang Date: Fri, 23 Mar 2018 18:37:32 +0800 Subject: [PATCH] add vc-slider v0.1 --- components/vc-slider/src/Handle.jsx | 153 +++-- components/vc-slider/src/Range.jsx | 591 +++++++++--------- components/vc-slider/src/Slider.jsx | 225 ++++--- components/vc-slider/src/common/Marks.jsx | 121 ++-- components/vc-slider/src/common/Steps.jsx | 43 +- .../vc-slider/src/common/createSlider.jsx | 442 ++++++------- .../vc-slider/src/createSliderWithTooltip.jsx | 161 ++--- components/vc-slider/src/utils.js | 3 +- 8 files changed, 845 insertions(+), 894 deletions(-) diff --git a/components/vc-slider/src/Handle.jsx b/components/vc-slider/src/Handle.jsx index 918ccc1e7..74fc1a8e4 100644 --- a/components/vc-slider/src/Handle.jsx +++ b/components/vc-slider/src/Handle.jsx @@ -1,74 +1,80 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import addEventListener from 'rc-util/lib/Dom/addEventListener'; -export default class Handle extends React.Component { - state = { - clickFocused: false, - } +import classNames from 'classnames' +import PropTypes from '../../../_util/vue-types' +import addEventListener from '../../../_util/Dom/addEventListener' +import BaseMixin from '../../../_util/BaseMixin' - componentDidMount() { - // mouseup won't trigger if mouse moved out of handle, - // so we listen on document here. - this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp); - } - - componentWillUnmount() { - if (this.onMouseUpListener) { - this.onMouseUpListener.remove(); +export default { + mixins: [BaseMixin], + props: { + prefixCls: PropTypes.string, + vertical: PropTypes.bool, + offset: PropTypes.number, + disabled: PropTypes.bool, + min: PropTypes.number, + max: PropTypes.number, + value: PropTypes.number, + tabIndex: PropTypes.number, + }, + data () { + return { + clickFocused: false, } - } - - setClickFocus(focused) { - this.setState({ clickFocused: focused }); - } - - handleMouseUp = () => { - if (document.activeElement === this.handle) { - this.setClickFocus(true); - } - } - - handleBlur = () => { - this.setClickFocus(false); - } - - handleKeyDown = () => { - this.setClickFocus(false); - } - - clickFocus() { - this.setClickFocus(true); - this.focus(); - } - - focus() { - this.handle.focus(); - } - - blur() { - this.handle.blur(); - } - - render() { + }, + mounted () { + this.$nextTick(() => { + this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp) + }) + }, + beforeDestroy () { + this.$nextTick(() => { + 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() + }, + blur () { + this.$refs.handle.blur() + }, + }, + render () { const { - prefixCls, vertical, offset, style, disabled, min, max, value, tabIndex, ...restProps, - } = this.props; + prefixCls, vertical, offset, disabled, min, max, value, tabIndex, ...restProps + } = this.$props const className = classNames( - this.props.className, { - [`${prefixCls}-handle-click-focused`]: this.state.clickFocused, + [`${prefixCls}-handle-click-focused`]: this.clickFocused, } - ); + ) - const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` }; + const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` } const elStyle = { - ...style, ...postionStyle, - }; - let ariaProps = {}; + } + let ariaProps = {} if (value !== undefined) { ariaProps = { ...ariaProps, @@ -76,34 +82,21 @@ export default class Handle extends React.Component { 'aria-valuemax': max, 'aria-valuenow': value, 'aria-disabled': !!disabled, - }; + } } return (
(this.handle = node)} - role="slider" + ref='handle' + role='slider' tabIndex= {disabled ? null : (tabIndex || 0)} {...ariaProps} {...restProps} - className={className} + class={className} style={elStyle} onBlur={this.handleBlur} - onKeyDown={this.handleKeyDown} + onKeydown={this.handleKeyDown} /> - ); - } + ) + }, } - -Handle.propTypes = { - prefixCls: PropTypes.string, - className: PropTypes.string, - vertical: PropTypes.bool, - offset: PropTypes.number, - style: PropTypes.object, - disabled: PropTypes.bool, - min: PropTypes.number, - max: PropTypes.number, - value: PropTypes.number, - tabIndex: PropTypes.number, -}; diff --git a/components/vc-slider/src/Range.jsx b/components/vc-slider/src/Range.jsx index 03876b052..7beeacf53 100644 --- a/components/vc-slider/src/Range.jsx +++ b/components/vc-slider/src/Range.jsx @@ -1,327 +1,308 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import shallowEqual from 'shallowequal'; -import Track from './common/Track'; -import createSlider from './common/createSlider'; -import * as utils from './utils'; +import classNames from 'classnames' +import PropTypes from '../../../_util/vue-types' +import BaseMixin from '../../../_util/BaseMixin' +import { initDefaultProps, hasProp } from '../../../_util/props-util' +import Track from './common/Track' +import createSlider from './common/createSlider' +import * as utils from './utils' -class Range extends React.Component { - static displayName = 'Range'; - static propTypes = { - defaultValue: PropTypes.arrayOf(PropTypes.number), - value: PropTypes.arrayOf(PropTypes.number), - count: PropTypes.number, - pushable: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.number, - ]), - allowCross: PropTypes.bool, - disabled: PropTypes.bool, - tabIndex: PropTypes.arrayOf(PropTypes.number), - }; - - static defaultProps = { +const rangeProps = { + defaultValue: PropTypes.arrayOf(PropTypes.number), + value: PropTypes.arrayOf(PropTypes.number), + count: PropTypes.number, + pushable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.number, + ]), + allowCross: PropTypes.bool, + disabled: PropTypes.bool, + tabIndex: PropTypes.arrayOf(PropTypes.number), +} +const Range = { + displayName: 'Range', + mixins: [BaseMixin], + props: initDefaultProps(rangeProps, { count: 1, allowCross: true, pushable: false, tabIndex: [], - }; - - constructor(props) { - super(props); - - const { count, min, max } = props; + }), + data () { + const { count, min, max } = this const initialValue = Array.apply(null, Array(count + 1)) - .map(() => min); - const defaultValue = 'defaultValue' in props ? - props.defaultValue : initialValue; - const value = props.value !== undefined ? - props.value : defaultValue; - const bounds = value.map((v, i) => this.trimAlignValue(v, i)); - const recent = bounds[0] === max ? 0 : bounds.length - 1; - - this.state = { + .map(() => min) + const defaultValue = hasProp(this, 'defaultValue') ? this.defaultValue : initialValue + let { value } = this + if (value === undefined) { + value = defaultValue + } + const bounds = value.map((v, i) => this.trimAlignValue(v, i)) + const recent = bounds[0] === max ? 0 : bounds.length - 1 + return { handle: null, recent, bounds, - }; - } - - componentWillReceiveProps(nextProps) { - if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return; - if (this.props.min === nextProps.min && - this.props.max === nextProps.max && - shallowEqual(this.props.value, nextProps.value)) { - return; } + }, + watch: { + value: { + handler (val) { + const { min, max } = this + this.setChangeValue(val, min, max) + }, + deep: true, + }, + min (val) { + const { bounds, max } = this + this.setChangeValue(bounds, val, max) + }, + max (val) { + const { bounds, min } = this + this.setChangeValue(bounds, min, val) + }, + }, + methods: { + setChangeValue (value, min, max) { + const { bounds } = this + const newValue = value || bounds + const minAmaxProps = { + min, + max, + } + const nextBounds = newValue.map((v, i) => this.trimAlignValue(v, i, minAmaxProps)) + if (nextBounds.length === bounds.length && nextBounds.every((v, i) => v === bounds[i])) return - const { bounds } = this.state; - const value = nextProps.value || bounds; - const nextBounds = value.map((v, i) => this.trimAlignValue(v, i, nextProps)); - if (nextBounds.length === bounds.length && nextBounds.every((v, i) => v === bounds[i])) return; + this.setState({ bounds: nextBounds }) - this.setState({ bounds: nextBounds }); + if (bounds.some(v => utils.isValueOutOfRange(v, minAmaxProps))) { + const newValues = newValue.map((v) => { + return utils.ensureValueInRange(v, minAmaxProps) + }) + this.$emit('change', newValues) + } + }, + onChange (state) { + const isNotControlled = !hasProp(this, 'value') + if (isNotControlled) { + this.setState(state) + } else if (state.handle !== undefined) { + this.setState({ handle: state.handle }) + } - if (bounds.some(v => utils.isValueOutOfRange(v, nextProps))) { - const newValues = value.map((v) => { - return utils.ensureValueInRange(v, nextProps); - }); - this.props.onChange(newValues); - } - } + const data = { ...this.$data, ...state } + const changedValue = data.bounds + this.$emit('change', changedValue) + }, + onStart (position) { + const { bounds } = this + this.$emit('beforeChange', bounds) - onChange(state) { - const props = this.props; - const isNotControlled = !('value' in props); - if (isNotControlled) { - this.setState(state); - } else if (state.handle !== undefined) { - this.setState({ handle: state.handle }); - } + const value = this.calcValueByPos(position) - const data = { ...this.state, ...state }; - const changedValue = data.bounds; - props.onChange(changedValue); - } + const closestBound = this.getClosestBound(value) + this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound) - onStart(position) { - const props = this.props; - const state = this.state; - const bounds = this.getValue(); - props.onBeforeChange(bounds); + this.setState({ + handle: this.prevMovedHandleIndex, + recent: this.prevMovedHandleIndex, + }) - const value = this.calcValueByPos(position); - this.startValue = value; - this.startPosition = position; + const prevValue = bounds[this.prevMovedHandleIndex] + if (value === prevValue) return - const closestBound = this.getClosestBound(value); - this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound); + const nextBounds = [...bounds] + nextBounds[this.prevMovedHandleIndex] = value + this.$emit('change', { bounds: nextBounds }) + }, + onEnd () { + this.removeDocumentEvents() + this.$emit('afterChange', this.bounds) + }, + onMove (e, position) { + utils.pauseEvent(e) + const { bounds, handle } = this + const value = this.calcValueByPos(position) + const oldValue = bounds[handle] + if (value === oldValue) return - this.setState({ - handle: this.prevMovedHandleIndex, - recent: this.prevMovedHandleIndex, - }); + this.moveTo(value) + }, + onKeyboard (e) { + const valueMutator = utils.getKeyboardValueMutator(e) - const prevValue = bounds[this.prevMovedHandleIndex]; - if (value === prevValue) return; + if (valueMutator) { + utils.pauseEvent(e) + const { bounds, handle } = this + const oldValue = bounds[handle] + 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 + let closestBound = 0 + for (let i = 1; i < bounds.length - 1; ++i) { + if (value > bounds[i]) { closestBound = i } + } + if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) { + closestBound = closestBound + 1 + } + return closestBound + }, + getBoundNeedMoving (value, closestBound) { + const { bounds, recent } = this + let boundNeedMoving = closestBound + const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]) - const nextBounds = [...state.bounds]; - nextBounds[this.prevMovedHandleIndex] = value; - this.onChange({ bounds: nextBounds }); - } + if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) { + boundNeedMoving = recent + } - onEnd = () => { - this.removeDocumentEvents(); - this.props.onAfterChange(this.getValue()); - } + if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) { + boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1 + } + return boundNeedMoving + }, + getLowerBound () { + return this.bounds[0] + }, + getUpperBound () { + const { bounds } = this + return bounds[bounds.length - 1] + }, + /** + * Returns an array of possible slider points, taking into account both + * `marks` and `step`. The result is cached. + */ + getPoints () { + const { marks, step, min, max } = this + const cache = this._getPointsCache + if (!cache || cache.marks !== marks || cache.step !== step) { + const pointsObject = { ...marks } + if (step !== null) { + for (let point = min; point <= max; point += step) { + pointsObject[point] = point + } + } + const points = Object.keys(pointsObject).map(parseFloat) + points.sort((a, b) => a - b) + this._getPointsCache = { marks, step, points } + } + return this._getPointsCache.points + }, + moveTo (value, isFromKeyboardEvent) { + const { bounds, handle } = this + const nextBounds = [...bounds] + nextBounds[handle] = value + let nextHandle = handle + if (this.pushable !== false) { + this.pushSurroundingHandles(nextBounds, nextHandle) + } else if (this.allowCross) { + nextBounds.sort((a, b) => a - b) + nextHandle = nextBounds.indexOf(value) + } + this.$emit('change', { + 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 + threshold = Number(threshold) - onMove(e, position) { - utils.pauseEvent(e); - const state = this.state; + let direction = 0 + if (bounds[handle + 1] - value < threshold) { + direction = +1 // push to right + } + if (value - bounds[handle - 1] < threshold) { + direction = -1 // push to left + } - const value = this.calcValueByPos(position); - const oldValue = state.bounds[state.handle]; - if (value === oldValue) return; + if (direction === 0) { return } - this.moveTo(value); - } - - onKeyboard(e) { - const valueMutator = utils.getKeyboardValueMutator(e); - - if (valueMutator) { - utils.pauseEvent(e); - const { state, props } = this; - const { bounds, handle } = state; - const oldValue = bounds[handle]; - const mutatedValue = valueMutator(oldValue, props); - const value = this.trimAlignValue(mutatedValue); - if (value === oldValue) return; - const isFromKeyboardEvent = true; - this.moveTo(value, isFromKeyboardEvent); - } - } - - getValue() { - return this.state.bounds; - } - - getClosestBound(value) { - const { bounds } = this.state; - let closestBound = 0; - for (let i = 1; i < bounds.length - 1; ++i) { - if (value > bounds[i]) { closestBound = i; } - } - if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) { - closestBound = closestBound + 1; - } - return closestBound; - } - - getBoundNeedMoving(value, closestBound) { - const { bounds, recent } = this.state; - let boundNeedMoving = closestBound; - const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]); - - if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) { - boundNeedMoving = recent; - } - - if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) { - boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1; - } - return boundNeedMoving; - } - - getLowerBound() { - return this.state.bounds[0]; - } - - getUpperBound() { - const { bounds } = this.state; - return bounds[bounds.length - 1]; - } - - /** - * Returns an array of possible slider points, taking into account both - * `marks` and `step`. The result is cached. - */ - getPoints() { - const { marks, step, min, max } = this.props; - const cache = this._getPointsCache; - if (!cache || cache.marks !== marks || cache.step !== step) { - const pointsObject = { ...marks }; - if (step !== null) { - for (let point = min; point <= max; point += step) { - pointsObject[point] = point; + const nextHandle = handle + direction + const diffToNext = direction * (bounds[nextHandle] - value) + if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) { + // revert to original value if pushing is impossible + bounds[handle] = bounds[nextHandle] - (direction * threshold) + } + }, + pushHandle (bounds, handle, direction, amount) { + const originalValue = bounds[handle] + let currentValue = bounds[handle] + while (direction * (currentValue - originalValue) < amount) { + if (!this.pushHandleOnePoint(bounds, handle, direction)) { + // can't push handle enough to create the needed `amount` gap, so we + // revert its position to the original value + bounds[handle] = originalValue + return false + } + currentValue = bounds[handle] + } + // the handle was pushed enough to create the needed `amount` gap + return true + }, + pushHandleOnePoint (bounds, handle, direction) { + const points = this.getPoints() + const pointIndex = points.indexOf(bounds[handle]) + const nextPointIndex = pointIndex + direction + if (nextPointIndex >= points.length || nextPointIndex < 0) { + // reached the minimum or maximum available point, can't push anymore + return false + } + const nextHandle = handle + direction + const nextValue = points[nextPointIndex] + const { pushable: threshold } = this + const diffToNext = direction * (bounds[nextHandle] - nextValue) + if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) { + // couldn't push next handle, so we won't push this one either + return false + } + // push the handle + bounds[handle] = nextValue + return true + }, + trimAlignValue (v, handle, nextProps = {}) { + const mergedProps = { ...this, ...nextProps } + const valInRange = utils.ensureValueInRange(v, mergedProps) + const valNotConflict = this.ensureValueNotConflict(handle, valInRange, mergedProps) + return utils.ensureValuePrecision(valNotConflict, mergedProps) + }, + ensureValueNotConflict (handle, val, { allowCross, pushable: thershold }) { + const state = this.$data || {} + const { bounds } = state + handle = handle === undefined ? state.handle : handle + thershold = Number(thershold) + /* eslint-disable eqeqeq */ + if (!allowCross && handle != null && bounds !== undefined) { + if (handle > 0 && val <= (bounds[handle - 1] + thershold)) { + return bounds[handle - 1] + thershold + } + if (handle < bounds.length - 1 && val >= (bounds[handle + 1] - thershold)) { + return bounds[handle + 1] - thershold } } - const points = Object.keys(pointsObject).map(parseFloat); - points.sort((a, b) => a - b); - this._getPointsCache = { marks, step, points }; - } - return this._getPointsCache.points; - } - - moveTo(value, isFromKeyboardEvent) { - const { state, props } = this; - const nextBounds = [...state.bounds]; - nextBounds[state.handle] = value; - let nextHandle = state.handle; - if (props.pushable !== false) { - this.pushSurroundingHandles(nextBounds, nextHandle); - } else if (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.props; - threshold = Number(threshold); - - let direction = 0; - if (bounds[handle + 1] - value < threshold) { - direction = +1; // push to right - } - if (value - bounds[handle - 1] < threshold) { - direction = -1; // push to left - } - - if (direction === 0) { return; } - - const nextHandle = handle + direction; - const diffToNext = direction * (bounds[nextHandle] - value); - if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) { - // revert to original value if pushing is impossible - bounds[handle] = bounds[nextHandle] - (direction * threshold); - } - } - - pushHandle(bounds, handle, direction, amount) { - const originalValue = bounds[handle]; - let currentValue = bounds[handle]; - while (direction * (currentValue - originalValue) < amount) { - if (!this.pushHandleOnePoint(bounds, handle, direction)) { - // can't push handle enough to create the needed `amount` gap, so we - // revert its position to the original value - bounds[handle] = originalValue; - return false; - } - currentValue = bounds[handle]; - } - // the handle was pushed enough to create the needed `amount` gap - return true; - } - - pushHandleOnePoint(bounds, handle, direction) { - const points = this.getPoints(); - const pointIndex = points.indexOf(bounds[handle]); - const nextPointIndex = pointIndex + direction; - if (nextPointIndex >= points.length || nextPointIndex < 0) { - // reached the minimum or maximum available point, can't push anymore - return false; - } - const nextHandle = handle + direction; - const nextValue = points[nextPointIndex]; - const { pushable: threshold } = this.props; - const diffToNext = direction * (bounds[nextHandle] - nextValue); - if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) { - // couldn't push next handle, so we won't push this one either - return false; - } - // push the handle - bounds[handle] = nextValue; - return true; - } - - trimAlignValue(v, handle, nextProps = {}) { - const mergedProps = { ...this.props, ...nextProps }; - const valInRange = utils.ensureValueInRange(v, mergedProps); - const valNotConflict = this.ensureValueNotConflict(handle, valInRange, mergedProps); - return utils.ensureValuePrecision(valNotConflict, mergedProps); - } - - ensureValueNotConflict(handle, val, { allowCross, pushable: thershold }) { - const state = this.state || {}; - const { bounds } = state; - handle = handle === undefined ? state.handle : handle; - thershold = Number(thershold); - /* eslint-disable eqeqeq */ - if (!allowCross && handle != null && bounds !== undefined) { - if (handle > 0 && val <= (bounds[handle - 1] + thershold)) { - return bounds[handle - 1] + thershold; - } - if (handle < bounds.length - 1 && val >= (bounds[handle + 1] - thershold)) { - return bounds[handle + 1] - thershold; - } - } - /* eslint-enable eqeqeq */ - return val; - } - - render() { + /* eslint-enable eqeqeq */ + return val + }, + }, + render () { const { handle, bounds, - } = this.state; - const { prefixCls, vertical, included, @@ -332,11 +313,11 @@ class Range extends React.Component { trackStyle, handleStyle, tabIndex, - } = this.props; + } = this - const offsets = bounds.map(v => this.calcOffset(v)); + const offsets = bounds.map(v => this.calcOffset(v)) - const handleClassName = `${prefixCls}-handle`; + const handleClassName = `${prefixCls}-handle` const handles = bounds.map((v, i) => handleGenerator({ className: classNames({ [handleClassName]: true, @@ -354,14 +335,14 @@ class Range extends React.Component { disabled, style: handleStyle[i], ref: h => this.saveHandle(i, h), - })); + })) const tracks = bounds.slice(0, -1).map((_, index) => { - const i = index + 1; + const i = index + 1 const trackClassName = classNames({ [`${prefixCls}-track`]: true, [`${prefixCls}-track-${i}`]: true, - }); + }) return ( - ); - }); + ) + }) - return { tracks, handles }; - } + return { tracks, handles } + }, } -export default createSlider(Range); +export default createSlider(Range) diff --git a/components/vc-slider/src/Slider.jsx b/components/vc-slider/src/Slider.jsx index 41a1094ad..de836d369 100644 --- a/components/vc-slider/src/Slider.jsx +++ b/components/vc-slider/src/Slider.jsx @@ -1,142 +1,142 @@ -/* eslint-disable react/prop-types */ -import React from 'react' -import PropTypes from 'prop-types' -import warning from 'warning' +import PropTypes from '../../../_util/vue-types' +import warning from '../../../_util/warning' +import BaseMixin from '../../../_util/BaseMixin' +import { hasProp } from '../../../_util/props-util' import Track from './common/Track' import createSlider from './common/createSlider' import * as utils from './utils' -class Slider extends React.Component { - static propTypes = { +const Slider = { + mixins: [BaseMixin], + props: { defaultValue: PropTypes.number, value: PropTypes.number, disabled: PropTypes.bool, autoFocus: PropTypes.bool, tabIndex: PropTypes.number, - }; + }, + data () { + const defaultValue = this.defaultValue !== undefined + ? this.defaultValue : this.min + const value = this.value !== undefined + ? this.value : defaultValue - constructor (props) { - super(props) - - const defaultValue = props.defaultValue !== undefined - ? props.defaultValue : props.min - const value = props.value !== undefined - ? props.value : defaultValue - - this.state = { - value: this.trimAlignValue(value), - dragging: false, - } if (process.env.NODE_ENV !== 'production') { warning( - !('minimumTrackStyle' in props), + !hasProp(this, 'minimumTrackStyle'), 'minimumTrackStyle will be deprecate, please use trackStyle instead.' ) warning( - !('maximumTrackStyle' in props), + !hasProp(this, 'maximumTrackStyle'), 'maximumTrackStyle will be deprecate, please use railStyle instead.' ) } - } - - componentDidMount () { - const { autoFocus, disabled } = this.props - if (autoFocus && !disabled) { - this.focus() + return { + sValue: this.trimAlignValue(value), + dragging: false, } - } + }, + mounted () { + this.$nextTick(() => { + const { autoFocus, disabled } = this + if (autoFocus && !disabled) { + this.focus() + } + }) + }, + watch: { + value: { + handler (val) { + const { min, max } = this + this.setChangeValue(val, min, max) + }, + deep: true, + }, + min (val) { + const { sValue, max } = this + this.setChangeValue(sValue, val, max) + }, + max (val) { + const { sValue, min } = this + this.setChangeValue(sValue, min, val) + }, + }, + methods: { + setChangeValue (value, min, max) { + const minAmaxProps = { + min, + max, + } + const newValue = value !== undefined + ? value : this.sValue + const nextValue = this.trimAlignValue(newValue, minAmaxProps) + if (nextValue === this.sValue) return - componentWillReceiveProps (nextProps) { - if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return + this.setState({ sValue: nextValue }) + if (utils.isValueOutOfRange(newValue, minAmaxProps)) { + this.$emit('change', nextValue) + } + }, + onChange (state) { + const isNotControlled = !hasProp(this, 'value') + if (isNotControlled) { + this.setState(state) + } - const prevValue = this.state.value - const value = nextProps.value !== undefined - ? nextProps.value : prevValue - const nextValue = this.trimAlignValue(value, nextProps) - if (nextValue === prevValue) return + const changedValue = state.sValue + this.$emit('change', changedValue) + }, + onStart (position) { + this.setState({ dragging: true }) + const { sValue } = this + this.$emit('beforeChange', sValue) - this.setState({ value: nextValue }) - if (utils.isValueOutOfRange(value, nextProps)) { - this.props.onChange(nextValue) - } - } + const value = this.calcValueByPos(position) - onChange (state) { - const props = this.props - const isNotControlled = !('value' in props) - if (isNotControlled) { - this.setState(state) - } + if (value === sValue) return - const changedValue = state.value - props.onChange(changedValue) - } + this.prevMovedHandleIndex = 0 - onStart (position) { - this.setState({ dragging: true }) - const props = this.props - const prevValue = this.getValue() - props.onBeforeChange(prevValue) - - const value = this.calcValueByPos(position) - this.startValue = value - this.startPosition = position - - if (value === prevValue) return - - this.prevMovedHandleIndex = 0 - - this.onChange({ value }) - } - - onEnd = () => { - this.setState({ dragging: false }) - this.removeDocumentEvents() - this.props.onAfterChange(this.getValue()) - } - - onMove (e, position) { - utils.pauseEvent(e) - const { value: oldValue } = this.state - const value = this.calcValueByPos(position) - if (value === oldValue) return - - this.onChange({ value }) - } - - onKeyboard (e) { - const valueMutator = utils.getKeyboardValueMutator(e) - - if (valueMutator) { + this.onChange({ sValue: value }) + }, + onEnd () { + this.setState({ dragging: false }) + this.removeDocumentEvents() + this.$emit('afterChange', this.sValue) + }, + onMove (e, position) { utils.pauseEvent(e) - const state = this.state - const oldValue = state.value - const mutatedValue = valueMutator(oldValue, this.props) - const value = this.trimAlignValue(mutatedValue) - if (value === oldValue) return + const { sValue } = this + const value = this.calcValueByPos(position) + if (value === sValue) return - this.onChange({ value }) - } - } + this.onChange({ sValue: value }) + }, + onKeyboard (e) { + const valueMutator = utils.getKeyboardValueMutator(e) - getValue () { - return this.state.value - } - - getLowerBound () { - return this.props.min - } - - getUpperBound () { - return this.state.value - } - - trimAlignValue (v, nextProps = {}) { - const mergedProps = { ...this.props, ...nextProps } - const val = utils.ensureValueInRange(v, mergedProps) - return utils.ensureValuePrecision(val, mergedProps) - } + if (valueMutator) { + utils.pauseEvent(e) + const { sValue } = this + const mutatedValue = valueMutator(sValue, this.$props) + const value = this.trimAlignValue(mutatedValue) + if (value === sValue) return + this.onChange({ sValue: value }) + } + }, + getLowerBound () { + return this.min + }, + getUpperBound () { + return this.sValue + }, + trimAlignValue (v, nextProps = {}) { + const mergedProps = { ...this.$props, ...nextProps } + const val = utils.ensureValueInRange(v, mergedProps) + return utils.ensureValuePrecision(val, mergedProps) + }, + }, render () { const { prefixCls, @@ -150,7 +150,7 @@ class Slider extends React.Component { min, max, handle: handleGenerator, - } = this.props + } = this const { value, dragging } = this.state const offset = this.calcOffset(value) const handle = handleGenerator({ @@ -183,9 +183,8 @@ class Slider extends React.Component { }} /> ) - return { tracks: track, handles: handle } - } + }, } export default createSlider(Slider) diff --git a/components/vc-slider/src/common/Marks.jsx b/components/vc-slider/src/common/Marks.jsx index f869ca143..0adbcafdd 100644 --- a/components/vc-slider/src/common/Marks.jsx +++ b/components/vc-slider/src/common/Marks.jsx @@ -1,66 +1,71 @@ -import React from 'react' import classNames from 'classnames' -const Marks = ({ - className, - vertical, - marks, - included, - upperBound, - lowerBound, - max, min, - onClickLabel, -}) => { - const marksKeys = Object.keys(marks) - const marksCount = marksKeys.length - const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100 - const markWidth = unit * 0.9 +const Marks = { + functional: true, + render (createElement, context) { + const { + className, + vertical, + marks, + included, + upperBound, + lowerBound, + max, min, + } = context.props + const { clickLabel } = context.listeners + const marksKeys = Object.keys(marks) + const marksCount = marksKeys.length + const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100 + const markWidth = unit * 0.9 - const range = max - min - const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => { - const markPoint = marks[point] - const markPointIsObject = typeof markPoint === 'object' && - !React.isValidElement(markPoint) - const markLabel = markPointIsObject ? markPoint.label : markPoint - if (!markLabel && markLabel !== 0) { - return null - } + const range = max - min + const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => { + const markPoint = marks[point] + // todo + // const markPointIsObject = typeof markPoint === 'object' && + // !React.isValidElement(markPoint) + const markPointIsObject = typeof markPoint === 'object' + const markLabel = markPointIsObject ? markPoint.label : markPoint + if (!markLabel && markLabel !== 0) { + return null + } - const isActive = (!included && point === upperBound) || - (included && point <= upperBound && point >= lowerBound) - const markClassName = classNames({ - [`${className}-text`]: true, - [`${className}-text-active`]: isActive, + const isActive = (!included && point === upperBound) || + (included && point <= upperBound && point >= lowerBound) + const markClassName = classNames({ + [`${className}-text`]: true, + [`${className}-text-active`]: isActive, + }) + + const bottomStyle = { + marginBottom: '-50%', + bottom: `${(point - min) / range * 100}%`, + } + + const leftStyle = { + width: `${markWidth}%`, + marginLeft: `${-markWidth / 2}%`, + left: `${(point - min) / range * 100}%`, + } + + const style = vertical ? bottomStyle : leftStyle + const markStyle = markPointIsObject + ? { ...style, ...markPoint.style } : style + return ( + clickLabel(e, point)} + onTouchStart={(e) => clickLabel(e, point)} + > + {markLabel} + + ) }) - const bottomStyle = { - marginBottom: '-50%', - bottom: `${(point - min) / range * 100}%`, - } - - const leftStyle = { - width: `${markWidth}%`, - marginLeft: `${-markWidth / 2}%`, - left: `${(point - min) / range * 100}%`, - } - - const style = vertical ? bottomStyle : leftStyle - const markStyle = markPointIsObject - ? { ...style, ...markPoint.style } : style - return ( - onClickLabel(e, point)} - onTouchStart={(e) => onClickLabel(e, point)} - > - {markLabel} - - ) - }) - - return
{elements}
-}; + return
{elements}
+ }, +} export default Marks diff --git a/components/vc-slider/src/common/Steps.jsx b/components/vc-slider/src/common/Steps.jsx index 2859a9582..f9a734b4c 100644 --- a/components/vc-slider/src/common/Steps.jsx +++ b/components/vc-slider/src/common/Steps.jsx @@ -1,6 +1,5 @@ -import React from 'react' import classNames from 'classnames' -import warning from 'warning' +import warning from '../../../_util/warning' const calcPoints = (vertical, marks, dots, step, min, max) => { warning( @@ -17,28 +16,32 @@ const calcPoints = (vertical, marks, dots, step, min, max) => { return points } -const Steps = ({ prefixCls, vertical, marks, dots, step, included, - lowerBound, upperBound, max, min, dotStyle, activeDotStyle }) => { - const range = max - min - const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => { - const offset = `${Math.abs(point - min) / range * 100}%` +const Steps = { + functional: true, + render (createElement, context) { + const { prefixCls, vertical, marks, dots, step, included, + lowerBound, upperBound, max, min, dotStyle, activeDotStyle } = context.data + const range = max - min + const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => { + const offset = `${Math.abs(point - min) / range * 100}%` - const isActived = (!included && point === upperBound) || - (included && point <= upperBound && point >= lowerBound) - let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle } - if (isActived) { - style = { ...style, ...activeDotStyle } - } + const isActived = (!included && point === upperBound) || + (included && point <= upperBound && point >= lowerBound) + let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle } + if (isActived) { + style = { ...style, ...activeDotStyle } + } - const pointClassName = classNames({ - [`${prefixCls}-dot`]: true, - [`${prefixCls}-dot-active`]: isActived, + const pointClassName = classNames({ + [`${prefixCls}-dot`]: true, + [`${prefixCls}-dot-active`]: isActived, + }) + + return }) - return - }) - - return
{elements}
+ return
{elements}
+ }, } export default Steps diff --git a/components/vc-slider/src/common/createSlider.jsx b/components/vc-slider/src/common/createSlider.jsx index 9ef8ddcbe..1fdbd0256 100644 --- a/components/vc-slider/src/common/createSlider.jsx +++ b/components/vc-slider/src/common/createSlider.jsx @@ -1,8 +1,8 @@ -import React from 'react' -import PropTypes from 'prop-types' -import addEventListener from 'rc-util/lib/Dom/addEventListener' import classNames from 'classnames' -import warning from 'warning' +import PropTypes from '../../../_util/vue-types' +import addEventListener from '../../../_util/Dom/addEventListener' +import warning from '../../../_util/warning' +import { initDefaultProps } from '../../../_util/props-util' import Steps from './Steps' import Marks from './Marks' import Handle from '../Handle' @@ -11,42 +11,32 @@ import * as utils from '../utils' function noop () {} export default function createSlider (Component) { - return class ComponentEnhancer extends Component { - static displayName = `ComponentEnhancer(${Component.displayName})`; - static propTypes = { - ...Component.propTypes, - min: PropTypes.number, - max: PropTypes.number, - step: PropTypes.number, - marks: PropTypes.object, - included: PropTypes.bool, - className: PropTypes.string, - prefixCls: PropTypes.string, - disabled: PropTypes.bool, - children: PropTypes.any, - onBeforeChange: PropTypes.func, - onChange: PropTypes.func, - onAfterChange: PropTypes.func, - handle: PropTypes.func, - dots: PropTypes.bool, - vertical: PropTypes.bool, - style: PropTypes.object, - minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate - maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate - handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), - trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), - railStyle: PropTypes.object, - dotStyle: PropTypes.object, - activeDotStyle: PropTypes.object, - autoFocus: PropTypes.bool, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - }; - - static defaultProps = { + // const displayName = `ComponentEnhancer(${Component.displayName})` + const propTypes = { + ...Component.propTypes, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + marks: PropTypes.object, + included: PropTypes.bool, + prefixCls: PropTypes.string, + disabled: PropTypes.bool, + handle: PropTypes.func, + dots: PropTypes.bool, + vertical: PropTypes.bool, + minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate + maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate + handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), + railStyle: PropTypes.object, + dotStyle: PropTypes.object, + activeDotStyle: PropTypes.object, + autoFocus: PropTypes.bool, + } + return { + props: initDefaultProps(propTypes, { ...Component.defaultProps, prefixCls: 'rc-slider', - className: '', min: 0, max: 100, step: 1, @@ -55,9 +45,6 @@ export default function createSlider (Component) { delete restProps.dragging return }, - onBeforeChange: noop, - onChange: noop, - onAfterChange: noop, included: true, disabled: false, dots: false, @@ -67,13 +54,10 @@ export default function createSlider (Component) { railStyle: {}, dotStyle: {}, activeDotStyle: {}, - }; - - constructor (props) { - super(props) - + }), + data () { if (process.env.NODE_ENV !== 'production') { - const { step, max, min } = props + const { step, max, min } = this warning( step && Math.floor(step) === step ? (max - min) % step === 0 : true, 'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)', @@ -82,190 +66,163 @@ export default function createSlider (Component) { ) } this.handlesRefs = {} - } - - componentWillUnmount () { - if (super.componentWillUnmount) super.componentWillUnmount() - this.removeDocumentEvents() - } - - componentDidMount () { - // Snapshot testing cannot handle refs, so be sure to null-check this. - this.document = this.sliderRef && this.sliderRef.ownerDocument - } - - onMouseDown = (e) => { - if (e.button !== 0) { return } - - const isVertical = this.props.vertical - let position = utils.getMousePosition(isVertical, e) - if (!utils.isEventFromHandle(e, this.handlesRefs)) { - this.dragOffset = 0 - } else { - const handlePosition = utils.getHandleCenterPosition(isVertical, e.target) - this.dragOffset = position - handlePosition - position = handlePosition - } - this.removeDocumentEvents() - this.onStart(position) - this.addDocumentMouseEvents() - } - - onTouchStart = (e) => { - if (utils.isNotTouchEvent(e)) return - - const isVertical = this.props.vertical - let position = utils.getTouchPosition(isVertical, e) - if (!utils.isEventFromHandle(e, this.handlesRefs)) { - this.dragOffset = 0 - } else { - const handlePosition = utils.getHandleCenterPosition(isVertical, e.target) - this.dragOffset = position - handlePosition - position = handlePosition - } - this.onStart(position) - this.addDocumentTouchEvents() - utils.pauseEvent(e) - } - - onFocus = (e) => { - const { onFocus, vertical } = this.props - if (utils.isEventFromHandle(e, this.handlesRefs)) { - const handlePosition = utils.getHandleCenterPosition(vertical, e.target) - this.dragOffset = 0 - this.onStart(handlePosition) - utils.pauseEvent(e) - if (onFocus) { - onFocus(e) + 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. + this.document = this.$refs.sliderRef && this.$refs.sliderRef.ownerDocument + }) + }, + methods: { + onMouseDown (e) { + if (e.button !== 0) { return } + const isVertical = this.vertical + let position = utils.getMousePosition(isVertical, e) + if (!utils.isEventFromHandle(e, this.handlesRefs)) { + this.dragOffset = 0 + } else { + const handlePosition = utils.getHandleCenterPosition(isVertical, e.target) + this.dragOffset = position - handlePosition + position = handlePosition } - } - } + this.removeDocumentEvents() + this.onStart(position) + this.addDocumentMouseEvents() + }, + onTouchStart (e) { + if (utils.isNotTouchEvent(e)) return - onBlur = (e) => { - const { onBlur } = this.props - this.onEnd(e) - if (onBlur) { - onBlur(e) - } - }; + const isVertical = this.vertical + let position = utils.getTouchPosition(isVertical, e) + if (!utils.isEventFromHandle(e, this.handlesRefs)) { + this.dragOffset = 0 + } else { + const handlePosition = utils.getHandleCenterPosition(isVertical, e.target) + this.dragOffset = position - handlePosition + position = handlePosition + } + this.onStart(position) + this.addDocumentTouchEvents() + utils.pauseEvent(e) + }, + onFocus (e) { + const { vertical } = this + if (utils.isEventFromHandle(e, this.handlesRefs)) { + const handlePosition = utils.getHandleCenterPosition(vertical, e.target) + this.dragOffset = 0 + this.onStart(handlePosition) + utils.pauseEvent(e) + this.$emit('focus', e) + } + }, + onBlur (e) { + 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() - 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) - } + 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) { + this.onEnd() + return + } + const position = utils.getMousePosition(this.vertical, e) + this.onMove(e, position - this.dragOffset) + }, + onTouchMove (e) { + if (utils.isNotTouchEvent(e) || !this.$refs.sliderRef) { + this.onEnd() + return + } - addDocumentMouseEvents () { - this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove) - this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd) - } + const position = utils.getTouchPosition(this.vertical, e) + this.onMove(e, position - this.dragOffset) + }, + onKeyDown (e) { + if (this.$refs.sliderRef && utils.isEventFromHandle(e, this.handlesRefs)) { + this.onKeyboard(e) + } + }, + focus () { + if (!this.disabled) { + this.handlesRefs[0].focus() + } + }, + blur () { + if (!this.disabled) { + this.handlesRefs[0].blur() + } + }, + getSliderStart () { + const slider = this.$refs.sliderRef + const rect = slider.getBoundingClientRect() - 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.sliderRef) { - this.onEnd() - return; - } - const position = utils.getMousePosition(this.props.vertical, e) - this.onMove(e, position - this.dragOffset) - } - - onTouchMove = (e) => { - if (utils.isNotTouchEvent(e) || !this.sliderRef) { - this.onEnd() - return; - } - - const position = utils.getTouchPosition(this.props.vertical, e) - this.onMove(e, position - this.dragOffset) - } - - onKeyDown = (e) => { - if (this.sliderRef && utils.isEventFromHandle(e, this.handlesRefs)) { - this.onKeyboard(e) - } - } - - focus () { - if (!this.props.disabled) { - this.handlesRefs[0].focus() - } - } - - blur () { - if (!this.props.disabled) { - this.handlesRefs[0].blur() - } - } - - getSliderStart () { - const slider = this.sliderRef - const rect = slider.getBoundingClientRect() - - return this.props.vertical ? rect.top : rect.left - } - - getSliderLength () { - const slider = this.sliderRef - if (!slider) { - return 0 - } - - const coords = slider.getBoundingClientRect() - return this.props.vertical ? coords.height : coords.width - } - - calcValue (offset) { - const { vertical, min, max } = this.props - const ratio = Math.abs(Math.max(offset, 0) / this.getSliderLength()) - const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min - return value - } - - calcValueByPos (position) { - const pixelOffset = position - this.getSliderStart() - const nextValue = this.trimAlignValue(this.calcValue(pixelOffset)) - return nextValue - } - - calcOffset (value) { - const { min, max } = this.props - const ratio = (value - min) / (max - min) - return ratio * 100 - } - - saveSlider = (slider) => { - this.sliderRef = slider - } - - saveHandle (index, handle) { - this.handlesRefs[index] = handle - } - - onClickMarkLabel = (e, value) => { - e.stopPropagation() - this.onChange({ value }) - } + return this.vertical ? rect.top : rect.left + }, + getSliderLength () { + const slider = this.$refs.sliderRef + if (!slider) { + return 0 + } + const coords = slider.getBoundingClientRect() + return this.vertical ? coords.height : coords.width + }, + calcValue (offset) { + const { vertical, min, max } = this + const ratio = Math.abs(Math.max(offset, 0) / this.getSliderLength()) + const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min + return value + }, + calcValueByPos (position) { + const pixelOffset = position - this.getSliderStart() + const nextValue = this.trimAlignValue(this.calcValue(pixelOffset)) + return nextValue + }, + calcOffset (value) { + const { min, max } = this.props + const ratio = (value - min) / (max - min) + return ratio * 100 + }, + saveHandle (index, handle) { + this.handlesRefs[index] = handle + }, + onClickMarkLabel (e, value) { + e.stopPropagation() + this.$emit('change', { value }) + }, + }, render () { const { prefixCls, - className, marks, dots, step, @@ -274,35 +231,46 @@ export default function createSlider (Component) { vertical, min, max, - children, maximumTrackStyle, - style, railStyle, dotStyle, activeDotStyle, - } = this.props + } = this const { tracks, handles } = super.render() const sliderClassName = classNames(prefixCls, { [`${prefixCls}-with-marks`]: Object.keys(marks).length, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-vertical`]: vertical, - [className]: className, }) + const markProps = { + props: { + vertical, + marks, + included, + lowerBound: this.getLowerBound(), + upperBound: this.getUpperBound(), + max, + min, + className: `${prefixCls}-mark`, + }, + on: { + clickLabel: disabled ? noop : this.onClickMarkLabel, + }, + } return (
{handles} - {children} + {this.$slots.default}
) - } + }, } } diff --git a/components/vc-slider/src/createSliderWithTooltip.jsx b/components/vc-slider/src/createSliderWithTooltip.jsx index 002357a48..23441d6be 100644 --- a/components/vc-slider/src/createSliderWithTooltip.jsx +++ b/components/vc-slider/src/createSliderWithTooltip.jsx @@ -1,80 +1,91 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Tooltip from 'rc-tooltip'; -import Handle from './Handle'; +import PropTypes from '../../../_util/vue-types' +import BaseMixin from '../../../_util/BaseMixin' +import Tooltip from '../../vc-tooltip' +import Handle from './Handle' -export default function createSliderWithTooltip(Component) { - return class ComponentWrapper extends React.Component { - static propTypes = { - tipFormatter: PropTypes.func, - handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), - tipProps: PropTypes.object, - }; - static defaultProps = { - tipFormatter(value) { return value; }, - handleStyle: [{}], - tipProps: {}, - }; - constructor(props) { - super(props); - this.state = { visibles: {} }; - } - handleTooltipVisibleChange = (index, visible) => { - this.setState((prevState) => { - return { - visibles: { - ...prevState.visibles, - [index]: visible, - }, - }; - }); - } - handleWithTooltip = ({ value, dragging, index, disabled, ...restProps }) => { - const { - tipFormatter, - tipProps, - handleStyle, - } = this.props; - - const { - prefixCls = 'rc-slider-tooltip', - overlay = tipFormatter(value), - placement = 'top', - visible = visible || false, - ...restTooltipProps, - } = tipProps; - - let handleStyleWithIndex; - if (Array.isArray(handleStyle)) { - handleStyleWithIndex = handleStyle[index] || handleStyle[0]; - } else { - handleStyleWithIndex = handleStyle; +export default function createSliderWithTooltip (Component) { + return { + mixins: [BaseMixin], + props: { + tipFormatter: PropTypes.func.def((value) => { return value }), + handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]).def([{}]), + tipProps: PropTypes.object.def({}), + }, + data () { + return { + visibles: {}, } + }, + methods: { + handleTooltipVisibleChange (index, visible) { + this.setState((prevState) => { + return { + visibles: { + ...prevState.visibles, + [index]: visible, + }, + } + }) + }, + handleWithTooltip ({ value, dragging, index, disabled, ...restProps }) { + const { + tipFormatter, + tipProps, + handleStyle, + } = this.$props - return ( - + const { + prefixCls = 'rc-slider-tooltip', + overlay = tipFormatter(value), + placement = 'top', + visible = visible || false, + ...restTooltipProps } = tipProps - this.handleTooltipVisibleChange(index, true)} - onMouseLeave={() => this.handleTooltipVisibleChange(index, false)} - /> - - ); - } - render() { - return ; - } - }; + let handleStyleWithIndex + if (Array.isArray(handleStyle)) { + handleStyleWithIndex = handleStyle[index] || handleStyle[0] + } else { + handleStyleWithIndex = handleStyle + } + + const tooltipProps = { + props: { + prefixCls, + overlay, + placement, + visible: (!disabled && (this.visibles[index] || dragging)) || visible, + ...restTooltipProps, + }, + key: index, + } + const handleProps = { + props: { + value, + ...restProps, + }, + on: { + mouseenter: () => this.handleTooltipVisibleChange(index, true), + mouseleave: () => this.handleTooltipVisibleChange(index, false), + }, + style: { + ...handleStyleWithIndex, + }, + } + + return ( + + + + + ) + }, + }, + render () { + return + }, + } } diff --git a/components/vc-slider/src/utils.js b/components/vc-slider/src/utils.js index 1b2f4a036..170f2ccf1 100644 --- a/components/vc-slider/src/utils.js +++ b/components/vc-slider/src/utils.js @@ -1,9 +1,8 @@ -import { findDOMNode } from 'react-dom' import keyCode from '../../_util/KeyCode' export function isEventFromHandle (e, handles) { return Object.keys(handles) - .some(key => e.target === findDOMNode(handles[key])) + .some(key => e.target === handles[key]) } export function isValueOutOfRange (value, { min, max }) {