-
Notifications
You must be signed in to change notification settings - Fork 2.5k
fix(VirtualInput): onBlur not triggered on Android when long-pressing native input #7033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
213fb8c
16ac97a
05997e3
ba36721
e2d446f
f17d3ca
f58ac4d
b313d14
ad7ac6a
8b1c7fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,40 +1,45 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { useEvent } from 'rc-util' | ||||||||||||||||||||||||||||||||||||||||||
| import { useEffect } from 'react' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 监听点击组件外部的事件 | ||||||||||||||||||||||||||||||||||||||||||
| function useClickOutside( | ||||||||||||||||||||||||||||||||||||||||||
| handler: (event: MouseEvent) => void, | ||||||||||||||||||||||||||||||||||||||||||
| ref: React.RefObject<HTMLElement>, | ||||||||||||||||||||||||||||||||||||||||||
| hasKeyboardProps: boolean = false | ||||||||||||||||||||||||||||||||||||||||||
| hasKeyboardProps = false | ||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||
| const stableHandler = useEvent(handler) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||
| function handleClick(event: MouseEvent) { | ||||||||||||||||||||||||||||||||||||||||||
| if (!ref.current || ref.current.contains(event.target as Node)) { | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| handler(event) | ||||||||||||||||||||||||||||||||||||||||||
| stableHandler(event) // 使用 ref 中的 handler | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 向前兼容逻辑: | ||||||||||||||||||||||||||||||||||||||||||
| // 1. 对于有键盘属性的 VirtualInput,在捕获阶段监听: | ||||||||||||||||||||||||||||||||||||||||||
| // 这是为了确保在事件被阻止传播之前触发。比如输入框中的单个数字 click 事件会 stopPropagation, 但这里依然能捕获到 | ||||||||||||||||||||||||||||||||||||||||||
| // 2. 对于无键盘属性的 VirtualInput 组件,在冒泡阶段监听: | ||||||||||||||||||||||||||||||||||||||||||
| // 这种情况通常是 VirtualInput + NumberKeyboard 为兄弟关系,在以前版本中点击 NumberKeyboard **不会**触发 VirtualInput 的 blur 事件 | ||||||||||||||||||||||||||||||||||||||||||
| // 原先原理:通过 NumberKeyboard 内部 onMouseDown 时 preventDefault 阻止的 VirtualInput 内原生的 blur 事件 | ||||||||||||||||||||||||||||||||||||||||||
| // 新的原理:NumberKeyboard 的 Popup 默认会 stopPropagation click, 这里在冒泡阶段监听不到,不会调用 VirtualInput 的 onBlur 回调(非原生事件)。 | ||||||||||||||||||||||||||||||||||||||||||
| document.addEventListener( | ||||||||||||||||||||||||||||||||||||||||||
| 'click', | ||||||||||||||||||||||||||||||||||||||||||
| handleClick, | ||||||||||||||||||||||||||||||||||||||||||
| hasKeyboardProps ? true : false | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 安卓长按 native input 时不会触发 click 事件,通过监听 focusin 补充处理: | ||||||||||||||||||||||||||||||||||||||||||
| // 长按后系统键盘弹出,native input 获得焦点并冒泡 focusin 到 document,以此触发 blur | ||||||||||||||||||||||||||||||||||||||||||
| function handleFocusIn(event: FocusEvent) { | ||||||||||||||||||||||||||||||||||||||||||
| if (!ref.current || ref.current.contains(event.target as Node)) { | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| stableHandler(event as unknown as MouseEvent) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| document.addEventListener('click', handleClick, hasKeyboardProps) | ||||||||||||||||||||||||||||||||||||||||||
| document.addEventListener('focusin', handleFocusIn, hasKeyboardProps) | ||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||
| document.removeEventListener( | ||||||||||||||||||||||||||||||||||||||||||
| 'click', | ||||||||||||||||||||||||||||||||||||||||||
| handleClick, | ||||||||||||||||||||||||||||||||||||||||||
| hasKeyboardProps ? true : false | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| document.removeEventListener('click', handleClick, hasKeyboardProps) | ||||||||||||||||||||||||||||||||||||||||||
| document.removeEventListener('focusin', handleFocusIn, hasKeyboardProps) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }, [handler, ref]) | ||||||||||||||||||||||||||||||||||||||||||
| }, [ref]) // 只依赖 ref,不依赖 handler | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 验证 effect 内是否使用 hasKeyboardProps 且依赖数组未包含它
rg -n "addEventListener\('click'.*hasKeyboardProps|removeEventListener\('click'.*hasKeyboardProps|\}, \[ref\]" src/components/virtual-input/use-click-outside.tsxRepository: ant-design/ant-design-mobile Length of output: 260 🏁 Script executed: cat -n src/components/virtual-input/use-click-outside.tsx | head -60Repository: ant-design/ant-design-mobile Length of output: 2054 将 第 36 行与第 39 行使用 建议修改- }, [ref]) // 只依赖 ref,不依赖 handler
+ }, [ref, hasKeyboardProps]) // 当 hasKeyboardProps 变化时重新绑定监听📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
5
to
43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type cast |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export default useClickOutside | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: ant-design/ant-design-mobile
Length of output: 1080
🏁 Script executed:
Repository: ant-design/ant-design-mobile
Length of output: 2090
🏁 Script executed:
sed -n '145,155p' src/components/virtual-input/virtual-input.tsxRepository: ant-design/ant-design-mobile
Length of output: 220
🏁 Script executed:
rg "useClickOutside" src/components --type ts --type tsxRepository: ant-design/ant-design-mobile
Length of output: 99
🏁 Script executed:
rg "useClickOutside" src/componentsRepository: ant-design/ant-design-mobile
Length of output: 489
🏁 Script executed:
Repository: ant-design/ant-design-mobile
Length of output: 1342
改进
useClickOutside的事件类型处理,消除类型不安全的强制转换。第 33 行的双重断言
event as unknown as MouseEvent存在类型安全风险。handleFocusIn接收FocusEvent但被强制转为MouseEvent,会掩盖实际的事件类型。建议将 handler 的类型改为联合类型(event: MouseEvent | FocusEvent) => void,去掉强制转换。建议修改
function handleFocusIn(event: FocusEvent) { if (!ref.current || ref.current.contains(event.target as Node)) { return } - stableHandler(event as unknown as MouseEvent) + stableHandler(event) }📝 Committable suggestion
🤖 Prompt for AI Agents