add vc-slider v0.1

pull/9/head
wangxueliang 2018-03-23 18:37:32 +08:00
parent 847bed5bca
commit 263dd3a520
8 changed files with 845 additions and 894 deletions

View File

@ -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 { import classNames from 'classnames'
state = { import PropTypes from '../../../_util/vue-types'
clickFocused: false, import addEventListener from '../../../_util/Dom/addEventListener'
} import BaseMixin from '../../../_util/BaseMixin'
componentDidMount() { export default {
// mouseup won't trigger if mouse moved out of handle, mixins: [BaseMixin],
// so we listen on document here. props: {
this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp); prefixCls: PropTypes.string,
} vertical: PropTypes.bool,
offset: PropTypes.number,
componentWillUnmount() { disabled: PropTypes.bool,
if (this.onMouseUpListener) { min: PropTypes.number,
this.onMouseUpListener.remove(); max: PropTypes.number,
value: PropTypes.number,
tabIndex: PropTypes.number,
},
data () {
return {
clickFocused: false,
} }
} },
mounted () {
setClickFocus(focused) { this.$nextTick(() => {
this.setState({ clickFocused: focused }); this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp)
} })
},
handleMouseUp = () => { beforeDestroy () {
if (document.activeElement === this.handle) { this.$nextTick(() => {
this.setClickFocus(true); if (this.onMouseUpListener) {
} this.onMouseUpListener.remove()
} }
})
handleBlur = () => { },
this.setClickFocus(false); methods: {
} setClickFocus (focused) {
this.setState({ clickFocused: focused })
handleKeyDown = () => { },
this.setClickFocus(false); handleMouseUp () {
} if (document.activeElement === this.$refs.handle) {
this.setClickFocus(true)
clickFocus() { }
this.setClickFocus(true); },
this.focus(); handleBlur () {
} this.setClickFocus(false)
},
focus() { handleKeyDown () {
this.handle.focus(); this.setClickFocus(false)
} },
clickFocus () {
blur() { this.setClickFocus(true)
this.handle.blur(); this.focus()
} },
focus () {
render() { this.$refs.handle.focus()
},
blur () {
this.$refs.handle.blur()
},
},
render () {
const { const {
prefixCls, vertical, offset, style, disabled, min, max, value, tabIndex, ...restProps, prefixCls, vertical, offset, disabled, min, max, value, tabIndex, ...restProps
} = this.props; } = this.$props
const className = classNames( 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 = { const elStyle = {
...style,
...postionStyle, ...postionStyle,
}; }
let ariaProps = {}; let ariaProps = {}
if (value !== undefined) { if (value !== undefined) {
ariaProps = { ariaProps = {
...ariaProps, ...ariaProps,
@ -76,34 +82,21 @@ export default class Handle extends React.Component {
'aria-valuemax': max, 'aria-valuemax': max,
'aria-valuenow': value, 'aria-valuenow': value,
'aria-disabled': !!disabled, 'aria-disabled': !!disabled,
}; }
} }
return ( return (
<div <div
ref={node => (this.handle = node)} ref='handle'
role="slider" role='slider'
tabIndex= {disabled ? null : (tabIndex || 0)} tabIndex= {disabled ? null : (tabIndex || 0)}
{...ariaProps} {...ariaProps}
{...restProps} {...restProps}
className={className} class={className}
style={elStyle} style={elStyle}
onBlur={this.handleBlur} 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,
};

View File

@ -1,327 +1,308 @@
/* eslint-disable react/prop-types */ import classNames from 'classnames'
import React from 'react'; import PropTypes from '../../../_util/vue-types'
import PropTypes from 'prop-types'; import BaseMixin from '../../../_util/BaseMixin'
import classNames from 'classnames'; import { initDefaultProps, hasProp } from '../../../_util/props-util'
import shallowEqual from 'shallowequal'; import Track from './common/Track'
import Track from './common/Track'; import createSlider from './common/createSlider'
import createSlider from './common/createSlider'; import * as utils from './utils'
import * as utils from './utils';
class Range extends React.Component { const rangeProps = {
static displayName = 'Range'; defaultValue: PropTypes.arrayOf(PropTypes.number),
static propTypes = { value: PropTypes.arrayOf(PropTypes.number),
defaultValue: PropTypes.arrayOf(PropTypes.number), count: PropTypes.number,
value: PropTypes.arrayOf(PropTypes.number), pushable: PropTypes.oneOfType([
count: PropTypes.number, PropTypes.bool,
pushable: PropTypes.oneOfType([ PropTypes.number,
PropTypes.bool, ]),
PropTypes.number, allowCross: PropTypes.bool,
]), disabled: PropTypes.bool,
allowCross: PropTypes.bool, tabIndex: PropTypes.arrayOf(PropTypes.number),
disabled: PropTypes.bool, }
tabIndex: PropTypes.arrayOf(PropTypes.number), const Range = {
}; displayName: 'Range',
mixins: [BaseMixin],
static defaultProps = { props: initDefaultProps(rangeProps, {
count: 1, count: 1,
allowCross: true, allowCross: true,
pushable: false, pushable: false,
tabIndex: [], tabIndex: [],
}; }),
data () {
constructor(props) { const { count, min, max } = this
super(props);
const { count, min, max } = props;
const initialValue = Array.apply(null, Array(count + 1)) const initialValue = Array.apply(null, Array(count + 1))
.map(() => min); .map(() => min)
const defaultValue = 'defaultValue' in props ? const defaultValue = hasProp(this, 'defaultValue') ? this.defaultValue : initialValue
props.defaultValue : initialValue; let { value } = this
const value = props.value !== undefined ? if (value === undefined) {
props.value : defaultValue; value = defaultValue
const bounds = value.map((v, i) => this.trimAlignValue(v, i)); }
const recent = bounds[0] === max ? 0 : bounds.length - 1; const bounds = value.map((v, i) => this.trimAlignValue(v, i))
const recent = bounds[0] === max ? 0 : bounds.length - 1
this.state = { return {
handle: null, handle: null,
recent, recent,
bounds, 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; this.setState({ bounds: nextBounds })
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 }); 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 data = { ...this.$data, ...state }
const newValues = value.map((v) => { const changedValue = data.bounds
return utils.ensureValueInRange(v, nextProps); this.$emit('change', changedValue)
}); },
this.props.onChange(newValues); onStart (position) {
} const { bounds } = this
} this.$emit('beforeChange', bounds)
onChange(state) { const value = this.calcValueByPos(position)
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 data = { ...this.state, ...state }; const closestBound = this.getClosestBound(value)
const changedValue = data.bounds; this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound)
props.onChange(changedValue);
}
onStart(position) { this.setState({
const props = this.props; handle: this.prevMovedHandleIndex,
const state = this.state; recent: this.prevMovedHandleIndex,
const bounds = this.getValue(); })
props.onBeforeChange(bounds);
const value = this.calcValueByPos(position); const prevValue = bounds[this.prevMovedHandleIndex]
this.startValue = value; if (value === prevValue) return
this.startPosition = position;
const closestBound = this.getClosestBound(value); const nextBounds = [...bounds]
this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound); 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({ this.moveTo(value)
handle: this.prevMovedHandleIndex, },
recent: this.prevMovedHandleIndex, onKeyboard (e) {
}); const valueMutator = utils.getKeyboardValueMutator(e)
const prevValue = bounds[this.prevMovedHandleIndex]; if (valueMutator) {
if (value === prevValue) return; 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]; if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) {
nextBounds[this.prevMovedHandleIndex] = value; boundNeedMoving = recent
this.onChange({ bounds: nextBounds }); }
}
onEnd = () => { if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) {
this.removeDocumentEvents(); boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1
this.props.onAfterChange(this.getValue()); }
} 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 hackbut 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) { let direction = 0
utils.pauseEvent(e); if (bounds[handle + 1] - value < threshold) {
const state = this.state; direction = +1 // push to right
}
if (value - bounds[handle - 1] < threshold) {
direction = -1 // push to left
}
const value = this.calcValueByPos(position); if (direction === 0) { return }
const oldValue = state.bounds[state.handle];
if (value === oldValue) return;
this.moveTo(value); const nextHandle = handle + direction
} const diffToNext = direction * (bounds[nextHandle] - value)
if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
onKeyboard(e) { // revert to original value if pushing is impossible
const valueMutator = utils.getKeyboardValueMutator(e); bounds[handle] = bounds[nextHandle] - (direction * threshold)
}
if (valueMutator) { },
utils.pauseEvent(e); pushHandle (bounds, handle, direction, amount) {
const { state, props } = this; const originalValue = bounds[handle]
const { bounds, handle } = state; let currentValue = bounds[handle]
const oldValue = bounds[handle]; while (direction * (currentValue - originalValue) < amount) {
const mutatedValue = valueMutator(oldValue, props); if (!this.pushHandleOnePoint(bounds, handle, direction)) {
const value = this.trimAlignValue(mutatedValue); // can't push handle enough to create the needed `amount` gap, so we
if (value === oldValue) return; // revert its position to the original value
const isFromKeyboardEvent = true; bounds[handle] = originalValue
this.moveTo(value, isFromKeyboardEvent); return false
} }
} currentValue = bounds[handle]
}
getValue() { // the handle was pushed enough to create the needed `amount` gap
return this.state.bounds; return true
} },
pushHandleOnePoint (bounds, handle, direction) {
getClosestBound(value) { const points = this.getPoints()
const { bounds } = this.state; const pointIndex = points.indexOf(bounds[handle])
let closestBound = 0; const nextPointIndex = pointIndex + direction
for (let i = 1; i < bounds.length - 1; ++i) { if (nextPointIndex >= points.length || nextPointIndex < 0) {
if (value > bounds[i]) { closestBound = i; } // reached the minimum or maximum available point, can't push anymore
} return false
if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) { }
closestBound = closestBound + 1; const nextHandle = handle + direction
} const nextValue = points[nextPointIndex]
return closestBound; const { pushable: threshold } = this
} const diffToNext = direction * (bounds[nextHandle] - nextValue)
if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
getBoundNeedMoving(value, closestBound) { // couldn't push next handle, so we won't push this one either
const { bounds, recent } = this.state; return false
let boundNeedMoving = closestBound; }
const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]); // push the handle
bounds[handle] = nextValue
if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) { return true
boundNeedMoving = recent; },
} trimAlignValue (v, handle, nextProps = {}) {
const mergedProps = { ...this, ...nextProps }
if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) { const valInRange = utils.ensureValueInRange(v, mergedProps)
boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1; const valNotConflict = this.ensureValueNotConflict(handle, valInRange, mergedProps)
} return utils.ensureValuePrecision(valNotConflict, mergedProps)
return boundNeedMoving; },
} ensureValueNotConflict (handle, val, { allowCross, pushable: thershold }) {
const state = this.$data || {}
getLowerBound() { const { bounds } = state
return this.state.bounds[0]; handle = handle === undefined ? state.handle : handle
} thershold = Number(thershold)
/* eslint-disable eqeqeq */
getUpperBound() { if (!allowCross && handle != null && bounds !== undefined) {
const { bounds } = this.state; if (handle > 0 && val <= (bounds[handle - 1] + thershold)) {
return bounds[bounds.length - 1]; return bounds[handle - 1] + thershold
} }
if (handle < bounds.length - 1 && val >= (bounds[handle + 1] - thershold)) {
/** return bounds[handle + 1] - thershold
* 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 points = Object.keys(pointsObject).map(parseFloat); /* eslint-enable eqeqeq */
points.sort((a, b) => a - b); return val
this._getPointsCache = { marks, step, points }; },
} },
return this._getPointsCache.points; render () {
}
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 hackbut 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() {
const { const {
handle, handle,
bounds, bounds,
} = this.state;
const {
prefixCls, prefixCls,
vertical, vertical,
included, included,
@ -332,11 +313,11 @@ class Range extends React.Component {
trackStyle, trackStyle,
handleStyle, handleStyle,
tabIndex, 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({ const handles = bounds.map((v, i) => handleGenerator({
className: classNames({ className: classNames({
[handleClassName]: true, [handleClassName]: true,
@ -354,14 +335,14 @@ class Range extends React.Component {
disabled, disabled,
style: handleStyle[i], style: handleStyle[i],
ref: h => this.saveHandle(i, h), ref: h => this.saveHandle(i, h),
})); }))
const tracks = bounds.slice(0, -1).map((_, index) => { const tracks = bounds.slice(0, -1).map((_, index) => {
const i = index + 1; const i = index + 1
const trackClassName = classNames({ const trackClassName = classNames({
[`${prefixCls}-track`]: true, [`${prefixCls}-track`]: true,
[`${prefixCls}-track-${i}`]: true, [`${prefixCls}-track-${i}`]: true,
}); })
return ( return (
<Track <Track
className={trackClassName} className={trackClassName}
@ -372,11 +353,11 @@ class Range extends React.Component {
style={trackStyle[index]} style={trackStyle[index]}
key={i} key={i}
/> />
); )
}); })
return { tracks, handles }; return { tracks, handles }
} },
} }
export default createSlider(Range); export default createSlider(Range)

View File

@ -1,142 +1,142 @@
/* eslint-disable react/prop-types */ import PropTypes from '../../../_util/vue-types'
import React from 'react' import warning from '../../../_util/warning'
import PropTypes from 'prop-types' import BaseMixin from '../../../_util/BaseMixin'
import warning from 'warning' import { hasProp } from '../../../_util/props-util'
import Track from './common/Track' import Track from './common/Track'
import createSlider from './common/createSlider' import createSlider from './common/createSlider'
import * as utils from './utils' import * as utils from './utils'
class Slider extends React.Component { const Slider = {
static propTypes = { mixins: [BaseMixin],
props: {
defaultValue: PropTypes.number, defaultValue: PropTypes.number,
value: PropTypes.number, value: PropTypes.number,
disabled: PropTypes.bool, disabled: PropTypes.bool,
autoFocus: PropTypes.bool, autoFocus: PropTypes.bool,
tabIndex: PropTypes.number, 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') { if (process.env.NODE_ENV !== 'production') {
warning( warning(
!('minimumTrackStyle' in props), !hasProp(this, 'minimumTrackStyle'),
'minimumTrackStyle will be deprecate, please use trackStyle instead.' 'minimumTrackStyle will be deprecate, please use trackStyle instead.'
) )
warning( warning(
!('maximumTrackStyle' in props), !hasProp(this, 'maximumTrackStyle'),
'maximumTrackStyle will be deprecate, please use railStyle instead.' 'maximumTrackStyle will be deprecate, please use railStyle instead.'
) )
} }
} return {
sValue: this.trimAlignValue(value),
componentDidMount () { dragging: false,
const { autoFocus, disabled } = this.props
if (autoFocus && !disabled) {
this.focus()
} }
} },
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) { this.setState({ sValue: nextValue })
if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return 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 changedValue = state.sValue
const value = nextProps.value !== undefined this.$emit('change', changedValue)
? nextProps.value : prevValue },
const nextValue = this.trimAlignValue(value, nextProps) onStart (position) {
if (nextValue === prevValue) return this.setState({ dragging: true })
const { sValue } = this
this.$emit('beforeChange', sValue)
this.setState({ value: nextValue }) const value = this.calcValueByPos(position)
if (utils.isValueOutOfRange(value, nextProps)) {
this.props.onChange(nextValue)
}
}
onChange (state) { if (value === sValue) return
const props = this.props
const isNotControlled = !('value' in props)
if (isNotControlled) {
this.setState(state)
}
const changedValue = state.value this.prevMovedHandleIndex = 0
props.onChange(changedValue)
}
onStart (position) { this.onChange({ sValue: value })
this.setState({ dragging: true }) },
const props = this.props onEnd () {
const prevValue = this.getValue() this.setState({ dragging: false })
props.onBeforeChange(prevValue) this.removeDocumentEvents()
this.$emit('afterChange', this.sValue)
const value = this.calcValueByPos(position) },
this.startValue = value onMove (e, position) {
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) {
utils.pauseEvent(e) utils.pauseEvent(e)
const state = this.state const { sValue } = this
const oldValue = state.value const value = this.calcValueByPos(position)
const mutatedValue = valueMutator(oldValue, this.props) if (value === sValue) return
const value = this.trimAlignValue(mutatedValue)
if (value === oldValue) return
this.onChange({ value }) this.onChange({ sValue: value })
} },
} onKeyboard (e) {
const valueMutator = utils.getKeyboardValueMutator(e)
getValue () { if (valueMutator) {
return this.state.value utils.pauseEvent(e)
} const { sValue } = this
const mutatedValue = valueMutator(sValue, this.$props)
getLowerBound () { const value = this.trimAlignValue(mutatedValue)
return this.props.min if (value === sValue) return
}
getUpperBound () {
return this.state.value
}
trimAlignValue (v, nextProps = {}) {
const mergedProps = { ...this.props, ...nextProps }
const val = utils.ensureValueInRange(v, mergedProps)
return utils.ensureValuePrecision(val, mergedProps)
}
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 () { render () {
const { const {
prefixCls, prefixCls,
@ -150,7 +150,7 @@ class Slider extends React.Component {
min, min,
max, max,
handle: handleGenerator, handle: handleGenerator,
} = this.props } = this
const { value, dragging } = this.state const { value, dragging } = this.state
const offset = this.calcOffset(value) const offset = this.calcOffset(value)
const handle = handleGenerator({ const handle = handleGenerator({
@ -183,9 +183,8 @@ class Slider extends React.Component {
}} }}
/> />
) )
return { tracks: track, handles: handle } return { tracks: track, handles: handle }
} },
} }
export default createSlider(Slider) export default createSlider(Slider)

View File

@ -1,66 +1,71 @@
import React from 'react'
import classNames from 'classnames' import classNames from 'classnames'
const Marks = ({ const Marks = {
className, functional: true,
vertical, render (createElement, context) {
marks, const {
included, className,
upperBound, vertical,
lowerBound, marks,
max, min, included,
onClickLabel, upperBound,
}) => { lowerBound,
const marksKeys = Object.keys(marks) max, min,
const marksCount = marksKeys.length } = context.props
const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100 const { clickLabel } = context.listeners
const markWidth = unit * 0.9 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 range = max - min
const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => { const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => {
const markPoint = marks[point] const markPoint = marks[point]
const markPointIsObject = typeof markPoint === 'object' && // todo
!React.isValidElement(markPoint) // const markPointIsObject = typeof markPoint === 'object' &&
const markLabel = markPointIsObject ? markPoint.label : markPoint // !React.isValidElement(markPoint)
if (!markLabel && markLabel !== 0) { const markPointIsObject = typeof markPoint === 'object'
return null const markLabel = markPointIsObject ? markPoint.label : markPoint
} if (!markLabel && markLabel !== 0) {
return null
}
const isActive = (!included && point === upperBound) || const isActive = (!included && point === upperBound) ||
(included && point <= upperBound && point >= lowerBound) (included && point <= upperBound && point >= lowerBound)
const markClassName = classNames({ const markClassName = classNames({
[`${className}-text`]: true, [`${className}-text`]: true,
[`${className}-text-active`]: isActive, [`${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 (
<span
class={markClassName}
style={markStyle}
key={point}
onMouseDown={(e) => clickLabel(e, point)}
onTouchStart={(e) => clickLabel(e, point)}
>
{markLabel}
</span>
)
}) })
const bottomStyle = { return <div class={className}>{elements}</div>
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 (
<span
className={markClassName}
style={markStyle}
key={point}
onMouseDown={(e) => onClickLabel(e, point)}
onTouchStart={(e) => onClickLabel(e, point)}
>
{markLabel}
</span>
)
})
return <div className={className}>{elements}</div>
};
export default Marks export default Marks

View File

@ -1,6 +1,5 @@
import React from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import warning from 'warning' import warning from '../../../_util/warning'
const calcPoints = (vertical, marks, dots, step, min, max) => { const calcPoints = (vertical, marks, dots, step, min, max) => {
warning( warning(
@ -17,28 +16,32 @@ const calcPoints = (vertical, marks, dots, step, min, max) => {
return points return points
} }
const Steps = ({ prefixCls, vertical, marks, dots, step, included, const Steps = {
lowerBound, upperBound, max, min, dotStyle, activeDotStyle }) => { functional: true,
const range = max - min render (createElement, context) {
const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => { const { prefixCls, vertical, marks, dots, step, included,
const offset = `${Math.abs(point - min) / range * 100}%` 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) || const isActived = (!included && point === upperBound) ||
(included && point <= upperBound && point >= lowerBound) (included && point <= upperBound && point >= lowerBound)
let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle } let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle }
if (isActived) { if (isActived) {
style = { ...style, ...activeDotStyle } style = { ...style, ...activeDotStyle }
} }
const pointClassName = classNames({ const pointClassName = classNames({
[`${prefixCls}-dot`]: true, [`${prefixCls}-dot`]: true,
[`${prefixCls}-dot-active`]: isActived, [`${prefixCls}-dot-active`]: isActived,
})
return <span className={pointClassName} style={style} key={point} />
}) })
return <span className={pointClassName} style={style} key={point} /> return <div className={`${prefixCls}-step`}>{elements}</div>
}) },
return <div className={`${prefixCls}-step`}>{elements}</div>
} }
export default Steps export default Steps

View File

@ -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 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 Steps from './Steps'
import Marks from './Marks' import Marks from './Marks'
import Handle from '../Handle' import Handle from '../Handle'
@ -11,42 +11,32 @@ import * as utils from '../utils'
function noop () {} function noop () {}
export default function createSlider (Component) { export default function createSlider (Component) {
return class ComponentEnhancer extends Component { // const displayName = `ComponentEnhancer(${Component.displayName})`
static displayName = `ComponentEnhancer(${Component.displayName})`; const propTypes = {
static propTypes = { ...Component.propTypes,
...Component.propTypes, min: PropTypes.number,
min: PropTypes.number, max: PropTypes.number,
max: PropTypes.number, step: PropTypes.number,
step: PropTypes.number, marks: PropTypes.object,
marks: PropTypes.object, included: PropTypes.bool,
included: PropTypes.bool, prefixCls: PropTypes.string,
className: PropTypes.string, disabled: PropTypes.bool,
prefixCls: PropTypes.string, handle: PropTypes.func,
disabled: PropTypes.bool, dots: PropTypes.bool,
children: PropTypes.any, vertical: PropTypes.bool,
onBeforeChange: PropTypes.func, minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
onChange: PropTypes.func, maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
onAfterChange: PropTypes.func, handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
handle: PropTypes.func, trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
dots: PropTypes.bool, railStyle: PropTypes.object,
vertical: PropTypes.bool, dotStyle: PropTypes.object,
style: PropTypes.object, activeDotStyle: PropTypes.object,
minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate autoFocus: PropTypes.bool,
maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate }
handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), return {
trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), props: initDefaultProps(propTypes, {
railStyle: PropTypes.object,
dotStyle: PropTypes.object,
activeDotStyle: PropTypes.object,
autoFocus: PropTypes.bool,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
};
static defaultProps = {
...Component.defaultProps, ...Component.defaultProps,
prefixCls: 'rc-slider', prefixCls: 'rc-slider',
className: '',
min: 0, min: 0,
max: 100, max: 100,
step: 1, step: 1,
@ -55,9 +45,6 @@ export default function createSlider (Component) {
delete restProps.dragging delete restProps.dragging
return <Handle {...restProps} key={index} /> return <Handle {...restProps} key={index} />
}, },
onBeforeChange: noop,
onChange: noop,
onAfterChange: noop,
included: true, included: true,
disabled: false, disabled: false,
dots: false, dots: false,
@ -67,13 +54,10 @@ export default function createSlider (Component) {
railStyle: {}, railStyle: {},
dotStyle: {}, dotStyle: {},
activeDotStyle: {}, activeDotStyle: {},
}; }),
data () {
constructor (props) {
super(props)
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const { step, max, min } = props const { step, max, min } = this
warning( warning(
step && Math.floor(step) === step ? (max - min) % step === 0 : true, step && Math.floor(step) === step ? (max - min) % step === 0 : true,
'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)', 'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)',
@ -82,190 +66,163 @@ export default function createSlider (Component) {
) )
} }
this.handlesRefs = {} this.handlesRefs = {}
} return {}
},
componentWillUnmount () { beforeDestroy () {
if (super.componentWillUnmount) super.componentWillUnmount() this.$nextTick(() => {
this.removeDocumentEvents() // if (super.componentWillUnmount) super.componentWillUnmount()
} this.removeDocumentEvents()
})
componentDidMount () { },
// Snapshot testing cannot handle refs, so be sure to null-check this. mounted () {
this.document = this.sliderRef && this.sliderRef.ownerDocument this.$nextTick(() => {
} // Snapshot testing cannot handle refs, so be sure to null-check this.
this.document = this.$refs.sliderRef && this.$refs.sliderRef.ownerDocument
onMouseDown = (e) => { })
if (e.button !== 0) { return } },
methods: {
const isVertical = this.props.vertical onMouseDown (e) {
let position = utils.getMousePosition(isVertical, e) if (e.button !== 0) { return }
if (!utils.isEventFromHandle(e, this.handlesRefs)) { const isVertical = this.vertical
this.dragOffset = 0 let position = utils.getMousePosition(isVertical, e)
} else { if (!utils.isEventFromHandle(e, this.handlesRefs)) {
const handlePosition = utils.getHandleCenterPosition(isVertical, e.target) this.dragOffset = 0
this.dragOffset = position - handlePosition } else {
position = handlePosition const handlePosition = utils.getHandleCenterPosition(isVertical, e.target)
} this.dragOffset = position - handlePosition
this.removeDocumentEvents() position = handlePosition
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)
} }
} this.removeDocumentEvents()
} this.onStart(position)
this.addDocumentMouseEvents()
},
onTouchStart (e) {
if (utils.isNotTouchEvent(e)) return
onBlur = (e) => { const isVertical = this.vertical
const { onBlur } = this.props let position = utils.getTouchPosition(isVertical, e)
this.onEnd(e) if (!utils.isEventFromHandle(e, this.handlesRefs)) {
if (onBlur) { this.dragOffset = 0
onBlur(e) } 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 () { this.onMouseMoveListener && this.onMouseMoveListener.remove()
// just work for Chrome iOS Safari and Android Browser this.onMouseUpListener && this.onMouseUpListener.remove()
this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove) /* eslint-enable no-unused-expressions */
this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd) },
} 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 () { const position = utils.getTouchPosition(this.vertical, e)
this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove) this.onMove(e, position - this.dragOffset)
this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd) },
} 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 () { return this.vertical ? rect.top : rect.left
/* eslint-disable no-unused-expressions */ },
this.onTouchMoveListener && this.onTouchMoveListener.remove() getSliderLength () {
this.onTouchUpListener && this.onTouchUpListener.remove() const slider = this.$refs.sliderRef
if (!slider) {
this.onMouseMoveListener && this.onMouseMoveListener.remove() return 0
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 })
}
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 () { render () {
const { const {
prefixCls, prefixCls,
className,
marks, marks,
dots, dots,
step, step,
@ -274,35 +231,46 @@ export default function createSlider (Component) {
vertical, vertical,
min, min,
max, max,
children,
maximumTrackStyle, maximumTrackStyle,
style,
railStyle, railStyle,
dotStyle, dotStyle,
activeDotStyle, activeDotStyle,
} = this.props } = this
const { tracks, handles } = super.render() const { tracks, handles } = super.render()
const sliderClassName = classNames(prefixCls, { const sliderClassName = classNames(prefixCls, {
[`${prefixCls}-with-marks`]: Object.keys(marks).length, [`${prefixCls}-with-marks`]: Object.keys(marks).length,
[`${prefixCls}-disabled`]: disabled, [`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-vertical`]: vertical, [`${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 ( return (
<div <div
ref={this.saveSlider} ref='sliderRef'
className={sliderClassName} class={sliderClassName}
onTouchStart={disabled ? noop : this.onTouchStart} onTouchStart={disabled ? noop : this.onTouchStart}
onMouseDown={disabled ? noop : this.onMouseDown} onMouseDown={disabled ? noop : this.onMouseDown}
onMouseUp={disabled ? noop : this.onMouseUp} onMouseUp={disabled ? noop : this.onMouseUp}
onKeyDown={disabled ? noop : this.onKeyDown} onKeyDown={disabled ? noop : this.onKeyDown}
onFocus={disabled ? noop : this.onFocus} onFocus={disabled ? noop : this.onFocus}
onBlur={disabled ? noop : this.onBlur} onBlur={disabled ? noop : this.onBlur}
style={style}
> >
<div <div
className={`${prefixCls}-rail`} class={`${prefixCls}-rail`}
style={{ style={{
...maximumTrackStyle, ...maximumTrackStyle,
...railStyle, ...railStyle,
@ -325,19 +293,11 @@ export default function createSlider (Component) {
/> />
{handles} {handles}
<Marks <Marks
className={`${prefixCls}-mark`} {...markProps}
onClickLabel={disabled ? noop : this.onClickMarkLabel}
vertical={vertical}
marks={marks}
included={included}
lowerBound={this.getLowerBound()}
upperBound={this.getUpperBound()}
max={max}
min={min}
/> />
{children} {this.$slots.default}
</div> </div>
) )
} },
} }
} }

View File

@ -1,80 +1,91 @@
import React from 'react'; import PropTypes from '../../../_util/vue-types'
import PropTypes from 'prop-types'; import BaseMixin from '../../../_util/BaseMixin'
import Tooltip from 'rc-tooltip'; import Tooltip from '../../vc-tooltip'
import Handle from './Handle'; import Handle from './Handle'
export default function createSliderWithTooltip(Component) { export default function createSliderWithTooltip (Component) {
return class ComponentWrapper extends React.Component { return {
static propTypes = { mixins: [BaseMixin],
tipFormatter: PropTypes.func, props: {
handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]), tipFormatter: PropTypes.func.def((value) => { return value }),
tipProps: PropTypes.object, handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]).def([{}]),
}; tipProps: PropTypes.object.def({}),
static defaultProps = { },
tipFormatter(value) { return value; }, data () {
handleStyle: [{}], return {
tipProps: {}, visibles: {},
};
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;
} }
},
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 {
<Tooltip prefixCls = 'rc-slider-tooltip',
{...restTooltipProps} overlay = tipFormatter(value),
prefixCls={prefixCls} placement = 'top',
overlay={overlay} visible = visible || false,
placement={placement} ...restTooltipProps } = tipProps
visible={(!disabled && (this.state.visibles[index] || dragging)) || visible}
key={index}
>
<Handle let handleStyleWithIndex
{...restProps} if (Array.isArray(handleStyle)) {
style={{ handleStyleWithIndex = handleStyle[index] || handleStyle[0]
...handleStyleWithIndex, } else {
}} handleStyleWithIndex = handleStyle
value={value} }
onMouseEnter={() => this.handleTooltipVisibleChange(index, true)}
onMouseLeave={() => this.handleTooltipVisibleChange(index, false)} const tooltipProps = {
/> props: {
</Tooltip> prefixCls,
); overlay,
} placement,
render() { visible: (!disabled && (this.visibles[index] || dragging)) || visible,
return <Component {...this.props} handle={this.handleWithTooltip} />; ...restTooltipProps,
} },
}; key: index,
}
const handleProps = {
props: {
value,
...restProps,
},
on: {
mouseenter: () => this.handleTooltipVisibleChange(index, true),
mouseleave: () => this.handleTooltipVisibleChange(index, false),
},
style: {
...handleStyleWithIndex,
},
}
return (
<Tooltip
{...tooltipProps}
>
<Handle
{...handleProps}
/>
</Tooltip>
)
},
},
render () {
return <Component {...this.$props} handle={this.handleWithTooltip} />
},
}
} }

View File

@ -1,9 +1,8 @@
import { findDOMNode } from 'react-dom'
import keyCode from '../../_util/KeyCode' import keyCode from '../../_util/KeyCode'
export function isEventFromHandle (e, handles) { export function isEventFromHandle (e, handles) {
return Object.keys(handles) return Object.keys(handles)
.some(key => e.target === findDOMNode(handles[key])) .some(key => e.target === handles[key])
} }
export function isValueOutOfRange (value, { min, max }) { export function isValueOutOfRange (value, { min, max }) {