feat(components): [input] textarea supports clearing effects (#23723)

* feat(components): [input] textarea supports clearing effects

* Update packages/components/input/__tests__/input.test.tsx

Co-authored-by: rzzf <cszhjh@gmail.com>

* feat: update

* Apply suggestions from code review

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* style: update

* Update packages/components/input/src/input.vue

Co-authored-by: rzzf <cszhjh@gmail.com>

---------

Co-authored-by: rzzf <cszhjh@gmail.com>
Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
This commit is contained in:
btea
2026-03-05 19:08:29 +08:00
committed by GitHub
parent 25a3595ede
commit ea926472df
6 changed files with 115 additions and 23 deletions

View File

@@ -25,7 +25,7 @@ input/disabled
## Clearable
:::demo Make the Input clearable with the `clearable` attribute.
:::demo Make the Input clearable with the `clearable` attribute. After version ^(2.13.4), the clearable feature is also available for textarea type of Input.
input/clearable

View File

@@ -1,10 +1,19 @@
<template>
<el-input
v-model="input"
clearable
:clear-icon="CloseBold"
placeholder="Custom clear icon"
/>
<div class="input-group">
<el-input
v-model="input"
clearable
:clear-icon="CloseBold"
placeholder="Custom clear icon"
/>
<el-input
v-model="textareaInput"
clearable
:clear-icon="CloseBold"
placeholder="Custom clear icon"
type="textarea"
/>
</div>
</template>
<script lang="ts" setup>
@@ -12,4 +21,13 @@ import { ref } from 'vue'
import { CloseBold } from '@element-plus/icons-vue'
const input = ref('Custom clear icon')
const textareaInput = ref('Custom clear icon')
</script>
<style scoped>
.input-group {
display: flex;
flex-direction: column;
gap: 1em;
}
</style>

View File

@@ -1,14 +1,32 @@
<template>
<el-input
v-model="input"
style="width: 240px"
placeholder="Please input"
clearable
/>
<div class="input-group">
<el-input
v-model="input"
style="width: 240px"
placeholder="Please input"
clearable
/>
<el-input
v-model="textareaInput"
style="width: 240px"
placeholder="Please input"
type="textarea"
clearable
/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const input = ref('')
const textareaInput = ref('')
</script>
<style scoped>
.input-group {
display: flex;
align-items: center;
gap: 1em;
}
</style>

View File

@@ -457,28 +457,49 @@ describe('Input.vue', () => {
const handleClear = vi.fn()
const handleInput = vi.fn()
const content = ref('a')
const handleTextareaClear = vi.fn()
const handleTextareaInput = vi.fn()
const textareaContent = ref('a')
const wrapper = mount(() => (
<Input
placeholder="请输入内容"
clearable
v-model={content.value}
onClear={handleClear}
onInput={handleInput}
/>
<>
<Input
placeholder="请输入内容"
clearable
v-model={content.value}
onClear={handleClear}
onInput={handleInput}
/>
<Input
type="textarea"
placeholder="请输入内容"
clearable
v-model={textareaContent.value}
onClear={handleTextareaClear}
onInput={handleTextareaInput}
/>
</>
))
const input = wrapper.find('input')
const vm = wrapper.vm
const textarea = wrapper.find('textarea')
// focus to show clear button
await input.trigger('focus')
await nextTick()
vm.$el.querySelector('.el-input__clear').click()
wrapper.find('.el-input__clear').trigger('click')
await nextTick()
expect(content.value).toEqual('')
expect(handleClear).toBeCalled()
expect(handleClear).toBeCalledWith(expect.any(MouseEvent))
expect(handleInput).toBeCalled()
// textarea
await textarea.trigger('focus')
await nextTick()
wrapper.find('.el-textarea__clear').trigger('click')
await nextTick()
expect(textareaContent.value).toEqual('')
expect(handleTextareaClear).toBeCalled()
expect(handleTextareaInput).toBeCalled()
})
test('event:input', async () => {

View File

@@ -121,7 +121,11 @@
<textarea
:id="inputId"
ref="textarea"
:class="[nsTextarea.e('inner'), nsInput.is('focus', isFocused)]"
:class="[
nsTextarea.e('inner'),
nsInput.is('focus', isFocused),
nsTextarea.is('clearable', clearable),
]"
v-bind="attrs"
:name="name"
:minlength="minlength"
@@ -146,6 +150,14 @@
@change="handleChange"
@keydown="handleKeydown"
/>
<el-icon
v-if="showClear"
:class="[nsTextarea.e('icon'), nsTextarea.e('clear')]"
@mousedown.prevent="NOOP"
@click="clear"
>
<component :is="clearIcon" />
</el-icon>
<span
v-if="isWordLimitVisible"
:style="countStyle"

View File

@@ -82,6 +82,11 @@
transition: getCssVar('transition-box-shadow');
border: none;
@include when(clearable) {
padding: 5px map.get($input-padding-horizontal, 'default') + 14px 5px
map.get($input-padding-horizontal, 'default')-$border-width;
}
&::placeholder {
color: getCssVarWithDefault(
'input-placeholder-color',
@@ -99,6 +104,20 @@
}
}
@include e(clear) {
position: absolute;
right: 11px;
top: 15px;
transform: translateY(-50%);
color: getCssVar('input-icon-color');
font-size: map.get($input-font-size, 'default');
cursor: pointer;
&:hover {
color: getCssVar('input-clear-hover-color');
}
}
& .#{$namespace}-input__count {
color: getCssVar('color-info');
background: getCssVar('fill-color', 'blank');
@@ -107,6 +126,7 @@
line-height: 14px;
bottom: 5px;
right: 10px;
@include when(outside) {
position: absolute;
background: transparent;
@@ -182,12 +202,14 @@
display: inline-block;
padding-left: 8px;
}
@include when(outside) {
height: unset;
position: absolute;
padding-top: 2px;
top: 100%;
right: 0;
.#{$namespace}-input__count-inner {
background: transparent;
padding-left: 0px;
@@ -401,6 +423,7 @@
padding: $border-width
map.get($input-padding-horizontal, $size)-$border-width;
}
& {
@include set-css-var-value(
'input-inner-height',