merge
						commit
						a1ef2e0962
					
				| 
						 | 
				
			
			@ -0,0 +1,115 @@
 | 
			
		|||
@inputNumberPrefixCls: rc-input-number;
 | 
			
		||||
 | 
			
		||||
.@{inputNumberPrefixCls} {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  line-height: 26px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  height: 26px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  border: 1px solid #D9D9D9;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
 | 
			
		||||
  &-handler {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 12px;
 | 
			
		||||
    height: 12px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    display:block;
 | 
			
		||||
    touch-action: none;
 | 
			
		||||
 | 
			
		||||
    &-active {
 | 
			
		||||
      background: #ddd;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-handler-up-inner, &-handler-down-inner {
 | 
			
		||||
    color: #666666;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
    -webkit-user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border-color: #23c0fa;
 | 
			
		||||
 | 
			
		||||
    .@{inputNumberPrefixCls}-handler-up, .@{inputNumberPrefixCls}-handler-wrap {
 | 
			
		||||
      border-color: #23c0fa;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-disabled:hover {
 | 
			
		||||
    border-color: #d9d9d9;
 | 
			
		||||
 | 
			
		||||
    .@{inputNumberPrefixCls}-handler-up, .@{inputNumberPrefixCls}-handler-wrap {
 | 
			
		||||
      border-color: #d9d9d9;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-input-wrap {
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    height: 26px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-input {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    -moz-appearance: textfield;
 | 
			
		||||
    line-height: 26px;
 | 
			
		||||
    height: 26px;
 | 
			
		||||
    transition: all 0.3s ease;
 | 
			
		||||
    color: #666666;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-handler-wrap {
 | 
			
		||||
    float: right;
 | 
			
		||||
    border-left: 1px solid #D9D9D9;
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 26px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-handler-up {
 | 
			
		||||
    border-bottom: 1px solid #D9D9D9;
 | 
			
		||||
    padding-top: 1px;
 | 
			
		||||
    &-inner {
 | 
			
		||||
      &:after {
 | 
			
		||||
        content: '+';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-handler-down {
 | 
			
		||||
    &-inner {
 | 
			
		||||
      &:after {
 | 
			
		||||
        content: '-';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .handler-disabled() {
 | 
			
		||||
    opacity: 0.72;
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: #999;
 | 
			
		||||
      border-color: #d9d9d9;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-handler-down-disabled, &-handler-up-disabled {
 | 
			
		||||
    .handler-disabled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &-disabled {
 | 
			
		||||
    .@{inputNumberPrefixCls}-input {
 | 
			
		||||
      opacity: 0.72;
 | 
			
		||||
      cursor: not-allowed;
 | 
			
		||||
      background-color: #f3f3f3;
 | 
			
		||||
    }
 | 
			
		||||
    .@{inputNumberPrefixCls}-handler {
 | 
			
		||||
      .handler-disabled();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
import React, { Component } from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import Touchable from 'rmc-feedback';
 | 
			
		||||
 | 
			
		||||
class InputHandler extends Component {
 | 
			
		||||
  render() {
 | 
			
		||||
    const {
 | 
			
		||||
      prefixCls, disabled, onTouchStart, onTouchEnd,
 | 
			
		||||
      onMouseDown, onMouseUp, onMouseLeave, ...otherProps,
 | 
			
		||||
    } = this.props;
 | 
			
		||||
    return (
 | 
			
		||||
      <Touchable
 | 
			
		||||
        disabled={disabled}
 | 
			
		||||
        onTouchStart={onTouchStart}
 | 
			
		||||
        onTouchEnd={onTouchEnd}
 | 
			
		||||
        onMouseDown={onMouseDown}
 | 
			
		||||
        onMouseUp={onMouseUp}
 | 
			
		||||
        onMouseLeave={onMouseLeave}
 | 
			
		||||
        activeClassName={`${prefixCls}-handler-active`}
 | 
			
		||||
      >
 | 
			
		||||
        <span {...otherProps} />
 | 
			
		||||
      </Touchable>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InputHandler.propTypes = {
 | 
			
		||||
  prefixCls: PropTypes.string,
 | 
			
		||||
  disabled: PropTypes.bool,
 | 
			
		||||
  onTouchStart: PropTypes.func,
 | 
			
		||||
  onTouchEnd: PropTypes.func,
 | 
			
		||||
  onMouseDown: PropTypes.func,
 | 
			
		||||
  onMouseUp: PropTypes.func,
 | 
			
		||||
  onMouseLeave: PropTypes.func,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default InputHandler;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,610 @@
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import isNegativeZero from 'is-negative-zero';
 | 
			
		||||
import InputHandler from './InputHandler';
 | 
			
		||||
 | 
			
		||||
function noop() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function preventDefault(e) {
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function defaultParser(input) {
 | 
			
		||||
  return input.replace(/[^\w\.-]+/g, '');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When click and hold on a button - the speed of auto changin the value.
 | 
			
		||||
 */
 | 
			
		||||
const SPEED = 200;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When click and hold on a button - the delay before auto changin the value.
 | 
			
		||||
 */
 | 
			
		||||
const DELAY = 600;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Max Safe Integer -- on IE this is not available, so manually set the number in that case.
 | 
			
		||||
 * The reason this is used, instead of Infinity is because numbers above the MSI are unstable
 | 
			
		||||
 */
 | 
			
		||||
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;
 | 
			
		||||
 | 
			
		||||
export default class InputNumber extends React.Component {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    value: PropTypes.oneOfType([
 | 
			
		||||
      PropTypes.number,
 | 
			
		||||
      PropTypes.string,
 | 
			
		||||
    ]),
 | 
			
		||||
    defaultValue: PropTypes.oneOfType([
 | 
			
		||||
      PropTypes.number,
 | 
			
		||||
      PropTypes.string,
 | 
			
		||||
    ]),
 | 
			
		||||
    focusOnUpDown: PropTypes.bool,
 | 
			
		||||
    autoFocus: PropTypes.bool,
 | 
			
		||||
    onChange: PropTypes.func,
 | 
			
		||||
    onKeyDown: PropTypes.func,
 | 
			
		||||
    onKeyUp: PropTypes.func,
 | 
			
		||||
    prefixCls: PropTypes.string,
 | 
			
		||||
    tabIndex: PropTypes.string,
 | 
			
		||||
    disabled: PropTypes.bool,
 | 
			
		||||
    onFocus: PropTypes.func,
 | 
			
		||||
    onBlur: PropTypes.func,
 | 
			
		||||
    readOnly: PropTypes.bool,
 | 
			
		||||
    max: PropTypes.number,
 | 
			
		||||
    min: PropTypes.number,
 | 
			
		||||
    step: PropTypes.oneOfType([
 | 
			
		||||
      PropTypes.number,
 | 
			
		||||
      PropTypes.string,
 | 
			
		||||
    ]),
 | 
			
		||||
    upHandler: PropTypes.node,
 | 
			
		||||
    downHandler: PropTypes.node,
 | 
			
		||||
    useTouch: PropTypes.bool,
 | 
			
		||||
    formatter: PropTypes.func,
 | 
			
		||||
    parser: PropTypes.func,
 | 
			
		||||
    onMouseEnter: PropTypes.func,
 | 
			
		||||
    onMouseLeave: PropTypes.func,
 | 
			
		||||
    onMouseOver: PropTypes.func,
 | 
			
		||||
    onMouseOut: PropTypes.func,
 | 
			
		||||
    precision: PropTypes.number,
 | 
			
		||||
    required: PropTypes.bool,
 | 
			
		||||
    pattern: PropTypes.string,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static defaultProps = {
 | 
			
		||||
    focusOnUpDown: true,
 | 
			
		||||
    useTouch: false,
 | 
			
		||||
    prefixCls: 'rc-input-number',
 | 
			
		||||
    min: -MAX_SAFE_INTEGER,
 | 
			
		||||
    step: 1,
 | 
			
		||||
    style: {},
 | 
			
		||||
    onChange: noop,
 | 
			
		||||
    onKeyDown: noop,
 | 
			
		||||
    onFocus: noop,
 | 
			
		||||
    onBlur: noop,
 | 
			
		||||
    parser: defaultParser,
 | 
			
		||||
    required: false,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(props) {
 | 
			
		||||
    super(props);
 | 
			
		||||
 | 
			
		||||
    let value;
 | 
			
		||||
    if ('value' in props) {
 | 
			
		||||
      value = props.value;
 | 
			
		||||
    } else {
 | 
			
		||||
      value = props.defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    value = this.toNumber(value);
 | 
			
		||||
 | 
			
		||||
    this.state = {
 | 
			
		||||
      inputValue: this.toPrecisionAsStep(value),
 | 
			
		||||
      value,
 | 
			
		||||
      focused: props.autoFocus,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount() {
 | 
			
		||||
    this.componentDidUpdate();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillReceiveProps(nextProps) {
 | 
			
		||||
    if ('value' in nextProps) {
 | 
			
		||||
      const value = this.state.focused
 | 
			
		||||
        ? nextProps.value : this.getValidValue(nextProps.value, nextProps.min, nextProps.max);
 | 
			
		||||
      this.setState({
 | 
			
		||||
        value,
 | 
			
		||||
        inputValue: this.inputting ? value : this.toPrecisionAsStep(value),
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUpdate() {
 | 
			
		||||
    try {
 | 
			
		||||
      this.start = this.input.selectionStart;
 | 
			
		||||
      this.end = this.input.selectionEnd;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      // Fix error in Chrome:
 | 
			
		||||
      // Failed to read the 'selectionStart' property from 'HTMLInputElement'
 | 
			
		||||
      // http://stackoverflow.com/q/21177489/3040605
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate() {
 | 
			
		||||
    // pressingUpOrDown is true means that someone just click up or down button
 | 
			
		||||
    // https://github.com/ant-design/ant-design/issues/9204
 | 
			
		||||
    if (!this.pressingUpOrDown) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.props.focusOnUpDown && this.state.focused) {
 | 
			
		||||
      const selectionRange = this.input.setSelectionRange;
 | 
			
		||||
      if (selectionRange &&
 | 
			
		||||
          typeof selectionRange === 'function' &&
 | 
			
		||||
          this.start !== undefined &&
 | 
			
		||||
          this.end !== undefined) {
 | 
			
		||||
        this.input.setSelectionRange(this.start, this.end);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.focus();
 | 
			
		||||
      }
 | 
			
		||||
      this.pressingUpOrDown = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount() {
 | 
			
		||||
    this.stop();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onKeyDown = (e, ...args) => {
 | 
			
		||||
    if (e.keyCode === 38) {
 | 
			
		||||
      const ratio = this.getRatio(e);
 | 
			
		||||
      this.up(e, ratio);
 | 
			
		||||
      this.stop();
 | 
			
		||||
    } else if (e.keyCode === 40) {
 | 
			
		||||
      const ratio = this.getRatio(e);
 | 
			
		||||
      this.down(e, ratio);
 | 
			
		||||
      this.stop();
 | 
			
		||||
    }
 | 
			
		||||
    const { onKeyDown } = this.props;
 | 
			
		||||
    if (onKeyDown) {
 | 
			
		||||
      onKeyDown(e, ...args);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onKeyUp = (e, ...args) => {
 | 
			
		||||
    this.stop();
 | 
			
		||||
    const { onKeyUp } = this.props;
 | 
			
		||||
    if (onKeyUp) {
 | 
			
		||||
      onKeyUp(e, ...args);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChange = (e) => {
 | 
			
		||||
    if (this.state.focused) {
 | 
			
		||||
      this.inputting = true;
 | 
			
		||||
    }
 | 
			
		||||
    const input = this.props.parser(this.getValueFromEvent(e));
 | 
			
		||||
    this.setState({ inputValue: input });
 | 
			
		||||
    this.props.onChange(this.toNumberWhenUserInput(input)); // valid number or invalid string
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onFocus = (...args) => {
 | 
			
		||||
    this.setState({
 | 
			
		||||
      focused: true,
 | 
			
		||||
    });
 | 
			
		||||
    this.props.onFocus(...args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onBlur = (e, ...args) => {
 | 
			
		||||
    this.inputting = false;
 | 
			
		||||
    this.setState({
 | 
			
		||||
      focused: false,
 | 
			
		||||
    });
 | 
			
		||||
    const value = this.getCurrentValidValue(this.state.inputValue);
 | 
			
		||||
    e.persist();  // fix https://github.com/react-component/input-number/issues/51
 | 
			
		||||
    this.setValue(value, () => {
 | 
			
		||||
      this.props.onBlur(e, ...args);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getCurrentValidValue(value) {
 | 
			
		||||
    let val = value;
 | 
			
		||||
    if (val === '') {
 | 
			
		||||
      val = '';
 | 
			
		||||
    } else if (!this.isNotCompleteNumber(val)) {
 | 
			
		||||
      val = this.getValidValue(val);
 | 
			
		||||
    } else {
 | 
			
		||||
      val = this.state.value;
 | 
			
		||||
    }
 | 
			
		||||
    return this.toNumber(val);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getRatio(e) {
 | 
			
		||||
    let ratio = 1;
 | 
			
		||||
    if (e.metaKey || e.ctrlKey) {
 | 
			
		||||
      ratio = 0.1;
 | 
			
		||||
    } else if (e.shiftKey) {
 | 
			
		||||
      ratio = 10;
 | 
			
		||||
    }
 | 
			
		||||
    return ratio;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getValueFromEvent(e) {
 | 
			
		||||
    // optimize for chinese input expierence
 | 
			
		||||
    // https://github.com/ant-design/ant-design/issues/8196
 | 
			
		||||
    return e.target.value.trim().replace(/。/g, '.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getValidValue(value, min = this.props.min, max = this.props.max) {
 | 
			
		||||
    let val = parseFloat(value, 10);
 | 
			
		||||
    // https://github.com/ant-design/ant-design/issues/7358
 | 
			
		||||
    if (isNaN(val)) {
 | 
			
		||||
      return value;
 | 
			
		||||
    }
 | 
			
		||||
    if (val < min) {
 | 
			
		||||
      val = min;
 | 
			
		||||
    }
 | 
			
		||||
    if (val > max) {
 | 
			
		||||
      val = max;
 | 
			
		||||
    }
 | 
			
		||||
    return val;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setValue(v, callback) {
 | 
			
		||||
    // trigger onChange
 | 
			
		||||
    const newValue = this.isNotCompleteNumber(parseFloat(v, 10)) ? undefined : parseFloat(v, 10);
 | 
			
		||||
    const changed = newValue !== this.state.value ||
 | 
			
		||||
      `${newValue}` !== `${this.state.inputValue}`; // https://github.com/ant-design/ant-design/issues/7363
 | 
			
		||||
    if (!('value' in this.props)) {
 | 
			
		||||
      this.setState({
 | 
			
		||||
        value: newValue,
 | 
			
		||||
        inputValue: this.toPrecisionAsStep(v),
 | 
			
		||||
      }, callback);
 | 
			
		||||
    } else {
 | 
			
		||||
      // always set input value same as value
 | 
			
		||||
      this.setState({
 | 
			
		||||
        inputValue: this.toPrecisionAsStep(this.state.value),
 | 
			
		||||
      }, callback);
 | 
			
		||||
    }
 | 
			
		||||
    if (changed) {
 | 
			
		||||
      this.props.onChange(newValue);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getPrecision(value) {
 | 
			
		||||
    if ('precision' in this.props) {
 | 
			
		||||
      return this.props.precision;
 | 
			
		||||
    }
 | 
			
		||||
    const valueString = value.toString();
 | 
			
		||||
    if (valueString.indexOf('e-') >= 0) {
 | 
			
		||||
      return parseInt(valueString.slice(valueString.indexOf('e-') + 2), 10);
 | 
			
		||||
    }
 | 
			
		||||
    let precision = 0;
 | 
			
		||||
    if (valueString.indexOf('.') >= 0) {
 | 
			
		||||
      precision = valueString.length - valueString.indexOf('.') - 1;
 | 
			
		||||
    }
 | 
			
		||||
    return precision;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // step={1.0} value={1.51}
 | 
			
		||||
  // press +
 | 
			
		||||
  // then value should be 2.51, rather than 2.5
 | 
			
		||||
  // if this.props.precision is undefined
 | 
			
		||||
  // https://github.com/react-component/input-number/issues/39
 | 
			
		||||
  getMaxPrecision(currentValue, ratio = 1) {
 | 
			
		||||
    if ('precision' in this.props) {
 | 
			
		||||
      return this.props.precision;
 | 
			
		||||
    }
 | 
			
		||||
    const { step } = this.props;
 | 
			
		||||
    const ratioPrecision = this.getPrecision(ratio);
 | 
			
		||||
    const stepPrecision = this.getPrecision(step);
 | 
			
		||||
    const currentValuePrecision = this.getPrecision(currentValue);
 | 
			
		||||
    if (!currentValue) {
 | 
			
		||||
      return ratioPrecision + stepPrecision;
 | 
			
		||||
    }
 | 
			
		||||
    return Math.max(currentValuePrecision, ratioPrecision + stepPrecision);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getPrecisionFactor(currentValue, ratio = 1) {
 | 
			
		||||
    const precision = this.getMaxPrecision(currentValue, ratio);
 | 
			
		||||
    return Math.pow(10, precision);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  focus() {
 | 
			
		||||
    this.input.focus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  blur() {
 | 
			
		||||
    this.input.blur();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  formatWrapper(num) {
 | 
			
		||||
    // http://2ality.com/2012/03/signedzero.html
 | 
			
		||||
    // https://github.com/ant-design/ant-design/issues/9439
 | 
			
		||||
    if (isNegativeZero(num)) {
 | 
			
		||||
      return '-0';
 | 
			
		||||
    }
 | 
			
		||||
    if (this.props.formatter) {
 | 
			
		||||
      return this.props.formatter(num);
 | 
			
		||||
    }
 | 
			
		||||
    return num;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toPrecisionAsStep(num) {
 | 
			
		||||
    if (this.isNotCompleteNumber(num) || num === '') {
 | 
			
		||||
      return num;
 | 
			
		||||
    }
 | 
			
		||||
    const precision = Math.abs(this.getMaxPrecision(num));
 | 
			
		||||
    if (precision === 0) {
 | 
			
		||||
      return num.toString();
 | 
			
		||||
    }
 | 
			
		||||
    if (!isNaN(precision)) {
 | 
			
		||||
      return Number(num).toFixed(precision);
 | 
			
		||||
    }
 | 
			
		||||
    return num.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // '1.' '1x' 'xx' '' => are not complete numbers
 | 
			
		||||
  isNotCompleteNumber(num) {
 | 
			
		||||
    return (
 | 
			
		||||
      isNaN(num) ||
 | 
			
		||||
      num === '' ||
 | 
			
		||||
      num === null ||
 | 
			
		||||
      (num && num.toString().indexOf('.') === num.toString().length - 1)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toNumber(num) {
 | 
			
		||||
    if (this.isNotCompleteNumber(num)) {
 | 
			
		||||
      return num;
 | 
			
		||||
    }
 | 
			
		||||
    if ('precision' in this.props) {
 | 
			
		||||
      return Number(Number(num).toFixed(this.props.precision));
 | 
			
		||||
    }
 | 
			
		||||
    return Number(num);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // '1.0' '1.00'  => may be a inputing number
 | 
			
		||||
  toNumberWhenUserInput(num) {
 | 
			
		||||
    // num.length > 16 => prevent input large number will became Infinity
 | 
			
		||||
    if ((/\.\d*0$/.test(num) || num.length > 16) && this.state.focused) {
 | 
			
		||||
      return num;
 | 
			
		||||
    }
 | 
			
		||||
    return this.toNumber(num);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  upStep(val, rat) {
 | 
			
		||||
    const { step, min } = this.props;
 | 
			
		||||
    const precisionFactor = this.getPrecisionFactor(val, rat);
 | 
			
		||||
    const precision = Math.abs(this.getMaxPrecision(val, rat));
 | 
			
		||||
    let result;
 | 
			
		||||
    if (typeof val === 'number') {
 | 
			
		||||
      result =
 | 
			
		||||
        ((precisionFactor * val + precisionFactor * step * rat) /
 | 
			
		||||
        precisionFactor).toFixed(precision);
 | 
			
		||||
    } else {
 | 
			
		||||
      result = min === -Infinity ? step : min;
 | 
			
		||||
    }
 | 
			
		||||
    return this.toNumber(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downStep(val, rat) {
 | 
			
		||||
    const { step, min } = this.props;
 | 
			
		||||
    const precisionFactor = this.getPrecisionFactor(val, rat);
 | 
			
		||||
    const precision = Math.abs(this.getMaxPrecision(val, rat));
 | 
			
		||||
    let result;
 | 
			
		||||
    if (typeof val === 'number') {
 | 
			
		||||
      result =
 | 
			
		||||
        ((precisionFactor * val - precisionFactor * step * rat) /
 | 
			
		||||
        precisionFactor).toFixed(precision);
 | 
			
		||||
    } else {
 | 
			
		||||
      result = min === -Infinity ? -step : min;
 | 
			
		||||
    }
 | 
			
		||||
    return this.toNumber(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  step(type, e, ratio = 1, recursive) {
 | 
			
		||||
    this.stop();
 | 
			
		||||
    if (e) {
 | 
			
		||||
      e.persist();
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
    const props = this.props;
 | 
			
		||||
    if (props.disabled) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const value = this.getCurrentValidValue(this.state.inputValue) || 0;
 | 
			
		||||
    if (this.isNotCompleteNumber(value)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let val = this[`${type}Step`](value, ratio);
 | 
			
		||||
    const outOfRange = val > props.max || val < props.min;
 | 
			
		||||
    if (val > props.max) {
 | 
			
		||||
      val = props.max;
 | 
			
		||||
    } else if (val < props.min) {
 | 
			
		||||
      val = props.min;
 | 
			
		||||
    }
 | 
			
		||||
    this.setValue(val);
 | 
			
		||||
    this.setState({
 | 
			
		||||
      focused: true,
 | 
			
		||||
    });
 | 
			
		||||
    if (outOfRange) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.autoStepTimer = setTimeout(() => {
 | 
			
		||||
      this[type](e, ratio, true);
 | 
			
		||||
    }, recursive ? SPEED : DELAY);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  stop = () => {
 | 
			
		||||
    if (this.autoStepTimer) {
 | 
			
		||||
      clearTimeout(this.autoStepTimer);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  down = (e, ratio, recursive) => {
 | 
			
		||||
    this.pressingUpOrDown = true;
 | 
			
		||||
    this.step('down', e, ratio, recursive);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  up = (e, ratio, recursive) => {
 | 
			
		||||
    this.pressingUpOrDown = true;
 | 
			
		||||
    this.step('up', e, ratio, recursive);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  saveInput = (node) => {
 | 
			
		||||
    this.input = node;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const props = { ...this.props };
 | 
			
		||||
    const { prefixCls, disabled, readOnly, useTouch } = props;
 | 
			
		||||
    const classes = classNames({
 | 
			
		||||
      [prefixCls]: true,
 | 
			
		||||
      [props.className]: !!props.className,
 | 
			
		||||
      [`${prefixCls}-disabled`]: disabled,
 | 
			
		||||
      [`${prefixCls}-focused`]: this.state.focused,
 | 
			
		||||
    });
 | 
			
		||||
    let upDisabledClass = '';
 | 
			
		||||
    let downDisabledClass = '';
 | 
			
		||||
    const { value } = this.state;
 | 
			
		||||
    if (value || value === 0) {
 | 
			
		||||
      if (!isNaN(value)) {
 | 
			
		||||
        const val = Number(value);
 | 
			
		||||
        if (val >= props.max) {
 | 
			
		||||
          upDisabledClass = `${prefixCls}-handler-up-disabled`;
 | 
			
		||||
        }
 | 
			
		||||
        if (val <= props.min) {
 | 
			
		||||
          downDisabledClass = `${prefixCls}-handler-down-disabled`;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        upDisabledClass = `${prefixCls}-handler-up-disabled`;
 | 
			
		||||
        downDisabledClass = `${prefixCls}-handler-down-disabled`;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const editable = !props.readOnly && !props.disabled;
 | 
			
		||||
 | 
			
		||||
    // focus state, show input value
 | 
			
		||||
    // unfocus state, show valid value
 | 
			
		||||
    let inputDisplayValue;
 | 
			
		||||
    if (this.state.focused) {
 | 
			
		||||
      inputDisplayValue = this.state.inputValue;
 | 
			
		||||
    } else {
 | 
			
		||||
      inputDisplayValue = this.toPrecisionAsStep(this.state.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (inputDisplayValue === undefined || inputDisplayValue === null) {
 | 
			
		||||
      inputDisplayValue = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let upEvents;
 | 
			
		||||
    let downEvents;
 | 
			
		||||
    if (useTouch) {
 | 
			
		||||
      upEvents = {
 | 
			
		||||
        onTouchStart: (editable && !upDisabledClass) ? this.up : noop,
 | 
			
		||||
        onTouchEnd: this.stop,
 | 
			
		||||
      };
 | 
			
		||||
      downEvents = {
 | 
			
		||||
        onTouchStart: (editable && !downDisabledClass) ? this.down : noop,
 | 
			
		||||
        onTouchEnd: this.stop,
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      upEvents = {
 | 
			
		||||
        onMouseDown: (editable && !upDisabledClass) ? this.up : noop,
 | 
			
		||||
        onMouseUp: this.stop,
 | 
			
		||||
        onMouseLeave: this.stop,
 | 
			
		||||
      };
 | 
			
		||||
      downEvents = {
 | 
			
		||||
        onMouseDown: (editable && !downDisabledClass) ? this.down : noop,
 | 
			
		||||
        onMouseUp: this.stop,
 | 
			
		||||
        onMouseLeave: this.stop,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    const inputDisplayValueFormat = this.formatWrapper(inputDisplayValue);
 | 
			
		||||
    const isUpDisabled = !!upDisabledClass || disabled || readOnly;
 | 
			
		||||
    const isDownDisabled = !!downDisabledClass || disabled || readOnly;
 | 
			
		||||
    // ref for test
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        className={classes}
 | 
			
		||||
        style={props.style}
 | 
			
		||||
        onMouseEnter={props.onMouseEnter}
 | 
			
		||||
        onMouseLeave={props.onMouseLeave}
 | 
			
		||||
        onMouseOver={props.onMouseOver}
 | 
			
		||||
        onMouseOut={props.onMouseOut}
 | 
			
		||||
      >
 | 
			
		||||
        <div className={`${prefixCls}-handler-wrap`}>
 | 
			
		||||
          <InputHandler
 | 
			
		||||
            ref="up"
 | 
			
		||||
            disabled={isUpDisabled}
 | 
			
		||||
            prefixCls={prefixCls}
 | 
			
		||||
            unselectable="unselectable"
 | 
			
		||||
            {...upEvents}
 | 
			
		||||
            role="button"
 | 
			
		||||
            aria-label="Increase Value"
 | 
			
		||||
            aria-disabled={!!isUpDisabled}
 | 
			
		||||
            className={`${prefixCls}-handler ${prefixCls}-handler-up ${upDisabledClass}`}
 | 
			
		||||
          >
 | 
			
		||||
            {this.props.upHandler || <span
 | 
			
		||||
              unselectable="unselectable"
 | 
			
		||||
              className={`${prefixCls}-handler-up-inner`}
 | 
			
		||||
              onClick={preventDefault}
 | 
			
		||||
            />}
 | 
			
		||||
          </InputHandler>
 | 
			
		||||
          <InputHandler
 | 
			
		||||
            ref="down"
 | 
			
		||||
            disabled={isDownDisabled}
 | 
			
		||||
            prefixCls={prefixCls}
 | 
			
		||||
            unselectable="unselectable"
 | 
			
		||||
            {...downEvents}
 | 
			
		||||
            role="button"
 | 
			
		||||
            aria-label="Decrease Value"
 | 
			
		||||
            aria-disabled={!!isDownDisabled}
 | 
			
		||||
            className={`${prefixCls}-handler ${prefixCls}-handler-down ${downDisabledClass}`}
 | 
			
		||||
          >
 | 
			
		||||
            {this.props.downHandler || <span
 | 
			
		||||
              unselectable="unselectable"
 | 
			
		||||
              className={`${prefixCls}-handler-down-inner`}
 | 
			
		||||
              onClick={preventDefault}
 | 
			
		||||
            />}
 | 
			
		||||
          </InputHandler>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
          className={`${prefixCls}-input-wrap`}
 | 
			
		||||
          role="spinbutton"
 | 
			
		||||
          aria-valuemin={props.min}
 | 
			
		||||
          aria-valuemax={props.max}
 | 
			
		||||
          aria-valuenow={value}
 | 
			
		||||
        >
 | 
			
		||||
          <input
 | 
			
		||||
            required={props.required}
 | 
			
		||||
            type={props.type}
 | 
			
		||||
            placeholder={props.placeholder}
 | 
			
		||||
            onClick={props.onClick}
 | 
			
		||||
            className={`${prefixCls}-input`}
 | 
			
		||||
            tabIndex={props.tabIndex}
 | 
			
		||||
            autoComplete="off"
 | 
			
		||||
            onFocus={this.onFocus}
 | 
			
		||||
            onBlur={this.onBlur}
 | 
			
		||||
            onKeyDown={editable ? this.onKeyDown : noop}
 | 
			
		||||
            onKeyUp={editable ? this.onKeyUp : noop}
 | 
			
		||||
            autoFocus={props.autoFocus}
 | 
			
		||||
            maxLength={props.maxLength}
 | 
			
		||||
            readOnly={props.readOnly}
 | 
			
		||||
            disabled={props.disabled}
 | 
			
		||||
            max={props.max}
 | 
			
		||||
            min={props.min}
 | 
			
		||||
            step={props.step}
 | 
			
		||||
            name={props.name}
 | 
			
		||||
            id={props.id}
 | 
			
		||||
            onChange={this.onChange}
 | 
			
		||||
            ref={this.saveInput}
 | 
			
		||||
            value={inputDisplayValueFormat}
 | 
			
		||||
            pattern={props.pattern}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
import TouchFeedback from '../index'
 | 
			
		||||
import './simple.less'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  render () {
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ marginBottom: 12 }}>
 | 
			
		||||
        <TouchFeedback activeClassName='active' activeStyle={{ color: 'red' }}>
 | 
			
		||||
          <div class='normal' style={{
 | 
			
		||||
            backgroundColor: 'yellow',
 | 
			
		||||
          }}
 | 
			
		||||
          onClick={() => console.log('click div')}>click to active</div>
 | 
			
		||||
        </TouchFeedback>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
.normal {
 | 
			
		||||
  color: '#000'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.active {
 | 
			
		||||
  font-size: 40px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
 | 
			
		||||
import TouchFeedback from './src/TouchFeedback'
 | 
			
		||||
export default TouchFeedback
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
import PropTypes from '../../_util/vue-types'
 | 
			
		||||
 | 
			
		||||
export const ITouchProps = {
 | 
			
		||||
  disabled: PropTypes.bool,
 | 
			
		||||
  activeClassName: PropTypes.string,
 | 
			
		||||
  activeStyle: PropTypes.any,
 | 
			
		||||
  // onTouchStart: PropTypes.func,
 | 
			
		||||
  // onTouchEnd: PropTypes.func,
 | 
			
		||||
  // onTouchCancel: PropTypes.func,
 | 
			
		||||
  // onMouseDown: PropTypes.func,
 | 
			
		||||
  // onMouseUp: PropTypes.func,
 | 
			
		||||
  // onMouseLeave: PropTypes.func,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,98 @@
 | 
			
		|||
import { initDefaultProps } from '../../_util/props-util'
 | 
			
		||||
import { cloneElement } from '../../_util/vnode'
 | 
			
		||||
import warning from '../../_util/warning'
 | 
			
		||||
import BaseMixin from '../../_util/BaseMixin'
 | 
			
		||||
import { ITouchProps } from './PropTypes'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'TouchFeedback',
 | 
			
		||||
  mixins: [BaseMixin],
 | 
			
		||||
  props: initDefaultProps(ITouchProps, {
 | 
			
		||||
    disabled: false,
 | 
			
		||||
  }),
 | 
			
		||||
  data () {
 | 
			
		||||
    return {
 | 
			
		||||
      active: false,
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted () {
 | 
			
		||||
    this.$nextTick(() => {
 | 
			
		||||
      if (this.disabled && this.active) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
          active: false,
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    triggerEvent (type, isActive, ev) {
 | 
			
		||||
      // const eventType = `on${type}`
 | 
			
		||||
      // if (this.props[eventType]) {
 | 
			
		||||
      //   this.props[eventType](ev)
 | 
			
		||||
      // }
 | 
			
		||||
      this.$emit(type, ev)
 | 
			
		||||
      if (isActive !== this.active) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
          active: isActive,
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    onTouchStart (e) {
 | 
			
		||||
      this.triggerEvent('touchstart', true, e)
 | 
			
		||||
    },
 | 
			
		||||
    onTouchMove (e) {
 | 
			
		||||
      this.triggerEvent('touchmove', false, e)
 | 
			
		||||
    },
 | 
			
		||||
    onTouchEnd (e) {
 | 
			
		||||
      this.triggerEvent('touchend', false, e)
 | 
			
		||||
    },
 | 
			
		||||
    onTouchCancel (e) {
 | 
			
		||||
      this.triggerEvent('touchcancel', false, e)
 | 
			
		||||
    },
 | 
			
		||||
    onMouseDown (e) {
 | 
			
		||||
      // todo
 | 
			
		||||
      // pc simulate mobile
 | 
			
		||||
      // if (this.props.onTouchStart) {
 | 
			
		||||
      this.triggerEvent('touchstart', true, e)
 | 
			
		||||
      // }
 | 
			
		||||
      this.triggerEvent('mousedown', true, e)
 | 
			
		||||
    },
 | 
			
		||||
    onMouseUp (e) {
 | 
			
		||||
      // if (this.props.onTouchEnd) {
 | 
			
		||||
      this.triggerEvent('touchend', false, e)
 | 
			
		||||
      // }
 | 
			
		||||
      this.triggerEvent('mouseup', false, e)
 | 
			
		||||
    },
 | 
			
		||||
    onMouseLeave (e) {
 | 
			
		||||
      this.triggerEvent('mouseleave', false, e)
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  render () {
 | 
			
		||||
    const { disabled, activeClassName = '', activeStyle = {}} = this.$props
 | 
			
		||||
 | 
			
		||||
    const child = this.$slots.default
 | 
			
		||||
    if (child.length !== 1) {
 | 
			
		||||
      warning(false, '只能包含一个子元素')
 | 
			
		||||
    }
 | 
			
		||||
    let childProps = {
 | 
			
		||||
      on: disabled ? {} : {
 | 
			
		||||
        touchstart: this.onTouchStart,
 | 
			
		||||
        touchmove: this.onTouchMove,
 | 
			
		||||
        touchend: this.onTouchEnd,
 | 
			
		||||
        touchcancel: this.onTouchCancel,
 | 
			
		||||
        mousedown: this.onMouseDown,
 | 
			
		||||
        mouseup: this.onMouseUp,
 | 
			
		||||
        mouseleave: this.onMouseLeave,
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!disabled && this.active) {
 | 
			
		||||
      childProps = { ...childProps, ...{
 | 
			
		||||
        style: activeStyle,
 | 
			
		||||
        class: activeClassName,
 | 
			
		||||
      }}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return cloneElement(child, childProps)
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ const AsyncComp = () => {
 | 
			
		|||
  const hashs = window.location.hash.split('/')
 | 
			
		||||
  const d = hashs[hashs.length - 1]
 | 
			
		||||
  return {
 | 
			
		||||
    component: import(`../components/vc-table/demo/${d}`),
 | 
			
		||||
    component: import(`../components/vc-m-feedback/demo/${d}`),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export default [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue