pull/165/head
wangxueliang 7 years ago
parent 2824082c43
commit e7e64675bb

@ -0,0 +1,302 @@
@prefixClass: rc-slider;
@disabledColor: #ccc;
@border-radius-base: 6px;
@primary-color: #2db7f5;
@tooltip-color: #fff;
@tooltip-bg: tint(#666, 4%);
@tooltip-arrow-width: 4px;
@tooltip-distance: @tooltip-arrow-width+4;
@tooltip-arrow-color: @tooltip-bg;
@ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1);
@ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06);
.borderBox() {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); // remove tap highlight color for mobile safari
* {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); // remove tap highlight color for mobile safari
}
}
.@{prefixClass} {
position: relative;
height: 14px;
padding: 5px 0;
width: 100%;
border-radius: @border-radius-base;
touch-action: none;
.borderBox();
&-rail {
position: absolute;
width: 100%;
background-color: #e9e9e9;
height: 4px;
border-radius: @border-radius-base;
}
&-track {
position: absolute;
left: 0;
height: 4px;
border-radius: @border-radius-base;
background-color: tint(@primary-color, 60%);
}
&-handle {
position: absolute;
margin-left: -7px;
margin-top: -5px;
width: 14px;
height: 14px;
cursor: pointer;
cursor: -webkit-grab;
cursor: grab;
border-radius: 50%;
border: solid 2px tint(@primary-color, 50%);
background-color: #fff;
touch-action: pan-x;
&:focus {
border-color: tint(@primary-color, 20%);
box-shadow: 0 0 0 5px tint(@primary-color, 50%);
outline: none;
}
&-click-focused:focus {
border-color: tint(@primary-color, 50%);
box-shadow: unset;
}
&:hover {
border-color: tint(@primary-color, 20%);
}
&:active {
border-color: tint(@primary-color, 20%);
box-shadow: 0 0 5px tint(@primary-color, 20%);
cursor: -webkit-grabbing;
cursor: grabbing;
}
}
&-mark {
position: absolute;
top: 18px;
left: 0;
width: 100%;
font-size: 12px;
}
&-mark-text {
position: absolute;
display: inline-block;
vertical-align: middle;
text-align: center;
cursor: pointer;
color: #999;
&-active {
color: #666;
}
}
&-step {
position: absolute;
width: 100%;
height: 4px;
background: transparent;
}
&-dot {
position: absolute;
bottom: -2px;
margin-left: -4px;
width: 8px;
height: 8px;
border: 2px solid #e9e9e9;
background-color: #fff;
cursor: pointer;
border-radius: 50%;
vertical-align: middle;
&-active {
border-color: tint(@primary-color, 50%);
}
}
&-disabled {
background-color: #e9e9e9;
.@{prefixClass}-track {
background-color: @disabledColor;
}
.@{prefixClass}-handle, .@{prefixClass}-dot {
border-color: @disabledColor;
box-shadow: none;
background-color: #fff;
cursor: not-allowed;
}
.@{prefixClass}-mark-text, .@{prefixClass}-dot {
cursor: not-allowed!important;
}
}
}
.@{prefixClass}-vertical {
width: 14px;
height: 100%;
padding: 0 5px;
.@{prefixClass} {
&-rail {
height: 100%;
width: 4px;
}
&-track {
left: 5px;
bottom: 0;
width: 4px;
}
&-handle {
margin-left: -5px;
margin-bottom: -7px;
touch-action: pan-y;
}
&-mark {
top: 0;
left: 18px;
height: 100%;
}
&-step {
height: 100%;
width: 4px;
}
&-dot {
left: 2px;
margin-bottom: -4px;
&:first-child {
margin-bottom: -4px;
}
&:last-child {
margin-bottom: -4px;
}
}
}
}
.motion-common() {
animation-duration: .3s;
animation-fill-mode: both;
display: block !important;
}
.make-motion(@className, @keyframeName) {
.@{className}-enter, .@{className}-appear {
.motion-common();
animation-play-state: paused;
}
.@{className}-leave {
.motion-common();
animation-play-state: paused;
}
.@{className}-enter.@{className}-enter-active, .@{className}-appear.@{className}-appear-active {
animation-name: ~"@{keyframeName}In";
animation-play-state: running;
}
.@{className}-leave.@{className}-leave-active {
animation-name: ~"@{keyframeName}Out";
animation-play-state: running;
}
}
.zoom-motion(@className, @keyframeName) {
.make-motion(@className, @keyframeName);
.@{className}-enter, .@{className}-appear {
transform: scale(0, 0); // need this by yiminghe
animation-timing-function: @ease-out-quint;
}
.@{className}-leave {
animation-timing-function: @ease-in-quint;
}
}
.zoom-motion(rc-slider-tooltip-zoom-down, rcSliderTooltipZoomDown);
@keyframes rcSliderTooltipZoomDownIn {
0% {
opacity: 0;
transform-origin: 50% 100%;
transform: scale(0, 0);
}
100% {
transform-origin: 50% 100%;
transform: scale(1, 1);
}
}
@keyframes rcSliderTooltipZoomDownOut {
0% {
transform-origin: 50% 100%;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform-origin: 50% 100%;
transform: scale(0, 0);
}
}
.@{prefixClass}-tooltip {
position: absolute;
left: -9999px;
top: -9999px;
visibility: visible;
.borderBox();
&-hidden {
display: none;
}
&-placement-top {
padding: @tooltip-arrow-width 0 @tooltip-distance 0;
}
&-inner {
padding: 6px 2px;
min-width: 24px;
height: 24px;
font-size: 12px;
line-height: 1;
color: @tooltip-color;
text-align: center;
text-decoration: none;
background-color: @tooltip-bg;
border-radius: @border-radius-base;
box-shadow: 0 0 4px #d9d9d9;
}
&-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
&-placement-top &-arrow {
bottom: @tooltip-distance - @tooltip-arrow-width;
left: 50%;
margin-left: -@tooltip-arrow-width;
border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
border-top-color: @tooltip-arrow-color;
}
}

@ -0,0 +1,109 @@
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,
}
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();
}
}
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() {
const {
prefixCls, vertical, offset, style, disabled, min, max, value, tabIndex, ...restProps,
} = this.props;
const className = classNames(
this.props.className,
{
[`${prefixCls}-handle-click-focused`]: this.state.clickFocused,
}
);
const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` };
const elStyle = {
...style,
...postionStyle,
};
let ariaProps = {};
if (value !== undefined) {
ariaProps = {
...ariaProps,
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
'aria-disabled': !!disabled,
};
}
return (
<div
ref={node => (this.handle = node)}
role="slider"
tabIndex= {disabled ? null : (tabIndex || 0)}
{...ariaProps}
{...restProps}
className={className}
style={elStyle}
onBlur={this.handleBlur}
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,
};

@ -0,0 +1,382 @@
/* 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';
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 = {
count: 1,
allowCross: true,
pushable: false,
tabIndex: [],
};
constructor(props) {
super(props);
const { count, min, max } = props;
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 = {
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;
}
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 });
if (bounds.some(v => utils.isValueOutOfRange(v, nextProps))) {
const newValues = value.map((v) => {
return utils.ensureValueInRange(v, nextProps);
});
this.props.onChange(newValues);
}
}
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 data = { ...this.state, ...state };
const changedValue = data.bounds;
props.onChange(changedValue);
}
onStart(position) {
const props = this.props;
const state = this.state;
const bounds = this.getValue();
props.onBeforeChange(bounds);
const value = this.calcValueByPos(position);
this.startValue = value;
this.startPosition = position;
const closestBound = this.getClosestBound(value);
this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound);
this.setState({
handle: this.prevMovedHandleIndex,
recent: this.prevMovedHandleIndex,
});
const prevValue = bounds[this.prevMovedHandleIndex];
if (value === prevValue) return;
const nextBounds = [...state.bounds];
nextBounds[this.prevMovedHandleIndex] = value;
this.onChange({ bounds: nextBounds });
}
onEnd = () => {
this.removeDocumentEvents();
this.props.onAfterChange(this.getValue());
}
onMove(e, position) {
utils.pauseEvent(e);
const state = this.state;
const value = this.calcValueByPos(position);
const oldValue = state.bounds[state.handle];
if (value === oldValue) 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 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 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 {
handle,
bounds,
} = this.state;
const {
prefixCls,
vertical,
included,
disabled,
min,
max,
handle: handleGenerator,
trackStyle,
handleStyle,
tabIndex,
} = this.props;
const offsets = bounds.map(v => this.calcOffset(v));
const handleClassName = `${prefixCls}-handle`;
const handles = bounds.map((v, i) => handleGenerator({
className: classNames({
[handleClassName]: true,
[`${handleClassName}-${i + 1}`]: true,
}),
prefixCls,
vertical,
offset: offsets[i],
value: v,
dragging: handle === i,
index: i,
tabIndex: tabIndex[i] || 0,
min,
max,
disabled,
style: handleStyle[i],
ref: h => this.saveHandle(i, h),
}));
const tracks = bounds.slice(0, -1).map((_, index) => {
const i = index + 1;
const trackClassName = classNames({
[`${prefixCls}-track`]: true,
[`${prefixCls}-track-${i}`]: true,
});
return (
<Track
className={trackClassName}
vertical={vertical}
included={included}
offset={offsets[i - 1]}
length={offsets[i] - offsets[i - 1]}
style={trackStyle[index]}
key={i}
/>
);
});
return { tracks, handles };
}
}
export default createSlider(Range);

@ -0,0 +1,191 @@
/* eslint-disable react/prop-types */
import React from 'react'
import PropTypes from 'prop-types'
import warning from 'warning'
import Track from './common/Track'
import createSlider from './common/createSlider'
import * as utils from './utils'
class Slider extends React.Component {
static propTypes = {
defaultValue: PropTypes.number,
value: PropTypes.number,
disabled: PropTypes.bool,
autoFocus: PropTypes.bool,
tabIndex: PropTypes.number,
};
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),
'minimumTrackStyle will be deprecate, please use trackStyle instead.'
)
warning(
!('maximumTrackStyle' in props),
'maximumTrackStyle will be deprecate, please use railStyle instead.'
)
}
}
componentDidMount () {
const { autoFocus, disabled } = this.props
if (autoFocus && !disabled) {
this.focus()
}
}
componentWillReceiveProps (nextProps) {
if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return
const prevValue = this.state.value
const value = nextProps.value !== undefined
? nextProps.value : prevValue
const nextValue = this.trimAlignValue(value, nextProps)
if (nextValue === prevValue) return
this.setState({ value: nextValue })
if (utils.isValueOutOfRange(value, nextProps)) {
this.props.onChange(nextValue)
}
}
onChange (state) {
const props = this.props
const isNotControlled = !('value' in props)
if (isNotControlled) {
this.setState(state)
}
const changedValue = state.value
props.onChange(changedValue)
}
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) {
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
this.onChange({ value })
}
}
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)
}
render () {
const {
prefixCls,
vertical,
included,
disabled,
minimumTrackStyle,
trackStyle,
handleStyle,
tabIndex,
min,
max,
handle: handleGenerator,
} = this.props
const { value, dragging } = this.state
const offset = this.calcOffset(value)
const handle = handleGenerator({
className: `${prefixCls}-handle`,
prefixCls,
vertical,
offset,
value,
dragging,
disabled,
min,
max,
index: 0,
tabIndex,
style: handleStyle[0] || handleStyle,
ref: h => this.saveHandle(0, h),
})
const _trackStyle = trackStyle[0] || trackStyle
const track = (
<Track
className={`${prefixCls}-track`}
vertical={vertical}
included={included}
offset={0}
length={offset}
style={{
...minimumTrackStyle,
..._trackStyle,
}}
/>
)
return { tracks: track, handles: handle }
}
}
export default createSlider(Slider)

@ -0,0 +1,66 @@
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 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 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 (
<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

@ -0,0 +1,44 @@
import React from 'react'
import classNames from 'classnames'
import warning from 'warning'
const calcPoints = (vertical, marks, dots, step, min, max) => {
warning(
dots ? step > 0 : true,
'`Slider[step]` should be a positive number in order to make Slider[dots] work.'
)
const points = Object.keys(marks).map(parseFloat)
if (dots) {
for (let i = min; i <= max; i = i + step) {
if (points.indexOf(i) >= 0) continue
points.push(i)
}
}
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 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,
})
return <span className={pointClassName} style={style} key={point} />
})
return <div className={`${prefixCls}-step`}>{elements}</div>
}
export default Steps

@ -0,0 +1,22 @@
/* eslint-disable react/prop-types */
import React from 'react'
const Track = (props) => {
const { className, included, vertical, offset, length, style } = props
const positonStyle = vertical ? {
bottom: `${offset}%`,
height: `${length}%`,
} : {
left: `${offset}%`,
width: `${length}%`,
}
const elStyle = {
...style,
...positonStyle,
}
return included ? <div className={className} style={elStyle} /> : null
}
export default Track

@ -0,0 +1,343 @@
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 Steps from './Steps'
import Marks from './Marks'
import Handle from '../Handle'
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 = {
...Component.defaultProps,
prefixCls: 'rc-slider',
className: '',
min: 0,
max: 100,
step: 1,
marks: {},
handle ({ index, ...restProps }) {
delete restProps.dragging
return <Handle {...restProps} key={index} />
},
onBeforeChange: noop,
onChange: noop,
onAfterChange: noop,
included: true,
disabled: false,
dots: false,
vertical: false,
trackStyle: [{}],
handleStyle: [{}],
railStyle: {},
dotStyle: {},
activeDotStyle: {},
};
constructor (props) {
super(props)
if (process.env.NODE_ENV !== 'production') {
const { step, max, min } = props
warning(
step && Math.floor(step) === step ? (max - min) % step === 0 : true,
'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)',
max - min,
step
)
}
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)
}
}
}
onBlur = (e) => {
const { onBlur } = this.props
this.onEnd(e)
if (onBlur) {
onBlur(e)
}
};
addDocumentTouchEvents () {
// just work for Chrome iOS Safari and Android Browser
this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove)
this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd)
}
addDocumentMouseEvents () {
this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove)
this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd)
}
removeDocumentEvents () {
/* eslint-disable no-unused-expressions */
this.onTouchMoveListener && this.onTouchMoveListener.remove()
this.onTouchUpListener && this.onTouchUpListener.remove()
this.onMouseMoveListener && this.onMouseMoveListener.remove()
this.onMouseUpListener && this.onMouseUpListener.remove()
/* eslint-enable no-unused-expressions */
}
onMouseUp = () => {
if (this.handlesRefs[this.prevMovedHandleIndex]) {
this.handlesRefs[this.prevMovedHandleIndex].clickFocus()
}
}
onMouseMove = (e) => {
if (!this.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 })
}
render () {
const {
prefixCls,
className,
marks,
dots,
step,
included,
disabled,
vertical,
min,
max,
children,
maximumTrackStyle,
style,
railStyle,
dotStyle,
activeDotStyle,
} = this.props
const { tracks, handles } = super.render()
const sliderClassName = classNames(prefixCls, {
[`${prefixCls}-with-marks`]: Object.keys(marks).length,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-vertical`]: vertical,
[className]: className,
})
return (
<div
ref={this.saveSlider}
className={sliderClassName}
onTouchStart={disabled ? noop : this.onTouchStart}
onMouseDown={disabled ? noop : this.onMouseDown}
onMouseUp={disabled ? noop : this.onMouseUp}
onKeyDown={disabled ? noop : this.onKeyDown}
onFocus={disabled ? noop : this.onFocus}
onBlur={disabled ? noop : this.onBlur}
style={style}
>
<div
className={`${prefixCls}-rail`}
style={{
...maximumTrackStyle,
...railStyle,
}}
/>
{tracks}
<Steps
prefixCls={prefixCls}
vertical={vertical}
marks={marks}
dots={dots}
step={step}
included={included}
lowerBound={this.getLowerBound()}
upperBound={this.getUpperBound()}
max={max}
min={min}
dotStyle={dotStyle}
activeDotStyle={activeDotStyle}
/>
{handles}
<Marks
className={`${prefixCls}-mark`}
onClickLabel={disabled ? noop : this.onClickMarkLabel}
vertical={vertical}
marks={marks}
included={included}
lowerBound={this.getLowerBound()}
upperBound={this.getUpperBound()}
max={max}
min={min}
/>
{children}
</div>
)
}
}
}

@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import Tooltip from 'rc-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;
}
return (
<Tooltip
{...restTooltipProps}
prefixCls={prefixCls}
overlay={overlay}
placement={placement}
visible={(!disabled && (this.state.visibles[index] || dragging)) || visible}
key={index}
>
<Handle
{...restProps}
style={{
...handleStyleWithIndex,
}}
value={value}
onMouseEnter={() => this.handleTooltipVisibleChange(index, true)}
onMouseLeave={() => this.handleTooltipVisibleChange(index, false)}
/>
</Tooltip>
);
}
render() {
return <Component {...this.props} handle={this.handleWithTooltip} />;
}
};
}

@ -0,0 +1,10 @@
import Slider from './Slider'
import Range from './Range'
import Handle from './Handle'
import createSliderWithTooltip from './createSliderWithTooltip'
Slider.Range = Range
Slider.Handle = Handle
Slider.createSliderWithTooltip = createSliderWithTooltip
export default Slider
export { Range, Handle, createSliderWithTooltip }

@ -0,0 +1,92 @@
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]))
}
export function isValueOutOfRange (value, { min, max }) {
return value < min || value > max
}
export function isNotTouchEvent (e) {
return e.touches.length > 1 ||
(e.type.toLowerCase() === 'touchend' && e.touches.length > 0)
}
export function getClosestPoint (val, { marks, step, min }) {
const points = Object.keys(marks).map(parseFloat)
if (step !== null) {
const closestStep =
Math.round((val - min) / step) * step + min
points.push(closestStep)
}
const diffs = points.map(point => Math.abs(val - point))
return points[diffs.indexOf(Math.min(...diffs))]
}
export function getPrecision (step) {
const stepString = step.toString()
let precision = 0
if (stepString.indexOf('.') >= 0) {
precision = stepString.length - stepString.indexOf('.') - 1
}
return precision
}
export function getMousePosition (vertical, e) {
return vertical ? e.clientY : e.pageX
}
export function getTouchPosition (vertical, e) {
return vertical ? e.touches[0].clientY : e.touches[0].pageX
}
export function getHandleCenterPosition (vertical, handle) {
const coords = handle.getBoundingClientRect()
return vertical
? coords.top + (coords.height * 0.5)
: coords.left + (coords.width * 0.5)
}
export function ensureValueInRange (val, { max, min }) {
if (val <= min) {
return min
}
if (val >= max) {
return max
}
return val
}
export function ensureValuePrecision (val, props) {
const { step } = props
const closestPoint = getClosestPoint(val, props)
return step === null ? closestPoint
: parseFloat(closestPoint.toFixed(getPrecision(step)))
}
export function pauseEvent (e) {
e.stopPropagation()
e.preventDefault()
}
export function getKeyboardValueMutator (e) {
switch (e.keyCode) {
case keyCode.UP:
case keyCode.RIGHT:
return (value, props) => value + props.step
case keyCode.DOWN:
case keyCode.LEFT:
return (value, props) => value - props.step
case keyCode.END: return (value, props) => props.max
case keyCode.HOME: return (value, props) => props.min
case keyCode.PAGE_UP: return (value, props) => value + props.step * 2
case keyCode.PAGE_DOWN: return (value, props) => value - props.step * 2
default: return undefined
}
}

@ -71,8 +71,8 @@ export default {
adjustMarginRight,
} = getOptionProps(this)
const title = this.title || this.$slots.title
const description = this.description || this.$slots.description
const title = getComponentFromProp(this, 'title')
const description = getComponentFromProp(this, 'description')
const classString = {
[`${prefixCls}-item`]: true,

@ -41,7 +41,7 @@ TreeSelect
##王
Rate | done
Pagination | done|select完成后补全
Pagination | done
Avatar | done
Badge | done
Breadcrumb | done
@ -49,7 +49,7 @@ Card | done
Collapse | done
Spin | done
Switch | done
Steps
Steps | done
Progress
Slider
Table

@ -3,7 +3,7 @@ const AsyncComp = () => {
const hashs = window.location.hash.split('/')
const d = hashs[hashs.length - 1]
return {
component: import(`../components/pagination/demo/${d}`),
component: import(`../components/steps/demo/${d}`),
}
}
export default [

Loading…
Cancel
Save