From 08c0a23628c5e6c3ff4c859433f37b8b6be91385 Mon Sep 17 00:00:00 2001 From: wangxueliang Date: Wed, 4 Apr 2018 12:04:49 +0800 Subject: [PATCH] add vc-input-number --- .../demo/combination-key-format.jsx | 56 + components/vc-input-number/demo/custom.jsx | 43 + components/vc-input-number/demo/precision.jsx | 38 + .../vc-input-number/demo/simple-use-touch.jsx | 44 + components/vc-input-number/demo/simple.jsx | 43 + .../vc-input-number/demo/small-step.jsx | 36 + .../vc-input-number/src/InputHandler.js | 56 +- components/vc-input-number/src/index.js | 1028 ++++++++--------- examples/routes.js | 6 +- package.json | 1 + 10 files changed, 792 insertions(+), 559 deletions(-) create mode 100644 components/vc-input-number/demo/combination-key-format.jsx create mode 100644 components/vc-input-number/demo/custom.jsx create mode 100644 components/vc-input-number/demo/precision.jsx create mode 100644 components/vc-input-number/demo/simple-use-touch.jsx create mode 100644 components/vc-input-number/demo/simple.jsx create mode 100644 components/vc-input-number/demo/small-step.jsx diff --git a/components/vc-input-number/demo/combination-key-format.jsx b/components/vc-input-number/demo/combination-key-format.jsx new file mode 100644 index 000000000..cb3cdd386 --- /dev/null +++ b/components/vc-input-number/demo/combination-key-format.jsx @@ -0,0 +1,56 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + disabled: false, + readOnly: false, + value: 50000, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + toggleDisabled () { + this.disabled = !this.disabled + }, + toggleReadOnly () { + this.readOnly = !this.readOnly + }, + numberWithCommas (x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + }, + format (num) { + return `$ ${this.numberWithCommas(num)} boeing737` + }, + parser (num) { + return num.toString().split(' ')[1].replace(/,*/g, '') + }, + }, + render () { + return ( +
+ +

+ + +

+
+ ) + }, +} diff --git a/components/vc-input-number/demo/custom.jsx b/components/vc-input-number/demo/custom.jsx new file mode 100644 index 000000000..4014ae019 --- /dev/null +++ b/components/vc-input-number/demo/custom.jsx @@ -0,0 +1,43 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + disabled: false, + readOnly: false, + value: 5, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + toggleDisabled () { + this.disabled = !this.disabled + }, + toggleReadOnly () { + this.readOnly = !this.readOnly + }, + }, + render () { + const upHandler = (
x
) + const downHandler = (
V
) + return ( +
+ +
+ ) + }, +} diff --git a/components/vc-input-number/demo/precision.jsx b/components/vc-input-number/demo/precision.jsx new file mode 100644 index 000000000..755a7e3a9 --- /dev/null +++ b/components/vc-input-number/demo/precision.jsx @@ -0,0 +1,38 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + precision: 2, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + changeprecision (e) { + this.precision = parseInt(e.target.value, 10) + }, + }, + render () { + return ( +
+ +

+ precision: + +

+
+ ) + }, +} diff --git a/components/vc-input-number/demo/simple-use-touch.jsx b/components/vc-input-number/demo/simple-use-touch.jsx new file mode 100644 index 000000000..45886effd --- /dev/null +++ b/components/vc-input-number/demo/simple-use-touch.jsx @@ -0,0 +1,44 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + disabled: false, + readOnly: false, + value: 5, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + toggleDisabled () { + this.disabled = !this.disabled + }, + toggleReadOnly () { + this.readOnly = !this.readOnly + }, + }, + render () { + return ( +
+ +

+ + +

+
+ ) + }, +} diff --git a/components/vc-input-number/demo/simple.jsx b/components/vc-input-number/demo/simple.jsx new file mode 100644 index 000000000..0f9a89b81 --- /dev/null +++ b/components/vc-input-number/demo/simple.jsx @@ -0,0 +1,43 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + disabled: false, + readOnly: false, + value: 5, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + toggleDisabled () { + this.disabled = !this.disabled + }, + toggleReadOnly () { + this.readOnly = !this.readOnly + }, + }, + render () { + return ( +
+ +

+ + +

+
+ ) + }, +} diff --git a/components/vc-input-number/demo/small-step.jsx b/components/vc-input-number/demo/small-step.jsx new file mode 100644 index 000000000..b4d4693c2 --- /dev/null +++ b/components/vc-input-number/demo/small-step.jsx @@ -0,0 +1,36 @@ +import InputNumber from '../src/index' +import '../assets/index.less' + +export default { + data () { + return { + value: 0.000000001, + } + }, + methods: { + onChange (value) { + console.log('onChange:', value) + this.value = value + }, + toggleDisabled () { + this.disabled = !this.disabled + }, + toggleReadOnly () { + this.readOnly = !this.readOnly + }, + }, + render () { + return ( +
+ +
+ ) + }, +} diff --git a/components/vc-input-number/src/InputHandler.js b/components/vc-input-number/src/InputHandler.js index 2e079c5ca..15ee5b868 100755 --- a/components/vc-input-number/src/InputHandler.js +++ b/components/vc-input-number/src/InputHandler.js @@ -1,37 +1,33 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Touchable from 'rmc-feedback'; +import PropTypes from '../../_util/vue-types' +import Touchable from '../../vc-m-feedback' -class InputHandler extends Component { - render() { - const { - prefixCls, disabled, onTouchStart, onTouchEnd, - onMouseDown, onMouseUp, onMouseLeave, ...otherProps, - } = this.props; +const InputHandler = { + props: { + prefixCls: PropTypes.string, + disabled: PropTypes.bool, + }, + render () { + const { prefixCls, disabled } = this.$props + const touchableProps = { + props: { + disabled, + activeClassName: `${prefixCls}-handler-active`, + }, + on: this.$listeners, + } + const spanProps = { + attrs: this.$attrs, + } return ( - + + {this.$slots.default} + - ); - } + ) + }, } -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; +export default InputHandler diff --git a/components/vc-input-number/src/index.js b/components/vc-input-number/src/index.js index 293dc9270..6ad44864b 100755 --- a/components/vc-input-number/src/index.js +++ b/components/vc-input-number/src/index.js @@ -1,610 +1,590 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import isNegativeZero from 'is-negative-zero'; -import InputHandler from './InputHandler'; +import PropTypes from '../../_util/vue-types' +import BaseMixin from '../../_util/BaseMixin' +import { initDefaultProps, hasProp } from '../../_util/props-util' +import classNames from 'classnames' +import isNegativeZero from 'is-negative-zero' +import InputHandler from './InputHandler' -function noop() { +function noop () { } -function preventDefault(e) { - e.preventDefault(); +function preventDefault (e) { + e.preventDefault() } -function defaultParser(input) { - return input.replace(/[^\w\.-]+/g, ''); +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; +const SPEED = 200 /** * When click and hold on a button - the delay before auto changin the value. */ -const DELAY = 600; +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; +const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1 +const inputNumberProps = { + 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.any, + downHandler: PropTypes.any, + 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, +} -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 = { +export default { + name: 'InputNumber', + mixins: [BaseMixin], + model: { + prop: 'value', + event: 'change', + }, + props: initDefaultProps(inputNumberProps, { 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; + }), + data () { + let value + if (hasProp(this, 'value')) { + value = this.value } else { - value = props.defaultValue; + value = this.defaultValue } - value = this.toNumber(value); + value = this.toNumber(value) - this.state = { + return { 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), - }); + sValue: value, + focused: this.autoFocus, } - } - - 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(); + }, + mounted () { + this.$nextTick(() => { + this.updatedFunc() + }) + }, + beforeUpdate () { + this.$nextTick(() => { + try { + this.start = this.$refs.inputRef.selectionStart + this.end = this.$refs.inputRef.selectionEnd + } catch (e) { + // Fix error in Chrome: + // Failed to read the 'selectionStart' property from 'HTMLInputElement' + // http://stackoverflow.com/q/21177489/3040605 } - 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)) { + }) + }, + updated () { + this.$nextTick(() => { + this.updatedFunc() + }) + }, + beforeDestroy () { + this.stop() + }, + watch: { + value (val) { + const value = this.focused + ? val : this.getValidValue(val, this.min, this.max) this.setState({ - value: newValue, - inputValue: this.toPrecisionAsStep(v), - }, callback); - } else { - // always set input value same as value + sValue: val, + inputValue: this.inputting ? value : this.toPrecisionAsStep(value), + }) + }, + }, + methods: { + updatedFunc () { + if (!this.pressingUpOrDown) { + return + } + if (this.focusOnUpDown && this.focused) { + const selectionRange = this.$refs.inputRef.setSelectionRange + if (selectionRange && + typeof selectionRange === 'function' && + this.start !== undefined && + this.end !== undefined) { + this.$refs.inputRef.setSelectionRange(this.start, this.end) + } else { + this.focus() + } + this.pressingUpOrDown = false + } + }, + 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() + } + this.$emit('keydown', e, ...args) + }, + onKeyUp (e, ...args) { + this.stop() + this.$emit('keyup', e, ...args) + }, + onChange (e) { + if (this.focused) { + this.inputting = true + } + const input = this.parser(this.getValueFromEvent(e)) + this.setState({ inputValue: input }) + this.$emit('change', this.toNumberWhenUserInput(input)) // valid number or invalid string + }, + onFocus (...args) { 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; + focused: true, + }) + this.$emit('focus', ...args) + }, + onBlur (e, ...args) { + this.inputting = false + this.setState({ + focused: false, + }) + const value = this.getCurrentValidValue(this.inputValue) + // todo + // e.persist() // fix https://github.com/react-component/input-number/issues/51 + this.setValue(value, () => { + this.$emit('blur', e, ...args) + }) + }, + getCurrentValidValue (value) { + let val = value + if (val === '') { + val = '' + } else if (!this.isNotCompleteNumber(val)) { + val = this.getValidValue(val) + } else { + val = this.sValue + } + 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.min, max = this.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.sValue || + `${newValue}` !== `${this.inputValue}` // https://github.com/ant-design/ant-design/issues/7363 + if (!hasProp(this, 'value')) { + this.setState({ + sValue: newValue, + inputValue: this.toPrecisionAsStep(v), + }, callback) + } else { + // always set input value same as value + this.setState({ + inputValue: this.toPrecisionAsStep(this.sValue), + }, callback) + } + if (changed) { + this.$emit('change', newValue) + } + }, + getPrecision (value) { + if (hasProp(this, 'precision')) { + return this.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 (hasProp(this, 'precision')) { + return this.precision + } + const { step } = this + 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.$refs.inputRef.focus() + }, + blur () { + this.$refs.inputRef.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.formatter) { + return this.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 (hasProp(this, 'precision')) { + return Number(Number(num).toFixed(this.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.focused) { + return num + } + return this.toNumber(num) + }, + upStep (val, rat) { + const { step, min } = this + 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 + 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) + }, + stepFn (type, e, ratio = 1, recursive) { + this.stop() + if (e) { + // e.persist() + e.preventDefault() + } + if (this.disabled) { + return + } + const { max, min } = this + const value = this.getCurrentValidValue(this.inputValue) || 0 + if (this.isNotCompleteNumber(value)) { + return + } + let val = this[`${type}Step`](value, ratio) + const outOfRange = val > max || val < min + if (val > max) { + val = max + } else if (val < min) { + val = 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.stepFn('down', e, ratio, recursive) + }, + up (e, ratio, recursive) { + this.pressingUpOrDown = true + this.stepFn('up', e, ratio, recursive) + }, + handleInputClick () { + this.$emit('click') + }, + }, + render () { + const { prefixCls, disabled, readOnly, useTouch } = this.$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`; + [`${prefixCls}-focused`]: this.focused, + }) + let upDisabledClass = '' + let downDisabledClass = '' + const { sValue } = this + if (sValue || sValue === 0) { + if (!isNaN(sValue)) { + const val = Number(sValue) + if (val >= this.max) { + upDisabledClass = `${prefixCls}-handler-up-disabled` } - if (val <= props.min) { - downDisabledClass = `${prefixCls}-handler-down-disabled`; + if (val <= this.min) { + downDisabledClass = `${prefixCls}-handler-down-disabled` } } else { - upDisabledClass = `${prefixCls}-handler-up-disabled`; - downDisabledClass = `${prefixCls}-handler-down-disabled`; + upDisabledClass = `${prefixCls}-handler-up-disabled` + downDisabledClass = `${prefixCls}-handler-down-disabled` } } - const editable = !props.readOnly && !props.disabled; + const editable = !this.readOnly && !this.disabled // focus state, show input value // unfocus state, show valid value - let inputDisplayValue; - if (this.state.focused) { - inputDisplayValue = this.state.inputValue; + let inputDisplayValue + if (this.focused) { + inputDisplayValue = this.inputValue } else { - inputDisplayValue = this.toPrecisionAsStep(this.state.value); + inputDisplayValue = this.toPrecisionAsStep(this.sValue) } if (inputDisplayValue === undefined || inputDisplayValue === null) { - inputDisplayValue = ''; + inputDisplayValue = '' } - let upEvents; - let downEvents; + let upEvents + let downEvents if (useTouch) { upEvents = { - onTouchStart: (editable && !upDisabledClass) ? this.up : noop, - onTouchEnd: this.stop, - }; + touchstart: (editable && !upDisabledClass) ? this.up : noop, + touchend: this.stop, + } downEvents = { - onTouchStart: (editable && !downDisabledClass) ? this.down : noop, - onTouchEnd: this.stop, - }; + touchstart: (editable && !downDisabledClass) ? this.down : noop, + touchend: this.stop, + } } else { upEvents = { - onMouseDown: (editable && !upDisabledClass) ? this.up : noop, - onMouseUp: this.stop, - onMouseLeave: this.stop, - }; + mousedown: (editable && !upDisabledClass) ? this.up : noop, + mouseup: this.stop, + mouseleave: this.stop, + } downEvents = { - onMouseDown: (editable && !downDisabledClass) ? this.down : noop, - onMouseUp: this.stop, - onMouseLeave: this.stop, - }; + mousedown: (editable && !downDisabledClass) ? this.down : noop, + mouseup: this.stop, + mouseleave: this.stop, + } + } + const inputDisplayValueFormat = this.formatWrapper(inputDisplayValue) + const isUpDisabled = !!upDisabledClass || disabled || readOnly + const isDownDisabled = !!downDisabledClass || disabled || readOnly + const contentProps = { + on: this.$listeners, + class: classes, + } + const upHandlerProps = { + props: { + disabled: isUpDisabled, + prefixCls, + }, + attrs: { + unselectable: 'unselectable', + role: 'button', + 'aria-label': 'Increase Value', + 'aria-disabled': !!isUpDisabled, + }, + class: `${prefixCls}-handler ${prefixCls}-handler-up ${upDisabledClass}`, + on: upEvents, + ref: 'up', + } + const downHandlerProps = { + props: { + disabled: isDownDisabled, + prefixCls, + }, + attrs: { + unselectable: 'unselectable', + role: 'button', + 'aria-label': 'Decrease Value', + 'aria-disabled': !!isDownDisabled, + }, + class: `${prefixCls}-handler ${prefixCls}-handler-down ${downDisabledClass}`, + on: downEvents, + ref: 'down', } - const inputDisplayValueFormat = this.formatWrapper(inputDisplayValue); - const isUpDisabled = !!upDisabledClass || disabled || readOnly; - const isDownDisabled = !!downDisabledClass || disabled || readOnly; // ref for test return (
-
+
- {this.props.upHandler || } - {this.props.downHandler || }
- ); - } + ) + }, } diff --git a/examples/routes.js b/examples/routes.js index 4b4388054..3d6bac0a1 100644 --- a/examples/routes.js +++ b/examples/routes.js @@ -3,11 +3,7 @@ const AsyncComp = () => { const hashs = window.location.hash.split('/') const d = hashs[hashs.length - 1] return { -<<<<<<< HEAD - component: import(`../components/vc-m-feedback/demo/${d}`), -======= - component: import(`../components/table/demo/${d}`), ->>>>>>> 5d2271a131c74d672cc0cfada07e256752160b41 + component: import(`../components/vc-input-number/demo/${d}`), } } export default [ diff --git a/package.json b/package.json index c5ae2dd47..9b08e686a 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "dom-scroll-into-view": "^1.2.1", "enquire.js": "^2.1.6", "eslint-plugin-vue": "^3.13.0", + "is-negative-zero": "^2.0.0", "lodash": "^4.17.5", "moment": "^2.21.0", "omit.js": "^1.0.0",