From bec5a08368f90d4bf0f053ae84dd5e9e56989c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 22 Feb 2021 22:07:19 +0800 Subject: [PATCH] feat: InputNumber support stringMode (#29373) * add input-number demo * update snapshot * update demo * docs: Update desc * update doc * clean up React useLayoutEffect warning * fix ts definition * re-trigger * update rc-uitl version * bump rc-input-number * update * update * fix ts * update demo snapshot * update doc * update demo --- .../__snapshots__/components.test.js.snap | 36 +-- .../__tests__/__snapshots__/demo.test.js.snap | 26 +- .../__tests__/__snapshots__/demo.test.js.snap | 301 ++++++++++++------ .../__snapshots__/index.test.js.snap | 6 +- .../input-number/__tests__/index.test.js | 3 +- components/input-number/demo/digit.md | 25 +- components/input-number/demo/keyboard.md | 44 ++- components/input-number/demo/out-of-range.md | 38 +++ components/input-number/index.en-US.md | 11 + components/input-number/index.tsx | 41 +-- components/input-number/index.zh-CN.md | 11 + components/input-number/style/index.less | 7 + .../__tests__/__snapshots__/demo.test.js.snap | 12 +- .../__tests__/__snapshots__/demo.test.js.snap | 6 +- package.json | 4 +- typings/custom-typings.d.ts | 2 - 16 files changed, 355 insertions(+), 218 deletions(-) create mode 100644 components/input-number/demo/out-of-range.md diff --git a/components/config-provider/__tests__/__snapshots__/components.test.js.snap b/components/config-provider/__tests__/__snapshots__/components.test.js.snap index e213be63f7..305ab6da70 100644 --- a/components/config-provider/__tests__/__snapshots__/components.test.js.snap +++ b/components/config-provider/__tests__/__snapshots__/components.test.js.snap @@ -14108,6 +14108,7 @@ exports[`ConfigProvider components InputNumber configProvider 1`] = ` class="config-input-number-handler-wrap" >
@@ -245,9 +247,9 @@ Array [ class="ant-input-number-handler-wrap" > @@ -272,9 +274,9 @@ Array [
@@ -309,8 +311,6 @@ Array [ autocomplete="off" class="ant-input-number-input" disabled="" - max="10" - min="1" role="spinbutton" step="1" value="3" @@ -341,6 +341,7 @@ Array [ class="ant-input-number-handler-wrap" >
- - + + - - - + + - +
+
+ +
-
- -
- , +
+ +
+ +`; + +exports[`renders ./components/input-number/demo/out-of-range.md correctly 1`] = ` +
+
+
+
+ + + + + + + + + + +
+
+ +
+
+
+
-
, -] +
+ `; exports[`renders ./components/input-number/demo/size.md correctly 1`] = ` @@ -594,6 +709,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = ` class="ant-input-number-handler-wrap" > { const onChange = jest.fn(); const wrapper = mount(); wrapper.find('input').simulate('change', { target: { value: '' } }); - expect(onChange).toHaveBeenLastCalledWith(''); + expect(onChange).not.toHaveBeenCalled(); + wrapper.find('input').simulate('blur'); expect(onChange).toHaveBeenLastCalledWith(null); }); diff --git a/components/input-number/demo/digit.md b/components/input-number/demo/digit.md index 42fcfd005e..7a526fe434 100644 --- a/components/input-number/demo/digit.md +++ b/components/input-number/demo/digit.md @@ -1,24 +1,35 @@ --- order: 3 title: - zh-CN: 小数 - en-US: Decimals + zh-CN: 高精度小数 + en-US: High precision decimals --- ## zh-CN -和原生的数字输入框一样,value 的精度由 step 的小数位数决定。 +通过 `stringMode` 开启高精度小数支持,`onChange` 事件将返回 string 类型。对于旧版游览器,你需要 BigInt polyfill。 ## en-US -A numeric-only input box whose values can be increased or decreased using a decimal step. The number of decimals (also known as precision) is determined by the step prop. +Use `stringMode` to support high precision decimals support. `onChange` will return string value instead. You need polyfill of BigInt if browser not support. -```jsx +```tsx import { InputNumber } from 'antd'; -function onChange(value) { +function onChange(value: string) { console.log('changed', value); } -ReactDOM.render(, mountNode); +ReactDOM.render( + + style={{ width: 200 }} + defaultValue="1" + min="0" + max="10" + step="0.00000000000001" + onChange={onChange} + stringMode + />, + mountNode, +); ``` diff --git a/components/input-number/demo/keyboard.md b/components/input-number/demo/keyboard.md index 2680f8496e..d1ce9235ec 100644 --- a/components/input-number/demo/keyboard.md +++ b/components/input-number/demo/keyboard.md @@ -13,33 +13,25 @@ title: Control keyboard behavior by `keyboard`. -```jsx -import { InputNumber, Button } from 'antd'; +```tsx +import { InputNumber, Checkbox, Space } from 'antd'; -class App extends React.Component { - state = { - keyboard: true, - }; - - toggle = () => { - this.setState({ - keyboard: !this.state.keyboard, - }); - }; - - render() { - return ( - <> - -
- -
- - ); - } -} +const App = () => { + const [keyboard, setKeyboard] = React.useState(true); + return ( + + + { + setKeyboard(!keyboard); + }} + checked={keyboard} + > + Toggle keyboard + + + ); +}; ReactDOM.render(, mountNode); ``` diff --git a/components/input-number/demo/out-of-range.md b/components/input-number/demo/out-of-range.md new file mode 100644 index 0000000000..428d6145f7 --- /dev/null +++ b/components/input-number/demo/out-of-range.md @@ -0,0 +1,38 @@ +--- +order: 6 +title: + zh-CN: 超出边界 + en-US: Out of range +--- + +## zh-CN + +当通过受控将 `value` 超出边界时,提供警告样式。 + +## en-US + +Show warning style when `value` is out of range by control. + +```tsx +import { InputNumber, Button, Space } from 'antd'; + +const Demo = () => { + const [value, setValue] = React.useState('99'); + + return ( + + + + + ); +}; + +ReactDOM.render(, mountNode); +``` diff --git a/components/input-number/index.en-US.md b/components/input-number/index.en-US.md index 44f2b724b5..b7cc2627e7 100644 --- a/components/input-number/index.en-US.md +++ b/components/input-number/index.en-US.md @@ -29,6 +29,7 @@ When a numeric value needs to be provided. | readOnly | If readonly the input | boolean | false | - | | size | The height of input box | `large` \| `middle` \| `small` | - | - | | step | The number to which the current value is increased or decreased. It can be an integer or decimal | number \| string | 1 | - | +| stringMode | Set value as string to support high precision decimals. Will return string value by `onChange` | boolean | false | 4.13.0 | | value | The current value | number | - | - | | onChange | The callback triggered when the value is changed | function(value: number \| string \| null) | - | - | | onPressEnter | The callback function that is triggered when Enter key is pressed | function(e) | - | - | @@ -44,3 +45,13 @@ When a numeric value needs to be provided. ## Notes Per issues [#21158](https://github.com/ant-design/ant-design/issues/21158), [#17344](https://github.com/ant-design/ant-design/issues/17344), [#9421](https://github.com/ant-design/ant-design/issues/9421), and [documentation about inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number#Using_number_inputs), it appears this community does not support native inclusion of the `type="number"` in the `` attributes, so please feel free to include it as needed, and be aware that it is heavily suggested that server side validation be utilized, as client side validation can be edited by power users. + +## FAQ + +### Why `value` can exceed `min` or `max` in control? + +Developer handle data by their own in control. It will make data out of sync if InputNumber change display value. It also cause potential data issues when use in form. + +### Why dynamic change `min` or `max` which makes `value` out of range will not trigger `onChange`? + +`onChange` is user trigger event. Auto trigger will makes form lib can not detect data modify source. diff --git a/components/input-number/index.tsx b/components/input-number/index.tsx index 4ed6e354c2..5d77e7b6bf 100644 --- a/components/input-number/index.tsx +++ b/components/input-number/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import classNames from 'classnames'; -import RcInputNumber from 'rc-input-number'; +import RcInputNumber, { InputNumberProps as RcInputNumberProps } from 'rc-input-number'; import UpOutlined from '@ant-design/icons/UpOutlined'; import DownOutlined from '@ant-design/icons/DownOutlined'; @@ -8,37 +8,16 @@ import { ConfigContext } from '../config-provider'; import { Omit } from '../_util/type'; import SizeContext, { SizeType } from '../config-provider/SizeContext'; -// omitting this attrs because they conflicts with the ones defined in InputNumberProps -export type OmitAttrs = 'defaultValue' | 'onChange' | 'size'; +type ValueType = string | number; -export interface InputNumberProps - extends Omit, OmitAttrs> { +export interface InputNumberProps + extends Omit, 'size'> { prefixCls?: string; - min?: number; - max?: number; - value?: number; - step?: number | string; - defaultValue?: number; - tabIndex?: number; - onChange?: (value: number | string | undefined | null) => void; - disabled?: boolean; - readOnly?: boolean; size?: SizeType; bordered?: boolean; - formatter?: (value: number | string | undefined) => string; - parser?: (displayValue: string | undefined) => number | string; - decimalSeparator?: string; - placeholder?: string; - style?: React.CSSProperties; - className?: string; - name?: string; - id?: string; - precision?: number; - onPressEnter?: React.KeyboardEventHandler; - onStep?: (value: number, info: { offset: number; type: 'up' | 'down' }) => void; } -const InputNumber = React.forwardRef((props, ref) => { +const InputNumber = React.forwardRef((props, ref) => { const { getPrefixCls, direction } = React.useContext(ConfigContext); const size = React.useContext(SizeContext); @@ -80,8 +59,8 @@ const InputNumber = React.forwardRef((props, ref) => ); }); -InputNumber.defaultProps = { - step: 1, -}; - -export default InputNumber; +export default InputNumber as (( + props: React.PropsWithChildren> & { + ref?: React.Ref; + }, +) => React.ReactElement) & { displayName?: string }; diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index e7d603c941..ee6153fd9c 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -32,6 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/XOS8qZ0kU/InputNumber.svg | readOnly | 只读 | boolean | false | - | | size | 输入框大小 | `large` \| `middle` \| `small` | - | - | | step | 每次改变步数,可以为小数 | number \| string | 1 | - | +| stringMode | 字符值模式,开启后支持高精度小数。同时 `onChange` 将返回 string 类型 | boolean | false | 4.13.0 | | value | 当前值 | number | - | - | | onChange | 变化回调 | function(value: number \| string \| null) | - | - | | onPressEnter | 按下回车的回调 | function(e) | - | - | @@ -43,3 +44,13 @@ cover: https://gw.alipayobjects.com/zos/alicdn/XOS8qZ0kU/InputNumber.svg | ------- | -------- | | blur() | 移除焦点 | | focus() | 获取焦点 | + +## FAQ + +### 为何受控模式下,`value` 可以超出 `min` 和 `max` 范围? + +在受控模式下,开发者可能自行存储相关数据。如果组件将数据约束回范围内,会导致展示数据与实际存储数据不一致的情况。这使得一些如表单场景存在潜在的数据问题。 + +### 为何动态修改 `min` 和 `max` 让 `value` 超出范围不会触发 `onChange` 事件? + +`onChange` 事件为用户触发事件,自行触发会导致表单库误以为变更来自用户操作。我们以错误样式展示超出范围的数值。 diff --git a/components/input-number/style/index.less b/components/input-number/style/index.less index 1719737019..9f6fe65bb9 100644 --- a/components/input-number/style/index.less +++ b/components/input-number/style/index.less @@ -197,6 +197,13 @@ &-borderless { box-shadow: none; } + + // ===================== Out Of Range ===================== + &-out-of-range { + input { + color: @error-color; + } + } } @import './rtl'; diff --git a/components/input/__tests__/__snapshots__/demo.test.js.snap b/components/input/__tests__/__snapshots__/demo.test.js.snap index e8440be18f..f483e83dbc 100644 --- a/components/input/__tests__/__snapshots__/demo.test.js.snap +++ b/components/input/__tests__/__snapshots__/demo.test.js.snap @@ -360,6 +360,7 @@ Array [ class="ant-input-number-handler-wrap" >