Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Then open `http://localhost:8000`.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| allowClear | `boolean \| { clearIcon?: ReactNode; disabled?: boolean; label?: string }` | `false` | Show a clear button, optionally with a custom icon, disabled state, or accessible label. |
| autoFocus | `boolean` | `false` | Focus the input when mounted. |
| changeOnBlur | `boolean` | `true` | Commit value changes on blur. |
| changeOnWheel | `boolean` | `false` | Allow value changes from the mouse wheel. |
Expand Down Expand Up @@ -95,6 +96,7 @@ Then open `http://localhost:8000`.
| upHandler | `ReactNode` | - | Custom increment control. |
| value | `T \| null` | - | Controlled value. |
| onChange | `(value: T \| null) => void` | - | Triggered when the committed value changes. |
| onClear | `() => void` | - | Triggered when the clear button is clicked. |
| onInput | `(text: string) => void` | - | Triggered when the raw input text changes. |
| onPressEnter | `React.KeyboardEventHandler<HTMLInputElement>` | - | Triggered when Enter is pressed. |
| onStep | `(value: T, info: { offset: number \| string; type: 'up' \| 'down'; emitter: 'handler' \| 'keyboard' \| 'wheel' }) => void` | - | Triggered when the value changes by step. |
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ npm start

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| allowClear | `boolean \| { clearIcon?: ReactNode; disabled?: boolean; label?: string }` | `false` | 显示清除按钮,可自定义图标、禁用清除操作或设置无障碍标签。 |
| autoFocus | `boolean` | `false` | 安装后聚焦输入。 |
| changeOnBlur | `boolean` | `true` | 提交模糊值的变化。 |
| changeOnWheel | `boolean` | `false` | 允许通过鼠标滚轮更改值。 |
Expand Down Expand Up @@ -95,6 +96,7 @@ npm start
| upHandler | `ReactNode` | - | 自定义增量控制。 |
| value | `T \| null` | - | 受控值。 |
| onChange | `(value: T \| null) => void` | - | 当提交的值改变时触发。 |
| onClear | `() => void` | - | 点击清除按钮时触发。 |
| onInput | `(text: string) => void` | - | 当原始输入文本更改时触发。 |
| onPressEnter | `React.KeyboardEventHandler<HTMLInputElement>` | - | 当按下 Enter 时触发。 |
| onStep | `(value: T, info: { offset: number \| string; type: 'up' \| 'down'; emitter: 'handler' \| 'keyboard' \| 'wheel' }) => void` | - | 当值逐步变化时触发。 |
Expand Down
18 changes: 18 additions & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@
-moz-appearance: textfield;
}

&-clear-icon {
flex: none;
align-self: center;
padding: 0;
line-height: 1;
cursor: pointer;
background: none;
border: 0;

&-hidden {
visibility: hidden;
}

&-has-suffix {
margin-inline-end: 4px;
}
}

&-actions {
width: 20px;
height: 100%;
Expand Down
12 changes: 12 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ nav:
<td>false</td>
<td>Specifies that an InputNumber should automatically get focus when the page loads</td>
</tr>
<tr>
<td>allowClear</td>
<td>boolean | { clearIcon?: ReactNode; disabled?: boolean; label?: string }</td>
<td>false</td>
<td>Whether to show a clear button, optionally with a custom icon, disabled state, or accessible label</td>
</tr>
<tr>
<td>readOnly</td>
<td>Boolean</td>
Expand Down Expand Up @@ -124,6 +130,12 @@ nav:
<td></td>
<td>Called when value of an InputNumber changed</td>
</tr>
<tr>
<td>onClear</td>
<td>Function</td>
<td></td>
<td>Called when the clear button is clicked</td>
</tr>
<tr>
<td>onBlur</td>
<td>Function</td>
Expand Down
11 changes: 11 additions & 0 deletions docs/demo/allow-clear.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import InputNumber from '@rc-component/input-number';
import React from 'react';
import '../../assets/index.less';

export default () => (
<div style={{ display: 'flex', gap: 12, margin: 10 }}>
<InputNumber allowClear defaultValue={100} />
<InputNumber allowClear={{ clearIcon: '⌫', label: 'Clear amount' }} defaultValue={0} />
<InputNumber allowClear={{ disabled: true }} defaultValue={100} />
</div>
);
4 changes: 4 additions & 0 deletions docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ nav:

<code src="./demo/prefix-suffix.tsx"></code>

## allow-clear

<code src="./demo/allow-clear.tsx"></code>

## combination-key-format

<code src="./demo/combination-key-format.tsx"></code>
Expand Down
90 changes: 86 additions & 4 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ const getWheelDeltaY = (event: WheelEvent) => {
}
};

type SemanticName = 'root' | 'actions' | 'input' | 'action' | 'prefix' | 'suffix';
type SemanticName = 'root' | 'actions' | 'input' | 'action' | 'prefix' | 'suffix' | 'clear';
export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<
extends
Omit<
React.InputHTMLAttributes<HTMLInputElement>,
| 'value'
| 'defaultValue'
Expand Down Expand Up @@ -117,6 +118,13 @@ export interface InputNumberProps<T extends ValueType = ValueType>
controls?: boolean;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
allowClear?:
| boolean
| {
clearIcon?: React.ReactNode;
disabled?: boolean;
label?: string;
};
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;

Expand All @@ -137,6 +145,7 @@ export interface InputNumberProps<T extends ValueType = ValueType>

onInput?: (text: string) => void;
onChange?: (value: T | null) => void;
onClear?: () => void;
onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;

onStep?: (
Expand Down Expand Up @@ -174,6 +183,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r

prefix,
suffix,
allowClear,
stringMode,

parser,
Expand All @@ -182,6 +192,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
decimalSeparator,

onChange,
onClear,
onInput,
onPressEnter,
onStep,
Expand Down Expand Up @@ -285,6 +296,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r

// >>> Formatter
const inputValueRef = React.useRef<string | number>('');
const inputValueUpdateRef = React.useRef(0);
const mergedFormatter = React.useCallback(
(number: string, userTyping: boolean) => {
if (formatter) {
Expand Down Expand Up @@ -445,6 +457,8 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r

// >>> Collect input value
const collectInputValue = (inputStr: string) => {
const inputValueUpdateId = (inputValueUpdateRef.current += 1);

recordCursor();

// Update inputValue in case input can not parse as number
Expand All @@ -467,6 +481,10 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
// optimize for chinese input experience
// https://github.com/ant-design/ant-design/issues/8196
onNextPromise(() => {
if (inputValueUpdateId !== inputValueUpdateRef.current) {
return;
}

let nextInputStr = inputStr;
if (!parser) {
nextInputStr = inputStr.replace(/。/g, '.');
Expand Down Expand Up @@ -633,7 +651,12 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
}, [changeOnWheel, focus, onInternalWheel]);

// >>> Focus & Blur
const onBlur = () => {
const onBlur: React.FocusEventHandler<HTMLDivElement> = (event) => {
// Moving focus from an internal control back to the input does not blur InputNumber.
if (event.target !== inputRef.current && event.relatedTarget === inputRef.current) {
return;
}

if (changeOnBlur) {
flushInputValue(false);
}
Expand Down Expand Up @@ -706,6 +729,64 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
</StepHandler>
);

const clearConfig = allowClear && typeof allowClear === 'object' ? allowClear : {};
const clearDisabled = !!(disabled || readOnly || clearConfig.disabled);
const showClear = !!allowClear && !clearDisabled && String(inputValue).length > 0;
const clearIconCls = `${prefixCls}-clear-icon`;

const onClearKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {
const isStepKey = ['Up', 'ArrowUp', 'Down', 'ArrowDown'].includes(event.key);
if (event.key === 'Enter' || (keyboard !== false && isStepKey)) {
event.stopPropagation();
}
};

const onClearClick = () => {
if (!showClear) {
return;
}

userTypingRef.current = false;
inputValueRef.current = '';
inputValueUpdateRef.current += 1;

const emptyValue = getMiniDecimal(null);

// `triggerValueUpdate` only refreshes the display when the decimal value changes.
// Clear raw input such as `-`, or restore the source value in controlled mode.
if (value !== undefined) {
setInputValue(decimalValue, false);
} else if (decimalValue.isEmpty()) {
setInputValue(emptyValue, false);
}

inputRef.current?.focus();
triggerValueUpdate(emptyValue, false);
onClear?.();
};

const clearNode = allowClear && (
<button
type="button"
aria-label={clearConfig.label ?? 'Clear'}
disabled={!showClear}
className={clsx(
clearIconCls,
{
[`${clearIconCls}-hidden`]: !showClear,
[`${clearIconCls}-has-suffix`]: suffix !== undefined,
},
classNames?.clear,
)}
style={styles?.clear}
onMouseDown={(event) => event.preventDefault()}
onKeyDown={onClearKeyDown}
onClick={onClearClick}
>
{clearConfig.clearIcon ?? '✖'}
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);

// >>>>>> Render
return (
<div
Expand Down Expand Up @@ -760,8 +841,9 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
{...restProps}
/>

{suffix !== undefined && (
{(allowClear || suffix !== undefined) && (
<div className={clsx(`${prefixCls}-suffix`, classNames?.suffix)} style={styles?.suffix}>
{clearNode}
{suffix}
</div>
)}
Expand Down
Loading