From 6b6bacc7a122507ae656b3da2835ceeb09b4ce44 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 20 Feb 2020 17:45:18 +0800 Subject: [PATCH] feat: update input-number --- build/config.js | 2 +- components/_util/BaseMixin.js | 1 + .../__tests__/__snapshots__/demo.test.js.snap | 16 +- .../input-number/__tests__/index.test.js | 27 +- components/input-number/index.en-US.md | 9 +- components/input-number/index.jsx | 2 +- components/input-number/index.zh-CN.md | 7 +- .../demo/combination-key-format.jsx | 1 + .../vc-input-number/src/InputHandler.js | 1 + components/vc-input-number/src/index.js | 257 ++++++++++-------- 10 files changed, 185 insertions(+), 138 deletions(-) diff --git a/build/config.js b/build/config.js index a75a866b4..4aa11fe7e 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'input', // dev components + componentName: 'input-number', // dev components }, }; diff --git a/components/_util/BaseMixin.js b/components/_util/BaseMixin.js index fefcf18df..d15669e7d 100644 --- a/components/_util/BaseMixin.js +++ b/components/_util/BaseMixin.js @@ -19,6 +19,7 @@ export default { // Object.assign(newState, this.getDerivedStateFromProps(getOptionProps(this), { ...this.$data, ...newState }, true) || {}) // } Object.assign(this.$data, newState); + this.$forceUpdate(); this.$nextTick(() => { callback && callback(); }); diff --git a/components/input-number/__tests__/__snapshots__/demo.test.js.snap b/components/input-number/__tests__/__snapshots__/demo.test.js.snap index 54039b601..e716f677f 100644 --- a/components/input-number/__tests__/__snapshots__/demo.test.js.snap +++ b/components/input-number/__tests__/__snapshots__/demo.test.js.snap @@ -4,7 +4,7 @@ exports[`renders ./components/input-number/demo/basic.md correctly 1`] = `
-
+
当前值:3
@@ -13,7 +13,7 @@ exports[`renders ./components/input-number/demo/basic.md correctly 1`] = ` exports[`renders ./components/input-number/demo/digit.md correctly 1`] = `
-
+
`; @@ -21,7 +21,7 @@ exports[`renders ./components/input-number/demo/disabled.md correctly 1`] = `
-
+
@@ -31,11 +31,11 @@ exports[`renders ./components/input-number/demo/formatter.md correctly 1`] = `
-
+
-
+
`; @@ -44,15 +44,15 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
-
+
-
+
-
+
`; diff --git a/components/input-number/__tests__/index.test.js b/components/input-number/__tests__/index.test.js index b47c2658e..34ae712a0 100644 --- a/components/input-number/__tests__/index.test.js +++ b/components/input-number/__tests__/index.test.js @@ -1,4 +1,29 @@ +import { mount } from '@vue/test-utils'; import InputNumber from '..'; import focusTest from '../../../tests/shared/focusTest'; +import mountTest from '../../../tests/shared/mountTest'; -focusTest(InputNumber); +describe('InputNumber', () => { + focusTest(InputNumber); + mountTest(InputNumber); + + // https://github.com/ant-design/ant-design/issues/13896 + it('should return null when blur a empty input number', () => { + const onChange = jest.fn(); + const wrapper = mount( + { + render() { + return ; + }, + }, + { + sync: false, + }, + ); + wrapper.find('input').element.value = ''; + wrapper.find('input').trigger('input'); + expect(onChange).toHaveBeenLastCalledWith(''); + wrapper.find('input').trigger('blur'); + expect(onChange).toHaveBeenLastCalledWith(null); + }); +}); diff --git a/components/input-number/index.en-US.md b/components/input-number/index.en-US.md index af1a303c3..5e9609a58 100644 --- a/components/input-number/index.en-US.md +++ b/components/input-number/index.en-US.md @@ -11,15 +11,16 @@ | parser | Specifies the value extracted from formatter | function( string): number | - | | precision | precision of input value | number | - | | decimalSeparator | decimal separator | string | - | -| size | width of input box | string | - | +| size | height of input box | string | - | | step | The number to which the current value is increased or decreased. It can be an integer or decimal. | number\|string | 1 | | value(v-model) | current value | number | | ### events -| Events Name | Description | Arguments | -| --- | --- | --- | -| change | The callback triggered when the value is changed. | function(value: number \| string) | | +| Events Name | Description | Arguments | Version | +| --- | --- | --- | --- | +| change | The callback triggered when the value is changed. | function(value: number \| string) | | | +| pressEnter | The callback function that is triggered when Enter key is pressed. | function(e) | | 1.5.0 | ## Methods diff --git a/components/input-number/index.jsx b/components/input-number/index.jsx index 3b12358a3..22784e228 100644 --- a/components/input-number/index.jsx +++ b/components/input-number/index.jsx @@ -12,7 +12,7 @@ export const InputNumberProps = { max: PropTypes.number, value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), step: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - defaultValue: PropTypes.number, + defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), tabIndex: PropTypes.number, disabled: PropTypes.bool, size: PropTypes.oneOf(['large', 'small', 'default']), diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index 5aaa7e732..a6bd5d3c9 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -19,9 +19,10 @@ ### 事件 -| 事件名称 | 说明 | 回调参数 | -| -------- | -------- | --------------------------------- | -| change | 变化回调 | Function(value: number \| string) | | +| 事件名称 | 说明 | 回调参数 | 版本 | +| ---------- | -------------- | --------------------------------- | ---- | +| change | 变化回调 | Function(value: number \| string) | | +| pressEnter | 按下回车的回调 | function(e) | | ## 方法 diff --git a/components/vc-input-number/demo/combination-key-format.jsx b/components/vc-input-number/demo/combination-key-format.jsx index 958c0e266..736cecef1 100644 --- a/components/vc-input-number/demo/combination-key-format.jsx +++ b/components/vc-input-number/demo/combination-key-format.jsx @@ -37,6 +37,7 @@ export default { return (
value !== undefined && value !== null; +const isEqual = (oldValue, newValue) => + newValue === oldValue || + (typeof newValue === 'number' && + typeof oldValue === 'number' && + isNaN(newValue) && + isNaN(oldValue)); + const inputNumberProps = { value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), @@ -73,7 +80,7 @@ const inputNumberProps = { }; export default { - name: 'InputNumber', + name: 'VCInputNumber', mixins: [BaseMixin], model: { prop: 'value', @@ -90,17 +97,18 @@ export default { autoComplete: 'off', }), data() { + const props = getOptionProps(this); + this.prevProps = { ...props }; let value; - if (hasProp(this, 'value')) { + if ('value' in props) { value = this.value; } else { value = this.defaultValue; } - value = this.toNumber(value); - + const validValue = this.getValidValue(this.toNumber(value)); return { - inputValue: this.toPrecisionAsStep(value), - sValue: value, + inputValue: this.toPrecisionAsStep(validValue), + sValue: validValue, focused: this.autoFocus, }; }, @@ -112,19 +120,57 @@ export default { 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 - } - }); - }, updated() { + const { value, max, min } = this.$props; + const { focused } = this.$data; + const { prevProps } = this; + const props = getOptionProps(this); + // Don't trigger in componentDidMount + if (prevProps) { + if ( + !isEqual(prevProps.value, value) || + !isEqual(prevProps.max, max) || + !isEqual(prevProps.min, min) + ) { + const validValue = focused ? value : this.getValidValue(value); + let nextInputValue; + if (this.pressingUpOrDown) { + nextInputValue = validValue; + } else if (this.inputting) { + nextInputValue = this.rawInput; + } else { + nextInputValue = this.toPrecisionAsStep(validValue); + } + this.setState({ + // eslint-disable-line + sValue: validValue, + inputValue: nextInputValue, + }); + } + + // Trigger onChange when max or min change + // https://github.com/ant-design/ant-design/issues/11574 + const nextValue = 'value' in props ? value : this.sValue; + // ref: null < 20 === true + // https://github.com/ant-design/ant-design/issues/14277 + if ( + 'max' in props && + prevProps.max !== max && + typeof nextValue === 'number' && + nextValue > max + ) { + this.$emit('change', max); + } + if ( + 'min' in props && + prevProps.min !== min && + typeof nextValue === 'number' && + nextValue < min + ) { + this.$emit('change', min); + } + } + this.prevProps = { ...props }; this.$nextTick(() => { this.updatedFunc(); }); @@ -132,33 +178,6 @@ export default { beforeDestroy() { this.stop(); }, - watch: { - value(val) { - const value = this.focused ? val : this.getValidValue(val, this.min, this.max); - this.setState({ - sValue: val, - inputValue: this.inputting ? value : this.toPrecisionAsStep(value), - }); - }, - max(val) { - const props = getOptionProps(this); - // Trigger onChange when max or min change - // https://github.com/ant-design/ant-design/issues/11574 - const nextValue = 'value' in props ? props.value : this.sValue; - // ref: null < 20 === true - // https://github.com/ant-design/ant-design/issues/14277 - if (typeof nextValue === 'number' && nextValue > val) { - this.__emit('change', val); - } - }, - min(val) { - const props = getOptionProps(this); - const nextValue = 'value' in props ? props.value : this.sValue; - if (typeof nextValue === 'number' && nextValue < val) { - this.__emit('change', val); - } - }, - }, methods: { updatedFunc() { const inputElem = this.$refs.inputRef; @@ -231,6 +250,8 @@ export default { const ratio = this.getRatio(e); this.down(e, ratio); this.stop(); + } else if (e.keyCode === KeyCode.ENTER) { + this.$emit('pressEnter', e); } // Trigger user key down this.recordCursorPosition(); @@ -248,9 +269,9 @@ export default { 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 + this.rawInput = this.parser(this.getValueFromEvent(e)); + this.setState({ inputValue: this.rawInput }); + this.$emit('change', this.toNumber(this.rawInput)); // valid number or invalid string }, onFocus(...args) { this.setState({ @@ -258,17 +279,20 @@ export default { }); this.$emit('focus', ...args); }, - onBlur(e, ...args) { + onBlur(...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); - }); + const newValue = this.setValue(value); + if (this.$listeners.blur) { + const originValue = this.$refs.inputRef.value; + const inputValue = this.getInputDisplayValue({ focused: false, sValue: newValue }); + this.$refs.inputRef.value = inputValue; + this.$emit('blur', ...args); + this.$refs.inputRef.value = originValue; + } }, getCurrentValidValue(value) { let val = value; @@ -317,8 +341,14 @@ export default { }, setValue(v, callback) { // trigger onChange + const { precision } = this.$props; const newValue = this.isNotCompleteNumber(parseFloat(v, 10)) ? null : parseFloat(v, 10); - const changed = newValue !== this.sValue || `${newValue}` !== `${this.inputValue}`; // https://github.com/ant-design/ant-design/issues/7363 + const { sValue: value = null, inputValue = null } = this.$data; + // https://github.com/ant-design/ant-design/issues/7363 + // https://github.com/ant-design/ant-design/issues/16622 + const newValueInString = + typeof newValue === 'number' ? newValue.toFixed(precision) : `${newValue}`; + const changed = newValue !== value || newValueInString !== `${inputValue}`; if (!hasProp(this, 'value')) { this.setState( { @@ -339,6 +369,7 @@ export default { if (changed) { this.$emit('change', newValue); } + return newValue; }, getPrecision(value) { if (isValidProps(this.precision)) { @@ -357,7 +388,7 @@ export default { // step={1.0} value={1.51} // press + // then value should be 2.51, rather than 2.5 - // if this.props.precision is undefined + // if this.$props.precision is undefined // https://github.com/react-component/input-number/issues/39 getMaxPrecision(currentValue, ratio = 1) { if (isValidProps(this.precision)) { @@ -376,8 +407,8 @@ export default { const precision = this.getMaxPrecision(currentValue, ratio); return Math.pow(10, precision); }, - getInputDisplayValue() { - const { focused, inputValue, sValue } = this; + getInputDisplayValue(state) { + const { focused, inputValue, sValue } = state || this.$data; let inputDisplayValue; if (focused) { inputDisplayValue = inputValue; @@ -389,7 +420,14 @@ export default { inputDisplayValue = ''; } - return inputDisplayValue; + let inputDisplayValueFormat = this.formatWrapper(inputDisplayValue); + if (isValidProps(this.$props.decimalSeparator)) { + inputDisplayValueFormat = inputDisplayValueFormat + .toString() + .replace('.', this.$props.decimalSeparator); + } + + return inputDisplayValueFormat; }, recordCursorPosition() { // Record position @@ -407,7 +445,12 @@ export default { } }, fixCaret(start, end) { - if (start === undefined || end === undefined || !this.input || !this.input.value) { + if ( + start === undefined || + end === undefined || + !this.$refs.inputRef || + !this.$refs.inputRef.value + ) { return; } @@ -433,6 +476,14 @@ export default { if (index === -1) return false; + const prevCursorPos = this.cursorBefore.length; + if ( + this.lastKeyCode === KeyCode.DELETE && + this.cursorBefore.charAt(prevCursorPos - 1) === str[0] + ) { + this.fixCaret(prevCursorPos, prevCursorPos); + return true; + } if (index + str.length === fullStr.length) { this.fixCaret(index, index); @@ -463,9 +514,6 @@ export default { 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); } @@ -476,9 +524,6 @@ export default { return num; } const precision = Math.abs(this.getMaxPrecision(num)); - if (precision === 0) { - return num.toString(); - } if (!isNaN(precision)) { return Number(num).toFixed(precision); } @@ -494,48 +539,36 @@ export default { ); }, toNumber(num) { - if (this.isNotCompleteNumber(num)) { + const { precision, autoFocus } = this.$props; + const { focused = autoFocus } = this; + // num.length > 16 => This is to prevent input of large numbers + const numberIsTooLarge = num && num.length > 16 && focused; + if (this.isNotCompleteNumber(num) || numberIsTooLarge) { return num; } - if (isValidProps(this.precision)) { - return Number(Number(num).toFixed(this.precision)); + if (isValidProps(precision)) { + return Math.round(num * Math.pow(10, precision)) / Math.pow(10, 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 { step } = 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; - } + const result = ( + (precisionFactor * val + precisionFactor * step * rat) / + precisionFactor + ).toFixed(precision); return this.toNumber(result); }, downStep(val, rat) { - const { step, min } = this; + const { step } = 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; - } + const result = ( + (precisionFactor * val - precisionFactor * step * rat) / + precisionFactor + ).toFixed(precision); return this.toNumber(result); }, stepFn(type, e, ratio = 1, recursive) { @@ -627,16 +660,7 @@ export default { // focus state, show input value // unfocus state, show valid value - let inputDisplayValue; - if (this.focused) { - inputDisplayValue = this.inputValue; - } else { - inputDisplayValue = this.toPrecisionAsStep(this.sValue); - } - - if (inputDisplayValue === undefined || inputDisplayValue === null) { - inputDisplayValue = ''; - } + const inputDisplayValue = this.getInputDisplayValue(); let upEvents; let downEvents; @@ -661,12 +685,6 @@ export default { mouseleave: this.stop, }; } - let inputDisplayValueFormat = this.formatWrapper(inputDisplayValue); - if (isValidProps(this.decimalSeparator)) { - inputDisplayValueFormat = inputDisplayValueFormat - .toString() - .replace('.', this.decimalSeparator); - } const isUpDisabled = !!upDisabledClass || disabled || readOnly; const isDownDisabled = !!downDisabledClass || disabled || readOnly; const { @@ -733,14 +751,12 @@ export default { )}
-
+