Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 between internal controls does not blur InputNumber.
if (event.relatedTarget instanceof Node && rootRef.current?.contains(event.relatedTarget)) {
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