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