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,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
|
||||
}
|
||||
}
|
Loading…
Reference in new issue