From da67d58a975fd0a5e4ad7f52c288104b24eeb2c2 Mon Sep 17 00:00:00 2001
From: snowbitx <109521682+snowbitx@users.noreply.github.com>
Date: Fri, 18 Jul 2025 22:38:49 +0800
Subject: [PATCH] feat(components): [input-number] add `disabled-scientific`
prop (#21319)
* feat(components): [input-number] add disableScientific prop
* chore: useless change
* Update basic.vue
* Update basic.vue
* Update docs/en-US/component/input-number.md
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
* refactor: merge events
* test: add test case
* chore: tweaks
* Update notes
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/en-US/component/input-number.md
Co-authored-by: btea <2356281422@qq.com>
* chore: rename prop
---------
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
Co-authored-by: Dsaquel <291874700n@gmail.com>
Co-authored-by: sea <45450994+warmthsea@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: btea <2356281422@qq.com>
---
docs/en-US/component/input-number.md | 45 ++++++++++---------
.../__tests__/input-number.test.tsx | 26 +++++++++++
.../input-number/src/input-number.ts | 4 ++
.../input-number/src/input-number.vue | 22 ++++++++-
4 files changed, 73 insertions(+), 24 deletions(-)
diff --git a/docs/en-US/component/input-number.md b/docs/en-US/component/input-number.md
index 210ca8de01..bc51822895 100644
--- a/docs/en-US/component/input-number.md
+++ b/docs/en-US/component/input-number.md
@@ -99,28 +99,29 @@ input-number/with-prefix-suffix
### Attributes
-| Name | Description | Type | Default |
-| --------------------------- | ------------------------------------------------ | --------------------------------------------- | ----------------------- |
-| model-value / v-model | binding value | ^[number] / ^[null] | — |
-| min | the minimum allowed value | ^[number] | Number.MAX_SAFE_INTEGER |
-| max | the maximum allowed value | ^[number] | Number.MIN_SAFE_INTEGER |
-| step | incremental step | ^[number] | 1 |
-| step-strictly | whether input value can only be multiple of step | ^[boolean] | false |
-| precision | precision of input value | ^[number] | — |
-| size | size of the component | ^[enum]`'large' \| 'default' \| 'small'` | default |
-| readonly ^(2.2.16) | same as `readonly` in native input | ^[boolean] | false |
-| disabled | whether the component is disabled | ^[boolean] | false |
-| controls | whether to enable the control buttons | ^[boolean] | true |
-| controls-position | position of the control buttons | ^[enum]`'' \| 'right'` | — |
-| name | same as `name` in native input | ^[string] | — |
-| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in native input | ^[string] | — |
-| placeholder | same as `placeholder` in native input | ^[string] | — |
-| id | same as `id` in native input | ^[string] | — |
-| value-on-clear ^(2.2.0) | value should be set when input box is cleared | ^[number] / ^[null] / ^[enum]`'min' \| 'max'` | — |
-| validate-event | whether to trigger form validation | ^[boolean] | true |
-| label ^(a11y) ^(deprecated) | same as `aria-label` in native input | ^[string] | — |
-| inputmode ^(2.10.3) | same as `inputmode` in native input | ^[string] | — |
-| align ^(2.10.5) | alignment for the inner input text | ^[enum]`'left' \| 'center' \| 'right'` | 'center' |
+| Name | Description | Type | Default |
+| ----------------------------- | ------------------------------------------------ | --------------------------------------------- | ----------------------- |
+| model-value / v-model | binding value | ^[number] / ^[null] | — |
+| min | the minimum allowed value | ^[number] | Number.MAX_SAFE_INTEGER |
+| max | the maximum allowed value | ^[number] | Number.MIN_SAFE_INTEGER |
+| step | incremental step | ^[number] | 1 |
+| step-strictly | whether input value can only be multiple of step | ^[boolean] | false |
+| precision | precision of input value | ^[number] | — |
+| size | size of the component | ^[enum]`'large' \| 'default' \| 'small'` | default |
+| readonly ^(2.2.16) | same as `readonly` in native input | ^[boolean] | false |
+| disabled | whether the component is disabled | ^[boolean] | false |
+| controls | whether to enable the control buttons | ^[boolean] | true |
+| controls-position | position of the control buttons | ^[enum]`'' \| 'right'` | — |
+| name | same as `name` in native input | ^[string] | — |
+| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in native input | ^[string] | — |
+| placeholder | same as `placeholder` in native input | ^[string] | — |
+| id | same as `id` in native input | ^[string] | — |
+| value-on-clear ^(2.2.0) | value should be set when input box is cleared | ^[number] / ^[null] / ^[enum]`'min' \| 'max'` | — |
+| validate-event | whether to trigger form validation | ^[boolean] | true |
+| label ^(a11y) ^(deprecated) | same as `aria-label` in native input | ^[string] | — |
+| inputmode ^(2.10.3) | same as `inputmode` in native input | ^[string] | — |
+| align ^(2.10.5) | alignment for the inner input text | ^[enum]`'left' \| 'center' \| 'right'` | 'center' |
+| disabled-scientific ^(2.10.5) | disables input of scientific notation (e.g. 'e') | ^[boolean] | false |
### Slots
diff --git a/packages/components/input-number/__tests__/input-number.test.tsx b/packages/components/input-number/__tests__/input-number.test.tsx
index 917cb492d9..8624a1facc 100755
--- a/packages/components/input-number/__tests__/input-number.test.tsx
+++ b/packages/components/input-number/__tests__/input-number.test.tsx
@@ -632,4 +632,30 @@ describe('InputNumber.vue', () => {
}
)
})
+
+ test('should prevent typing "e" or "E" when disabledScientific is true', async () => {
+ const num = ref(1)
+ const wrapper = mount(() => (
+
+ ))
+ const input = wrapper.find('input')
+ const preventDefault = vi.fn()
+ await input.trigger('keydown', {
+ key: 'e',
+ preventDefault,
+ })
+ expect(preventDefault).toHaveBeenCalled()
+ preventDefault.mockClear()
+ await input.trigger('keydown', {
+ key: 'E',
+ preventDefault,
+ })
+ expect(preventDefault).toHaveBeenCalled()
+ preventDefault.mockClear()
+ await input.trigger('keydown', {
+ key: '1',
+ preventDefault,
+ })
+ expect(preventDefault).not.toHaveBeenCalled()
+ })
})
diff --git a/packages/components/input-number/src/input-number.ts b/packages/components/input-number/src/input-number.ts
index d3f3777ea2..f77fc5c762 100644
--- a/packages/components/input-number/src/input-number.ts
+++ b/packages/components/input-number/src/input-number.ts
@@ -127,6 +127,10 @@ export const inputNumberProps = buildProps({
type: definePropType<'left' | 'right' | 'center'>(String),
default: 'center',
},
+ /**
+ * @description whether to disable scientific notation input (e.g. 'e', 'E')
+ */
+ disabledScientific: Boolean,
} as const)
export type InputNumberProps = ExtractPropTypes
export type InputNumberPropsPublic = __ExtractPublicPropTypes<
diff --git a/packages/components/input-number/src/input-number.vue b/packages/components/input-number/src/input-number.vue
index b9e0429280..8ec36e2d6d 100755
--- a/packages/components/input-number/src/input-number.vue
+++ b/packages/components/input-number/src/input-number.vue
@@ -56,8 +56,7 @@
:aria-label="ariaLabel"
:validate-event="false"
:inputmode="inputmode"
- @keydown.up.prevent="increase"
- @keydown.down.prevent="decrease"
+ @keydown="handleKeydown"
@blur="handleBlur"
@focus="handleFocus"
@input="handleInput"
@@ -95,6 +94,7 @@ import {
import { ArrowDown, ArrowUp, Minus, Plus } from '@element-plus/icons-vue'
import {
CHANGE_EVENT,
+ EVENT_CODE,
INPUT_EVENT,
UPDATE_MODEL_EVENT,
} from '@element-plus/constants'
@@ -210,6 +210,24 @@ const ensurePrecision = (val: number, coefficient: 1 | -1 = 1) => {
// Solve the accuracy problem of JS decimal calculation by converting the value to integer.
return toPrecision(val + props.step * coefficient)
}
+const handleKeydown = (event: Event) => {
+ const e = event as KeyboardEvent
+ if (props.disabledScientific && ['e', 'E'].includes(e.key)) {
+ e.preventDefault()
+ return
+ }
+ const keyHandlers = {
+ [EVENT_CODE.up]: () => {
+ e.preventDefault()
+ increase()
+ },
+ [EVENT_CODE.down]: () => {
+ e.preventDefault()
+ decrease()
+ },
+ }
+ keyHandlers[e.key]?.()
+}
const increase = () => {
if (props.readonly || inputNumberDisabled.value || maxDisabled.value) return
const value = Number(displayValue.value) || 0