- 
                Notifications
    
You must be signed in to change notification settings  - Fork 2.5k
 
feat: 默认禁用光标位置可调整,支持通过 touchmove 调整光标位置 #6897
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
Conversation
          
📝 Walkthrough## Walkthrough
本次更改在 VirtualInput 组件中新增了 `cursor` 属性,支持用户通过点击字符或触摸拖动来调整光标位置(默认静态不可动)。更新了组件文档、样式和示例,并完善了相关的测试用例以覆盖光标位置调整的行为。
## Changes
| 文件/路径                                     | 变更摘要                                                         |
|-----------------------------------------------|------------------------------------------------------------------|
| src/components/virtual-input/index.zh.md      | 文档新增 `cursor` 属性说明,支持 `movable` 和 `onMove` 回调,默认 `{ movable: false }`。 |
| src/components/virtual-input/virtual-input.less | 样式中将光标动画类名改为变量,新增 `-caret-dragging` 状态以禁用闪烁动画。 |
| src/components/virtual-input/virtual-input.tsx | 新增 `cursor` 对象属性,实现光标点击和触摸拖动调整,增加相关状态、事件处理及回调支持。 |
| src/components/virtual-input/demos/demo1.tsx  | 在示例中添加 `cursor={{ movable: true }}` 属性,新增默认不可调光标示例,调整部分字体大小。 |
| src/components/virtual-input/tests/virtual-input.test.tsx | 新增触摸事件辅助函数,完善光标位置调整相关测试,统一模拟字符宽度,改进异步事件处理。 |
## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant User
    participant VirtualInput
    participant DOM
    User->>VirtualInput: 点击字符或触摸拖动(cursor.movable=true)
    VirtualInput->>VirtualInput: 计算并更新 caretPosition
    VirtualInput->>DOM: 渲染新光标位置
    User->>VirtualInput: 键盘输入或删除
    VirtualInput->>VirtualInput: 根据输入类型调整 caretPosition
    VirtualInput->>DOM: 更新显示
    VirtualInput->>User: 触发 onMove 回调(如有)Estimated code review effort3 (~45 minutes) Possibly related PRs
 Suggested reviewers
 Poem
  | 
    
          commit:   | 
    
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.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/virtual-input/virtual-input.tsx (1)
198-248: 触摸拖拽实现完善,建议提取魔法数字。实现了完整的触摸拖拽功能,包括边界处理和超时机制。建议将硬编码的阈值提取为常量。
+const TOUCH_DRAG_THRESHOLD = 20 // px +const TOUCH_DRAG_TIMEOUT = 200 // ms const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => { // ... - if (distance < 20) { + if (distance < TOUCH_DRAG_THRESHOLD) { // ... const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { // ... - }, 200) + }, TOUCH_DRAG_TIMEOUT)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/virtual-input.less(2 hunks)src/components/virtual-input/virtual-input.tsx(8 hunks)
🧰 Additional context used
🪛 GitHub Actions: Check
src/components/virtual-input/virtual-input.tsx
[warning] 181-181: React warning: An update to ForwardRef inside a test was not wrapped in act(...). See https://reactjs.org/link/wrap-tests-with-act
[warning] 55-55: React warning: An update to ForwardRef inside a test was not wrapped in act(...). See https://reactjs.org/link/wrap-tests-with-act
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: compressed-size
 - GitHub Check: build preview
 - GitHub Check: size
 
🔇 Additional comments (8)
src/components/virtual-input/virtual-input.less (2)
72-72: 使用动态类前缀变量保持一致性。将硬编码的类名改为使用变量形式,与文件中其他地方保持一致。
83-87: 拖拽时禁用光标闪烁效果。新增的拖拽状态样式合理地禁用了光标动画,提升了拖拽时的用户体验。
src/components/virtual-input/index.zh.md (1)
26-26: 文档更新准确完整。新增的
adjustableCaret属性文档说明清晰,格式规范。src/components/virtual-input/virtual-input.tsx (5)
28-28: 新属性定义合理。
adjustableCaret属性默认为false,确保了向后兼容性。Also applies to: 45-45
90-96: 字符宽度测量的潜在问题。当前实现假设所有字符宽度相同,这对于等宽字体有效,但对于包含中英文混合或特殊字符的内容可能不准确。
请确认是否需要支持可变宽度字符的场景。如果需要,可能需要更精确的光标定位算法。
97-109: 光标位置更新逻辑清晰。通过检查值是否与预期匹配来决定光标移动方式,处理了受控组件的边界情况。
185-196: 点击定位实现巧妙。通过判断点击位置相对于字符中心来决定光标位置,用户体验良好。
55-55: 测试中存在 React act() 警告。管道测试显示有状态更新未在
act()中包装。虽然不影响功能,但建议修复测试以消除警告。请检查相关测试代码,确保状态更新都正确包装在
act()中。Also applies to: 181-181
          Codecov ReportAll modified and coverable lines are covered by tests ✅ 
 Additional details and impacted files@@            Coverage Diff             @@
##           master    #6897      +/-   ##
==========================================
+ Coverage   92.80%   92.89%   +0.08%     
==========================================
  Files         336      336              
  Lines        7244     7318      +74     
  Branches     1784     1846      +62     
==========================================
+ Hits         6723     6798      +75     
+ Misses        513      484      -29     
- Partials        8       36      +28     ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
  | 
    
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.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/components/virtual-input/virtual-input.tsx (3)
65-65: 完善注释内容当前注释不够完整,建议补充说明该 ref 的具体用途。
- }>({}) // 临时记录虚拟键盘输入,在下次更新时用于判断光标位置如何调整 + }>({}) // 临时记录虚拟键盘输入的值和操作类型,用于在值更新后智能调整光标位置
93-98: 字符宽度测量可能不准确当前实现仅测量第一个字符的宽度,假设所有字符宽度相同。这在使用等宽字体时没问题,但对于比例字体或混合字符类型(如数字和小数点)可能导致光标定位不准确。
建议考虑更精确的测量方案,或在文档中说明此限制。
211-212: 触摸检测阈值应考虑屏幕密度硬编码的 20px 阈值在不同屏幕密度的设备上可能表现不一致。建议根据设备像素比调整或使其可配置。
- if (distance < 20) { - // 20px 阈值可调整 + const threshold = 20 * (window.devicePixelRatio || 1) + if (distance < threshold) { + // 根据设备像素比调整阈值或者考虑将其作为组件属性暴露,允许开发者自定义。
src/components/virtual-input/tests/virtual-input.test.tsx (2)
315-315: 使用更具体的断言建议使用更具体的断言来提高测试失败时的可读性。
- expect(caretContainer).toBeTruthy() + expect(caretContainer).not.toBeNull()Also applies to: 541-541, 621-621
492-654: 建议补充更多测试场景当前测试覆盖较好,但建议考虑增加以下测试场景:
- 多点触控的处理
 - 快速连续触摸的处理
 - 组件禁用状态下的触摸行为
 另外,建议在后续迭代中考虑:
- RTL(从右到左)文本的支持
 - 无障碍访问的影响(屏幕阅读器用户如何使用此功能)
 
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/virtual-input/demos/demo1.tsx(3 hunks)src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/tests/virtual-input.test.tsx(12 hunks)src/components/virtual-input/virtual-input.tsx(8 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/virtual-input/index.zh.md
 - src/components/virtual-input/demos/demo1.tsx
 
🧰 Additional context used
🪛 GitHub Check: codecov/patch
src/components/virtual-input/virtual-input.tsx
[warning] 248-249: src/components/virtual-input/virtual-input.tsx#L248-L249
Added lines #L248 - L249 were not covered by tests
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: size
 
🔇 Additional comments (5)
src/components/virtual-input/virtual-input.tsx (2)
28-28: 新增的属性定义合理新增的
allowCaretPositioning属性定义为可选布尔类型,默认值为false,保证了向后兼容性。Also applies to: 45-45
237-244: 超时处理机制设计良好使用超时机制来处理
touchend事件可能不触发的边缘情况是很好的防御性编程实践。src/components/virtual-input/tests/virtual-input.test.tsx (3)
84-103: 测试环境模拟设置合理通过 mock
getBoundingClientRect为所有字符设置统一宽度,确保了测试的一致性和可重复性。
492-587: 触摸拖动测试覆盖全面测试用例设计良好,覆盖了以下场景:
- 光标左右移动
 - 拖动状态的 CSS 类切换
 - 边界条件处理
 - 非光标附近的触摸事件处理
 测试实现质量高,场景覆盖完整。
589-654: 禁用状态测试完善正确验证了当
allowCaretPositioning未启用时,触摸和点击都不会改变光标位置,确保了功能的正确禁用。
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.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/components/virtual-input/virtual-input.tsx (1)
202-250: 优化触摸事件处理的用户体验。触摸事件处理逻辑基本正确,但建议进行以下优化:
- 考虑添加 preventDefault() 防止触摸时触发页面滚动
 - 调整超时时间 500ms 可能过长,建议减少到 200-300ms
 - 动态调整触摸阈值 20px 阈值可以根据设备 DPI 进行调整
 const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { if (!touchDataRef.current || mergedProps.cursor === 'locked') return + + e.preventDefault() setIsCaretDragging(true) // ... existing logic ... touchMoveTimeoutRef.current = setTimeout(() => { setIsCaretDragging(false) touchMoveTimeoutRef.current = null - }, 500) + }, 200) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/virtual-input/demos/demo1.tsx(3 hunks)src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/tests/virtual-input.test.tsx(12 hunks)src/components/virtual-input/virtual-input.tsx(8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/virtual-input/demos/demo1.tsx
 - src/components/virtual-input/index.zh.md
 - src/components/virtual-input/tests/virtual-input.test.tsx
 
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: compressed-size
 - GitHub Check: check
 - GitHub Check: build preview
 - GitHub Check: size
 
🔇 Additional comments (7)
src/components/virtual-input/virtual-input.tsx (7)
28-28: 新增的 cursor 属性设计合理。属性定义清晰,类型安全,默认值设置为 'locked' 保证了向后兼容性。
Also applies to: 45-45
61-61: 光标位置状态管理逻辑正确。状态初始化和更新逻辑合理,正确处理了键盘输入和外部值变更的场景。
Also applies to: 73-73, 100-111
66-74: Ref 类型定义已改进,字符宽度测量逻辑合理。很好地解决了之前关于 ref 类型的建议,现在使用了具体的类型定义。字符宽度测量对于光标位置计算是必要的。
Also applies to: 93-98
188-199: 字符点击定位逻辑实现正确。正确处理了禁用状态和锁定光标的判断,点击位置计算逻辑合理,事件传播控制得当。
145-164: 键盘输入与光标位置的集成处理正确。输入和删除操作正确使用了光标位置进行字符串操作,临时记录机制有助于后续位置调整。
260-260: DOM 结构更新合理支持新功能。拖拽状态样式类、触摸事件处理器、以及 ref 引用都正确集成到了 DOM 结构中。
Also applies to: 274-276, 280-280, 289-289
304-308: 清除按钮处理逻辑简化合理。移除显式的光标位置重置是正确的,因为值变更时的 effect 会自动将光标位置设置到末尾。
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.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/components/virtual-input/virtual-input.tsx (1)
222-245: 触摸移动事件处理需要优化性能。虽然逻辑正确,但频繁的状态更新可能影响性能。建议添加节流优化。
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { if (!touchDataRef.current || mergedProps.cursor === 'static') return setIsCaretDragging(true) const touch = e.touches[0] const deltaX = touch.clientX - touchDataRef.current.startX const charWidth = charWidthRef.current const moveChars = Math.round(deltaX / charWidth) let newCaretPosition = touchDataRef.current.startCaretPosition + moveChars // 边界处理 newCaretPosition = Math.max(0, Math.min(newCaretPosition, value.length)) - setCaretPosition(newCaretPosition) + + // 添加节流,避免频繁更新 + if (newCaretPosition !== caretPosition) { + setCaretPosition(newCaretPosition) + } // 防止 touchend 不触发 if (touchMoveTimeoutRef.current) { clearTimeout(touchMoveTimeoutRef.current) } touchMoveTimeoutRef.current = setTimeout(() => { setIsCaretDragging(false) touchMoveTimeoutRef.current = null }, 500) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/tests/virtual-input.test.tsx(12 hunks)src/components/virtual-input/virtual-input.tsx(8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/virtual-input/index.zh.md
 
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: size
 - GitHub Check: build preview
 - GitHub Check: compressed-size
 - GitHub Check: check
 
🔇 Additional comments (21)
src/components/virtual-input/virtual-input.tsx (12)
28-28: 光标属性定义正确。属性类型定义清晰,支持静态和可移动两种模式。
45-45: 默认值设置合理。默认为 'static' 模式符合向后兼容的设计原则。
60-74: 状态管理和引用定义良好。光标位置状态管理逻辑清晰,各个 ref 的用途明确,注释详细。过去的类型安全问题已得到解决。
93-98: 字符宽度测量实现正确。使用 useEffect 在值变化时重新测量字符宽度,确保光标位置计算准确。
100-111: 光标位置更新逻辑实现正确。根据键盘输入模式智能调整光标位置,处理了受控组件的边界情况。
189-199: 字符点击处理逻辑完善。正确处理了禁用状态和静态光标模式的检查,点击位置判断逻辑合理。
202-220: 触摸开始事件处理实现良好。距离阈值设置合理(20px),正确检查了禁用状态和静态模式。
247-250: 触摸结束事件处理简洁有效。正确清理了触摸数据和拖拽状态。
260-260: CSS 类名切换逻辑正确。动态添加拖拽状态的 CSS 类,用于控制光标闪烁动画。
274-276: 触摸事件绑定位置合适。在内容区域绑定触摸事件,确保用户可以在整个输入区域进行操作。
279-285: 字符渲染逻辑完善。正确设置了第一个字符的 ref 用于宽度测量,点击事件绑定恰当。
288-290: 光标渲染逻辑正确。添加了 ref 引用,便于触摸事件中的位置计算。
src/components/virtual-input/tests/virtual-input.test.tsx (9)
68-84: 触摸事件辅助函数实现正确。正确模拟了触摸事件的结构,支持不同类型的触摸事件和坐标位置。
87-106: DOM 元素模拟策略合理。通过模拟 getBoundingClientRect 为 SPAN 元素提供固定宽度,与组件中的字符宽度测量逻辑配合良好。
224-224: 测试用例正确更新。为需要光标移动功能的测试添加了
cursor='movable'属性。
254-256: 异步状态更新处理正确。使用
act()包装点击操作,确保 React 状态更新完成后再进行断言。
292-292: 测试配置一致性良好。在所有需要光标移动的测试中统一添加了
cursor='movable'属性。
318-360: 光标点击测试覆盖全面。测试涵盖了左右点击、键盘输入、删除操作等各种场景下的光标位置变化,使用
act()正确处理异步更新。
421-461: 受控组件测试更新恰当。在复杂的受控组件场景中正确使用
act()处理点击事件,确保测试稳定性。
495-607: 触摸拖拽测试实现全面。测试涵盖了触摸开始、移动、结束的完整流程,包括:
- 光标位置的精确计算和舍入逻辑
 - 拖拽状态的 CSS 类切换
 - 超时机制的验证
 - 边界条件的处理
 测试逻辑清晰,断言准确。
609-674: 禁用状态测试完整。正确验证了在默认(静态)模式下,触摸移动和字符点击都不会改变光标位置,确保了向后兼容性。
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.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/components/virtual-input/virtual-input.tsx (2)
250-250: 触摸拖拽体验问题需要关注。根据之前的评论,用户反映拖拽手感奇怪,有时候拖得动有时候拖不动。建议进行以下优化:
- 增加触摸反馈
 - 优化拖拽阈值判断
 - 增加视觉反馈提示用户可以拖拽
 建议测试以下场景:
- 在不同设备上的拖拽体验
 - 快速连续拖拽时的响应性
 - 边界情况下的光标位置准确性
 需要验证拖拽功能在实际使用中的稳定性和用户体验。
67-75: 状态管理过于复杂,建议优化ref的使用。根据之前的评论,这里确实存在ref过多的问题。建议考虑以下优化:
charWidthRef可以考虑使用状态管理touchMoveTimeoutRef可以使用useCallback配合clearTimeout来管理- 部分ref数据可以合并到一个对象中
 - const touchDataRef = useRef<{ - startX: number - startCaretPosition: number - } | null>() // 记录上一次 touch 时的坐标位置 - const charRef = useRef<HTMLElement>(null) // 第一个字符的 DOM - const charWidthRef = useRef<number>(0) // 单个字符宽度 - const caretRef = useRef<HTMLDivElement>(null) // 光标的 DOM - const [isCaretDragging, setIsCaretDragging] = useState<boolean>(false) - const touchMoveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>() + const charRef = useRef<HTMLElement>(null) + const caretRef = useRef<HTMLDivElement>(null) + const [charWidth, setCharWidth] = useState<number>(0) + const [isCaretDragging, setIsCaretDragging] = useState<boolean>(false) + const [touchData, setTouchData] = useState<{ + startX: number + startCaretPosition: number + } | null>(null)
🧹 Nitpick comments (2)
src/components/virtual-input/virtual-input.tsx (2)
94-99: 字符宽度计算可能存在性能问题。每次
value变化都会重新计算字符宽度,但通常字符宽度是固定的。建议只在组件首次渲染或字体相关属性变化时计算。- useEffect(() => { - // 记录单个字符的宽度,用于光标移动时的计算 - if (charRef.current) { - charWidthRef.current = charRef.current.getBoundingClientRect().width - } - }, [value]) + useEffect(() => { + // 记录单个字符的宽度,用于光标移动时的计算 + if (charRef.current && charWidthRef.current === 0) { + charWidthRef.current = charRef.current.getBoundingClientRect().width + } + }, [value])
208-226: 触摸开始事件的阈值判断需要优化。20px的硬编码阈值可能在不同设备上表现不一致,建议考虑使用相对值或可配置的阈值。
- if (distance < 20) { - // 20px 阈值可调整 + const threshold = Math.max(20, charWidthRef.current) // 使用字符宽度或20px的较大值 + if (distance < threshold) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/virtual-input.tsx(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/virtual-input/index.zh.md
 
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: compressed-size
 - GitHub Check: build preview
 - GitHub Check: size
 - GitHub Check: check
 
🔇 Additional comments (5)
src/components/virtual-input/virtual-input.tsx (5)
28-29: 新增属性定义合理,类型定义清晰。
cursor和onCursorMove属性的定义符合组件设计需求,类型定义也很清晰。
101-112: 光标位置更新逻辑清晰合理。通过
keyboardDataRef来区分输入和删除操作,相应调整光标位置的逻辑是正确的。边界处理也得当。
192-205: 字符点击事件处理逻辑正确。通过计算点击位置相对于字符中心的偏移来决定光标位置,逻辑合理。
e.stopPropagation()的使用也是必要的。
281-283: 触摸事件绑定位置合适。在 content 容器上绑定触摸事件可以确保整个输入区域都能响应触摸操作。
295-297: 光标渲染优化良好。只在获得焦点时渲染光标,避免了不必要的DOM操作,同时通过ref引用便于后续的位置计算。
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.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/components/virtual-input/virtual-input.tsx (3)
71-79: 建议将部分 ref 改为 state过多使用 ref 存储数据不符合 React 最佳实践。建议将
touchDataRef改为 state,因为它的改变会影响组件行为。- const touchDataRef = useRef<{ - startX: number - startCaretPosition: number - } | null>() // 记录上一次 touch 时的坐标位置 + const [touchData, setTouchData] = useState<{ + startX: number + startCaretPosition: number + } | null>(null) // 记录上一次 touch 时的坐标位置
232-256: 触摸事件处理存在多个问题
- 缺少
 preventDefault()导致可能触发页面滚动- 500ms 超时机制可能导致状态不一致
 - 拖动行为不符合预期(应该选中范围而非移动光标)
 建议修改实现:
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { if (!touchDataRef.current || !mergedProps.cursor?.movable) return + e.preventDefault() // 防止页面滚动 setIsCaretDragging(true) const touch = e.touches[0] const deltaX = touch.clientX - touchDataRef.current.startX const charWidth = charWidthRef.current const moveChars = Math.round(deltaX / charWidth) let newCaretPosition = touchDataRef.current.startCaretPosition + moveChars // 边界处理 newCaretPosition = Math.max(0, Math.min(newCaretPosition, value.length)) setCaretPosition(newCaretPosition) mergedProps.cursor?.onMove?.(newCaretPosition) - - // 防止 touchend 不触发 - if (touchMoveTimeoutRef.current) { - clearTimeout(touchMoveTimeoutRef.current) - } - touchMoveTimeoutRef.current = setTimeout(() => { - setIsCaretDragging(false) - touchMoveTimeoutRef.current = null - }, 500) }同时需要在组件卸载时清理定时器:
+ useEffect(() => { + return () => { + if (touchMoveTimeoutRef.current) { + clearTimeout(touchMoveTimeoutRef.current) + } + } + }, [])
258-261: touchEnd 处理需要清理定时器当前实现未清理可能存在的定时器。
const handleTouchEnd = () => { touchDataRef.current = null setIsCaretDragging(false) + if (touchMoveTimeoutRef.current) { + clearTimeout(touchMoveTimeoutRef.current) + touchMoveTimeoutRef.current = null + } }
🧹 Nitpick comments (1)
src/components/virtual-input/tests/virtual-input.test.tsx (1)
612-677: 建议补充更多测试场景当前测试覆盖较好,但建议增加以下场景:
- 组件卸载时的清理逻辑
 - 快速连续触摸的场景
 - 空值时的光标行为
 需要我帮您生成这些额外的测试用例吗?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/virtual-input/demos/demo1.tsx(3 hunks)src/components/virtual-input/index.zh.md(1 hunks)src/components/virtual-input/tests/virtual-input.test.tsx(12 hunks)src/components/virtual-input/virtual-input.tsx(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/virtual-input/demos/demo1.tsx
 - src/components/virtual-input/index.zh.md
 
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build preview
 - GitHub Check: compressed-size
 - GitHub Check: check
 - GitHub Check: size
 
🔇 Additional comments (2)
src/components/virtual-input/virtual-input.tsx (1)
21-51: 类型定义和默认值设置合理
Cursor类型定义清晰,默认禁用光标移动的设计符合 PR 目标。src/components/virtual-input/tests/virtual-input.test.tsx (1)
498-610: 触摸移动测试覆盖全面测试用例很好地覆盖了触摸移动的各种场景,包括:
- 双向拖动
 - CSS 类切换
 - 超时状态重置
 - 触摸区域判断
 
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.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/components/virtual-input/virtual-input.tsx (3)
71-79: ref 使用过多,建议优化状态管理。当前实现使用了大量 ref 来存储状态数据,这与 React 推荐的状态管理方式不符。建议将部分 ref 转换为 state。
-const touchDataRef = useRef<{ - startX: number - startCaretPosition: number -} | null>() +const [touchData, setTouchData] = useState<{ + startX: number + startCaretPosition: number +} | null>(null) -const [isCaretDragging, setIsCaretDragging] = useState<boolean>(false) -const touchMoveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>() +const [isCaretDragging, setIsCaretDragging] = useState<boolean>(false) +const [touchMoveTimeout, setTouchMoveTimeout] = useState<ReturnType<typeof setTimeout> | null>(null)
232-256: 触摸移动事件处理存在已知问题。正如之前的评论所指出的,当前的超时机制可能导致状态不一致。建议直接在
touchend事件中处理状态重置。另外,缺少对组件卸载时定时器的清理,可能导致内存泄漏:
+useEffect(() => { + return () => { + if (touchMoveTimeoutRef.current) { + clearTimeout(touchMoveTimeoutRef.current) + } + } +}, [])
254-254: 触摸交互体验问题需要解决。如评论中提到的,当前拖拽手感不稳定,有时能拖动有时不能。更重要的是,拖动操作的预期行为应该是选中文本范围而不是移动光标位置。建议重新考虑交互设计:
- 拖动应该创建文本选区,而不是移动光标
 - 单击才应该移动光标位置
 - 考虑添加haptic feedback提升触摸体验
 
🧹 Nitpick comments (2)
src/components/virtual-input/virtual-input.tsx (2)
105-116: 光标位置更新逻辑存在潜在问题。当前逻辑依赖
keyboardDataRef.current.newValue与value的比较来判断是否为外部更新,但这种方式在某些边缘情况下可能不够准确,比如用户输入相同的值。建议添加一个更明确的标识来区分输入来源:
const keyboardDataRef = useRef<{ newValue?: string mode?: 'input' | 'delete' + timestamp?: number }>({})
212-230: 触摸开始事件处理需要改进。当前实现使用固定的 20px 阈值来判断是否在光标附近,这个值可能需要根据不同设备的屏幕密度进行调整。
-if (distance < 20) { +const threshold = window.devicePixelRatio * 15 // 根据设备像素比调整 +if (distance < threshold) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/virtual-input/virtual-input.tsx(7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: compressed-size
 - GitHub Check: build
 - GitHub Check: size
 - GitHub Check: build preview
 - GitHub Check: check
 
🔇 Additional comments (8)
src/components/virtual-input/virtual-input.tsx (8)
21-24: 类型定义清晰且合理。新增的
Cursor类型定义结构合理,movable和onMove都是可选属性,符合设计需求。
33-33: Props 接口和默认值设计合理。
cursor属性的默认值{ movable: false }符合 PR 目标中提到的"默认禁用光标位置可调整"的需求。Also applies to: 50-50
98-103: 字符宽度测量逻辑合理。在 value 变化时重新测量字符宽度是必要的,实现方式正确。
196-209: 字符点击事件处理逻辑正确。通过计算点击位置相对于字符中心的位置来确定光标位置的逻辑合理,并正确调用了
onMove回调。
258-266: 触摸结束事件处理已改进。相比之前的版本,现在正确清理了所有相关的定时器和状态,确保组件状态的一致性。
274-277: 拖拽状态的 CSS 类名应用合理。通过
isCaretDragging状态动态添加 CSS 类来禁用光标闪烁动画是一个好的设计。
290-292: 触摸事件绑定位置合适。将触摸事件绑定到 content 容器上确保了事件能够正确捕获,同时不会影响其他交互。
304-307: 光标渲染条件合理。只在输入框获得焦点时渲染光标,符合用户体验预期。通过
caretRef获取光标元素用于触摸事件处理也是必要的。

Summary by CodeRabbit
cursor,支持用户通过点击字符或触摸拖动调整光标位置,默认值为{ movable: false }(不可调整)。cursor属性说明。cursor的行为。