feat(TextArea): add clearable functionality to TextArea component (#7021)#7022
feat(TextArea): add clearable functionality to TextArea component (#7021)#7022viko16 wants to merge 13 commits into
Conversation
- Introduced `clearable` prop to enable a clear icon for the TextArea. - Added `clearIcon` prop for custom clear icon support. - Implemented `onClear` callback to handle clear button actions. - Updated documentation in English and Chinese to reflect new props. - Enhanced styles for the clear button in the TextArea component. - Added tests to verify clear button functionality and integration with composition events.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough新增 TextArea 清空功能:添加 Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant TextArea as TextArea
participant ConfigProvider as ConfigProvider
participant Locale as Locale
User->>TextArea: 输入文本 / 点击清空按钮
TextArea->>ConfigProvider: 请求 textArea.clearIcon(若存在)
TextArea->>Locale: 获取 TextArea.clear 文案(无障碍标签)
TextArea->>TextArea: 清空 value,调用 onClear(若存在),更新聚焦/组合态
alt iOS composition 活跃
TextArea->>TextArea: 处理 composition 并执行 blur/focus 修复
end
TextArea->>User: 更新 UI(清空文本、更新控件可见性)
Estimated code review effort🎯 3 (中等) | ⏱️ ~25 minutes 诗句
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @viko16, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求为 Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/locales/ar-SA.ts`:
- Line 114: The translations for TextArea.clear and Input.clear are
inconsistent: TextArea.clear currently uses 'مسح' while Input.clear uses
'إزالة'; update the TextArea.clear entry to use the same Arabic term as
Input.clear (use 'إزالة') so both TextArea.clear and Input.clear are identical
and the user experience is consistent.
In `@src/locales/hr-HR.ts`:
- Around line 147-149: TextArea.clear currently uses the Croatian word 'obriši'
while Input.clear uses 'očisti', causing inconsistent wording; update
TextArea.clear to use the same translation as Input.clear (i.e., replace the
value for TextArea.clear to match Input.clear) so both keys (TextArea.clear and
Input.clear) are consistent across the locale file.
🧹 Nitpick comments (4)
src/components/text-area/text-area.less (1)
81-93: 可选:为清除按钮补充键盘可见焦点
若该元素可聚焦,建议添加:focus-visible样式以提升键盘可访问性。♿ 建议补充可见焦点样式
.adm-text-area-clear { flex: none; margin-left: 8px; color: var(--adm-color-light); &:active { color: var(--adm-color-weak); } padding: 4px; cursor: pointer; + &:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; + } .antd-mobile-icon { display: block; font-size: var(--adm-font-size-7); } }src/components/text-area/tests/text-area.test.tsx (1)
6-10: 建议:增加非 iOS 场景的测试覆盖。当前 mock 强制
isIOS()始终返回true,这意味着只测试了 iOS 特定的清除逻辑(composition 期间会触发 blur)。建议考虑增加非 iOS 场景的测试,验证在非 iOS 设备上点击清除按钮时不会触发 blur。可以通过条件 mock 或在单独的 describe 块中重置 mock 来实现:
♻️ 建议的测试补充
describe('TextArea on non-iOS', () => { beforeAll(() => { jest.resetModules() jest.doMock('../../../utils/validate', () => ({ isIOS: () => false, })) }) it('should not blur on clear during composition', async () => { // 测试非 iOS 场景 }) })src/components/text-area/text-area.tsx (2)
253-253: 可选:移除冗余的 defaultProps 赋值。由于在 Line 86 已通过
mergeProps(defaultProps, componentConfig, p)处理了默认值合并,此处的TextArea.defaultProps = defaultProps是冗余的。不过保留它也不会造成问题,这只是一个小的代码整洁建议。
215-234: 为清除按钮添加role="button"、tabIndex={0}和键盘事件处理以增强无障碍访问。清除按钮使用
<div>元素实现,作为一个独立的交互控件,虽然已添加aria-label,但缺少必要的无障碍属性。建议补充:
role="button"使屏幕阅读器正确识别该控件为按钮tabIndex={0}使键盘用户能够通过 Tab 键访问该按钮onKeyDown处理器支持 Enter 和空格键激活按钮建议的改进
<div className={`${classPrefix}-clear`} + role='button' + tabIndex={0} onMouseDown={e => { e.preventDefault() }} onClick={() => { setValue('') props.onClear?.() // https://github.com/ant-design/ant-design-mobile/issues/5212 if (isIOS() && compositingRef.current) { compositingRef.current = false nativeTextAreaRef.current?.blur() } }} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setValue('') + props.onClear?.() + } + }} aria-label={locale.TextArea.clear} >建议确认 Input 组件的清除按钮是否采用相同的无障碍模式,以保持全库的一致性。
- ar-SA: 'إزالة' - hr-HR, cnr-ME: 'očisti' - sr-RS: 'очисти'
commit: |
|
|
||
| const compositingRef = useRef(false) | ||
|
|
||
| const shouldShowClear = |
There was a problem hiding this comment.
Input 的设计是聚焦的时候才展示清除按钮。因为在长表单的情况下,手机不同于 PC,清除按钮会过多占据视觉区域。
There was a problem hiding this comment.
我曾经尝试过添加 onlyShowClearWhenFocus 功能,但问题是如果清除按钮动态出现/消失的话,会导致 TextArea 宽度变化,影响 autoSize 功能。
Input 没问题是因为永远只有单行,改变输入区域宽度不影响高度。
同时也调研过淘宝、拼多多、微信的多行输入框,都是常驻。感觉我们可以在 #7021 上讨论下。
There was a problem hiding this comment.
我的想法是可以在存在 clear 的时候提供一下边缘 padding,这样宽度就是稳定的了。因为 Input 和 TextArea 混用一个有一个没有,这个是非常古怪的。
There was a problem hiding this comment.
嗯,明白。这个的确是比较讨厌的点。在 Input 里不用考虑换行做的动态 padding。要不然这样,如果有动态高度的,就固定 padding,反之就是动态 padding 如何?做尽可能的保全
There was a problem hiding this comment.
longshot20260225104727_compressed.mp4
调整成了 clearIcon 会根据 focus 显示消失,按照上面的讨论:
- 在 autoSize 时,clearIcon 的位置会持续占据一列
- 非 autoSize 时,clearIcon 仅在聚焦的时候出现
取了一个相对平衡的点,也在文档上补充说明了这种现象。
- Updated TextArea styles for full responsiveness in width and height. - Refactored component structure to improve layout consistency and flexibility.
- Clear button now only displays when TextArea has focus in non-autoSize mode - In autoSize mode, clear container space is reserved to prevent layout jitter - Added `hasFocus` state to track focus status - Enhanced tests for clear button focus behavior on both autoSize and non-autoSize scenarios - Updated documentation to explain why onlyShowClearWhenFocus property is not needed
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/text-area/index.en.md`:
- Around line 34-39: Rename the FAQ header "Why there is no
onlyShowClearWhenFocus property?" to "Why is there no onlyShowClearWhenFocus
property?" and update the non-autoSize description to explicitly mention the
"value is non-empty" condition (i.e., in non-autoSize scenario the clear icon
only shows when focused and the value is non-empty) while keeping the autoSize
explanation about reserving space for the clear icon to avoid width/height
jitter; reference the "onlyShowClearWhenFocus" property and "autoSize" behavior
in the updated text.
In `@src/components/text-area/index.zh.md`:
- Around line 38-39: The doc lines describing clear-icon behavior are missing
the "有内容时" condition and should match the implementation: update the two bullets
(for non-autoSize and autoSize) in the TextArea docs so the non-autoSize bullet
reads that the clear icon only appears when 聚焦且有内容 (focused and has value), and
the autoSize bullet notes it always reserves space when 有内容 to avoid
width/height flicker; reference the TextArea component and the autoSize behavior
to ensure wording aligns with the actual show/visibility logic.
In `@src/components/text-area/text-area.less`:
- Around line 96-100: 在样式中 &:active 选择器块后直接跟着 padding 和 cursor 会触发 stylelint 的
declaration-empty-line-before 规则;在 text-area.less 中找到 &:active
块并在其结束后与后续声明(padding 和 cursor)之间插入一个空行,确保在 &:active { ... } 和 padding: 4px;
之间有空行以通过 linter(或调整相应声明使其在同一规则块内,如果更合适)。
In `@src/components/text-area/text-area.tsx`:
- Around line 240-259: Replace the non-semantic clear control div with a
semantic, keyboard-accessible button (e.g., <button type="button">) while
preserving behavior: keep className construction using classPrefix and
shouldShowClear, keep aria-hidden={!shouldShowClear} and
aria-label={locale.TextArea.clear}, retain the onMouseDown preventDefault logic
and the onClick handler that calls setValue(''), props.onClear?.(), and the iOS
compositing logic using compositingRef and nativeTextAreaRef; ensure the element
remains type="button" so it does not submit forms and that props.clearIcon is
rendered inside the button.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/components/text-area/tests/__snapshots__/text-area.test.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (5)
src/components/text-area/index.en.mdsrc/components/text-area/index.zh.mdsrc/components/text-area/tests/text-area.test.tsxsrc/components/text-area/text-area.lesssrc/components/text-area/text-area.tsx
| <div | ||
| className={`${classPrefix}-clear ${!shouldShowClear ? `${classPrefix}-clear-hidden` : ''}`} | ||
| aria-hidden={!shouldShowClear} | ||
| onMouseDown={e => { | ||
| e.preventDefault() | ||
| }} | ||
| onClick={() => { | ||
| setValue('') | ||
| props.onClear?.() | ||
|
|
||
| {autoSize && ( | ||
| <textarea | ||
| ref={hiddenTextAreaRef} | ||
| className={`${classPrefix}-element ${classPrefix}-element-hidden`} | ||
| value={value} | ||
| rows={rows} | ||
| aria-hidden | ||
| readOnly | ||
| /> | ||
| )} | ||
| // https://github.com/ant-design/ant-design-mobile/issues/5212 | ||
| if (isIOS() && compositingRef.current) { | ||
| compositingRef.current = false | ||
| nativeTextAreaRef.current?.blur() | ||
| } | ||
| }} | ||
| aria-label={locale.TextArea.clear} | ||
| > | ||
| {props.clearIcon} | ||
| </div> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查找 clear 控件的实现代码
rg -n "className=\`\${classPrefix}-clear" src/components/text-area/text-area.tsx -A 15 -B 2Repository: ant-design/ant-design-mobile
Length of output: 198
🏁 Script executed:
#!/bin/bash
# 查找键盘事件处理和 aria 相关实现
rg -n "onKeyDown|onKeyPress|onKeyUp|role=.*button|tabIndex" src/components/text-area/text-area.tsxRepository: ant-design/ant-design-mobile
Length of output: 172
🏁 Script executed:
#!/bin/bash
# 检查测试文件中是否有键盘相关的测试
rg -n "clear.*key|keyboard|Enter|Space|keyDown" src/components/text-area/tests/text-area.test.tsx -C 2Repository: ant-design/ant-design-mobile
Length of output: 900
🏁 Script executed:
#!/bin/bash
# 查看测试文件的结构和是否有 clear 相关测试
fd "text-area.test" src/components/text-area/ && cat src/components/text-area/text-area.test.tsx | head -100Repository: ant-design/ant-design-mobile
Length of output: 283
🏁 Script executed:
#!/bin/bash
# 查看文本框组件的完整 clear 控件代码
sed -n '230,270p' src/components/text-area/text-area.tsxRepository: ant-design/ant-design-mobile
Length of output: 1213
🏁 Script executed:
#!/bin/bash
# 查看整个 text-area.tsx 文件的大小和内容片段
wc -l src/components/text-area/text-area.tsx
sed -n '235,265p' src/components/text-area/text-area.tsxRepository: ant-design/ant-design-mobile
Length of output: 1045
🏁 Script executed:
#!/bin/bash
# 搜索 clear 相关的代码和事件处理
rg -n "shouldShowClear" src/components/text-area/text-area.tsx -B 3 -A 10Repository: ant-design/ant-design-mobile
Length of output: 1195
🏁 Script executed:
#!/bin/bash
# 查看测试文件中是否有 clear 控件的键盘可访问性测试
rg -n "clear" src/components/text-area/tests/text-area.test.tsx -C 3Repository: ant-design/ant-design-mobile
Length of output: 7324
将清除控件改为语义化按钮以支持键盘访问。
当前使用 div + onClick 无法通过键盘激活,仅有 aria-label 无法弥补交互语义缺失。改为 <button type="button"> 使其具备键盘可达性、正确的语义角色和屏幕阅读器支持。
建议修改
{(shouldShowClear || reserveClearSpace) && (
- <div
+ <button
+ type="button"
className={`${classPrefix}-clear ${!shouldShowClear ? `${classPrefix}-clear-hidden` : ''}`}
aria-hidden={!shouldShowClear}
onMouseDown={e => {
e.preventDefault()
}}
onClick={() => {
setValue('')
props.onClear?.()
// https://github.com/ant-design/ant-design-mobile/issues/5212
if (isIOS() && compositingRef.current) {
compositingRef.current = false
nativeTextAreaRef.current?.blur()
}
}}
aria-label={locale.TextArea.clear}
>
{props.clearIcon}
- </div>
+ </button>
)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| className={`${classPrefix}-clear ${!shouldShowClear ? `${classPrefix}-clear-hidden` : ''}`} | |
| aria-hidden={!shouldShowClear} | |
| onMouseDown={e => { | |
| e.preventDefault() | |
| }} | |
| onClick={() => { | |
| setValue('') | |
| props.onClear?.() | |
| {autoSize && ( | |
| <textarea | |
| ref={hiddenTextAreaRef} | |
| className={`${classPrefix}-element ${classPrefix}-element-hidden`} | |
| value={value} | |
| rows={rows} | |
| aria-hidden | |
| readOnly | |
| /> | |
| )} | |
| // https://github.com/ant-design/ant-design-mobile/issues/5212 | |
| if (isIOS() && compositingRef.current) { | |
| compositingRef.current = false | |
| nativeTextAreaRef.current?.blur() | |
| } | |
| }} | |
| aria-label={locale.TextArea.clear} | |
| > | |
| {props.clearIcon} | |
| </div> | |
| <button | |
| type="button" | |
| className={`${classPrefix}-clear ${!shouldShowClear ? `${classPrefix}-clear-hidden` : ''}`} | |
| aria-hidden={!shouldShowClear} | |
| onMouseDown={e => { | |
| e.preventDefault() | |
| }} | |
| onClick={() => { | |
| setValue('') | |
| props.onClear?.() | |
| // https://github.com/ant-design/ant-design-mobile/issues/5212 | |
| if (isIOS() && compositingRef.current) { | |
| compositingRef.current = false | |
| nativeTextAreaRef.current?.blur() | |
| } | |
| }} | |
| aria-label={locale.TextArea.clear} | |
| > | |
| {props.clearIcon} | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/text-area/text-area.tsx` around lines 240 - 259, Replace the
non-semantic clear control div with a semantic, keyboard-accessible button
(e.g., <button type="button">) while preserving behavior: keep className
construction using classPrefix and shouldShowClear, keep
aria-hidden={!shouldShowClear} and aria-label={locale.TextArea.clear}, retain
the onMouseDown preventDefault logic and the onClick handler that calls
setValue(''), props.onClear?.(), and the iOS compositing logic using
compositingRef and nativeTextAreaRef; ensure the element remains type="button"
so it does not submit forms and that props.clearIcon is rendered inside the
button.
|
我已收到邮件,将尽快阅读。谢谢您的来信!
|
- Replace separate clearIcon/onClear props with clearable: boolean | TextAreaClearableConfig - Add IIFE to resolve clearable config with priority: prop config > ConfigProvider > default - Update tests to use clearable object form for onClear and clearIcon cases - Update zh/en docs with TextAreaClearableConfig sub-table - Add demo for clearable object config with custom icon and callback Made-with: Cursor
| }) | ||
|
|
||
| // Resolve clearable: boolean acts as toggle, object acts as detailed config | ||
| const clearableConfig = (() => { |
There was a problem hiding this comment.
已更新,目前放在了 src/hooks 下,整体往通用的方向拆了。
目前暂时只用在了 textarea ,如果 input 和 virtual-input 也要支持,我感觉可以再拉一个新 PR 来做🤔。
- Add src/hooks/useClearable.tsx with generic ClearableConfig interface and useClearable hook - Support both object-style (TextArea) and flat-props-style (Input) clearable patterns - Implement multi-level fallback chain for clearIcon and onClear resolution - Refactor TextArea to consume useClearable, removing inline clearableConfig logic - Remove TextAreaClearableConfig type in favor of shared ClearableConfig - Update docs to reference ClearableConfig instead of TextAreaClearableConfig
- Change onlyShowClearWhenFocus check from truthy to strict === false comparison - hasFocus is now factored into shouldShowClear by default - Only skip focus check when onlyShowClearWhenFocus is explicitly false (e.g. SearchBar)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #7022 +/- ##
==========================================
+ Coverage 92.94% 93.01% +0.06%
==========================================
Files 337 338 +1
Lines 7375 7417 +42
Branches 1879 1899 +20
==========================================
+ Hits 6855 6899 +44
+ Misses 512 482 -30
- Partials 8 36 +28 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| /> | ||
| {count} | ||
| <div className={`${classPrefix}-input-wrapper`}> | ||
| <div className={`${classPrefix}-textarea-wrapper`}> |
There was a problem hiding this comment.
套两层 dom 感觉有点太复杂了,在 focus 的时候直接 padding 一下。然后 clear 按钮悬浮在右侧。这样是不是就够了?
… var - Remove input-wrapper and textarea-wrapper, flatten to single container - Use absolute positioning for clear icon instead of flex layout - Add --clear-icon-padding CSS variable (default 28px) - Document --clear-icon-padding in zh and en markdown files - Update snapshot to reflect the flattened DOM structure
- Add test case 'clear button should preventDefault on mouseDown' - Render TextArea with clearable and defaultValue, focus to show clear button - Fire mouseDown on clear button and assert preventDefault was called - Covers line 250 of text-area.tsx (onMouseDown handler)


TextArea 支持 clearable
功能描述
截图
视觉层级
HTML 结构
Summary by CodeRabbit
新功能
文档
样式
本地化
测试