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`] = `
@@ -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 {
)}
-