Skip to content

[codex] fix useLockFocus retry when element is filled later#748

Merged
zombieJ merged 1 commit intomasterfrom
fix/use-lock-focus-late-element
Apr 7, 2026
Merged

[codex] fix useLockFocus retry when element is filled later#748
zombieJ merged 1 commit intomasterfrom
fix/use-lock-focus-late-element

Conversation

@zombieJ
Copy link
Copy Markdown
Member

@zombieJ zombieJ commented Apr 7, 2026

Summary

  • add a small useRetryEffect helper in src/Dom/focus.ts to retry an effect until a late-filled element becomes available
  • update useLockFocus to use that retry flow so locking still starts when the target ref is populated via a later state update
  • add a regression test covering the delayed element case

Why

useLockFocus previously only retried when [lock, id] changed. If the target element was filled by a later state update, the initial effect could miss the element and never lock focus.

Impact

Components that provide the lock target through a state-backed ref now still get focus locking after the element appears.

Validation

  • npx tsc --noEmit
  • npx eslint src/Dom/focus.ts tests/focus.test.tsx
  • npm test -- tests/focus.test.tsx --runInBand

Summary by CodeRabbit

发布说明

  • 错误修复

    • 改进了焦点管理的可靠性,确保在DOM元素延迟加载或动态渲染时,焦点锁定能够正确执行。
  • 测试

    • 添加了新的测试用例,验证动态元素加载场景下的焦点锁定行为。

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
util Ready Ready Preview, Comment Apr 7, 2026 6:51am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Walkthrough

更新了 src/Dom/focus.ts 中的焦点锁定逻辑,新增 useRetryEffect 辅助钩子以处理元素初始缺失的情况,并重构 useLockFocus 以支持重试机制。同时在测试文件中验证了此重试行为。

Changes

Cohort / File(s) Summary
焦点锁定核心逻辑
src/Dom/focus.ts
新增 useRetryEffect 钩子实现重试机制;引入 getElementRef 缓存最新的获取元素函数;将单次 useEffect 调用替换为 useRetryEffect,支持在元素初始缺失时进行重试。
焦点锁定测试
tests/focus.test.tsx
新增测试用例验证延迟出现的元素场景,通过 waitFor 确认焦点最终锁定到容器元素,验证一次重试行为。

Sequence Diagram(s)

sequenceDiagram
    participant Component
    participant useRetryEffect
    participant State as State (retryMark)
    participant Ref as Ref (getElementRef)
    participant DOM

    Component->>useRetryEffect: 调用 useRetryEffect(lockEffect, [id, lock])
    
    rect rgba(100, 150, 200, 0.5)
    Note over useRetryEffect,DOM: 首次效果周期
    useRetryEffect->>Ref: 读取 getElementRef.current()
    Ref->>DOM: 尝试获取目标元素
    DOM-->>Ref: 元素缺失 (undefined)
    Ref-->>useRetryEffect: 返回 ready: false
    useRetryEffect->>State: 更新 retryMark 触发重新渲染
    end
    
    rect rgba(100, 200, 150, 0.5)
    Note over useRetryEffect,DOM: 重试效果周期
    useRetryEffect->>Ref: 读取更新后的 getElementRef.current()
    Ref->>DOM: 尝试获取目标元素
    DOM-->>Ref: 元素存在
    Ref-->>useRetryEffect: 返回 ready: true
    useRetryEffect->>Component: 调用 lockFocus(...) 锁定焦点
    useRetryEffect->>Component: 完成,停止重试
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #727: 同样修改 src/Dom/focus.tsuseLockFocus 实现,涉及每个锁定的 ID 和 ignoreElement 处理的变更,代码级别相关。
  • PR #712: 同样修改 src/Dom/focus.ts 中的 useLockFocus 实现,当前 PR 为其添加元素缺失时的重试机制,代码级别直接相关。

Poem

🐰 焦点在跳舞,元素躲躲藏,
重试钩子来帮忙,耐心等一等,
一二再检查,焦点锁得牢!✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰地总结了主要变更:为 useLockFocus 添加了当元素后续填充时的重试机制。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/use-lock-focus-late-element

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 95.23810% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 86.42%. Comparing base (7e36953) to head (36dacc0).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/Dom/focus.ts 95.23% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #748      +/-   ##
==========================================
+ Coverage   86.31%   86.42%   +0.11%     
==========================================
  Files          39       39              
  Lines        1052     1068      +16     
  Branches      372      388      +16     
==========================================
+ Hits          908      923      +15     
- Misses        142      143       +1     
  Partials        2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a useRetryEffect hook to handle scenarios where DOM elements are not immediately available for focus locking. The useLockFocus hook has been refactored to utilize this retry mechanism, and a new test case verifies that focus is correctly applied even if the target element appears after the initial render. Review feedback suggests increasing the hardcoded retry limit to better support asynchronous state updates and notes that spreading dependencies in the useEffect hook bypasses static analysis linting rules.

@zombieJ zombieJ marked this pull request as ready for review April 7, 2026 07:38
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/focus.test.tsx (1)

127-129: 建议补充“聚焦外部元素后被拉回”的断言(增强锁定语义)

当前只验证了最终会聚焦到容器。可再补一条外部按钮聚焦后的断言,确认延迟场景下锁定行为也生效。

可选补充断言(示例)
       await waitFor(() => {
         expect(document.activeElement).toBe(focusContainer);
       });
+
+      const outerButton = getByTestId('outer-button') as HTMLButtonElement;
+      outerButton.focus();
+      expect(document.activeElement).toBe(focusContainer);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/focus.test.tsx` around lines 127 - 129, Add an extra assertion that
verifies an external element's focus is immediately pulled back to the locked
container: after focusing the outside button (simulate focus on the outside
button element used in the test), assert that document.activeElement is not the
external button and instead becomes focusContainer (in addition to the existing
waitFor that ensures final focus). Locate the spot near the existing
waitFor/expect using waitFor, document.activeElement and focusContainer and add
the external-button-focus then assert it gets redirected back to focusContainer
to strengthen lock semantics.
src/Dom/focus.ts (1)

275-275: 重试上限硬编码为 1 次,建议提升为可配置常量

Line 275 在第二次尝试后就停止重试;如果元素在更晚的异步阶段才出现,仍可能锁定失败。建议改为可配置上限(或至少使用命名常量),减少边界场景遗漏。

可选改法(示例)
+const LOCK_FOCUS_MAX_RETRY = 5;
+
   const lockEffect = (retryTimes: number): RetryEffectResult => {
     if (!lock) {
       return [undefined, true];
     }

     const element = getElementRef.current();
     if (element) {
       return [lockFocus(element, id), true];
     }

-    return [undefined, retryTimes >= 1];
+    return [undefined, retryTimes >= LOCK_FOCUS_MAX_RETRY];
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Dom/focus.ts` at line 275, The return currently hard-codes the retry
limit via "retryTimes >= 1", which stops after one retry; change this to use a
named constant or configurable parameter (e.g., MAX_RETRIES or a function arg
maxRetries) instead of the literal 1 so the limit is adjustable; update the code
that reads/updates retryTimes and the call sites to pass/configure the new
maxRetries (or import the constant) and replace the condition "retryTimes >= 1"
with "retryTimes >= MAX_RETRIES" (or the param name) to make retries
configurable and avoid the hard-coded boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Dom/focus.ts`:
- Line 275: The return currently hard-codes the retry limit via "retryTimes >=
1", which stops after one retry; change this to use a named constant or
configurable parameter (e.g., MAX_RETRIES or a function arg maxRetries) instead
of the literal 1 so the limit is adjustable; update the code that reads/updates
retryTimes and the call sites to pass/configure the new maxRetries (or import
the constant) and replace the condition "retryTimes >= 1" with "retryTimes >=
MAX_RETRIES" (or the param name) to make retries configurable and avoid the
hard-coded boundary.

In `@tests/focus.test.tsx`:
- Around line 127-129: Add an extra assertion that verifies an external
element's focus is immediately pulled back to the locked container: after
focusing the outside button (simulate focus on the outside button element used
in the test), assert that document.activeElement is not the external button and
instead becomes focusContainer (in addition to the existing waitFor that ensures
final focus). Locate the spot near the existing waitFor/expect using waitFor,
document.activeElement and focusContainer and add the external-button-focus then
assert it gets redirected back to focusContainer to strengthen lock semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a7362d3-f496-4405-9a05-15d60eeb1f1c

📥 Commits

Reviewing files that changed from the base of the PR and between 7e36953 and 36dacc0.

📒 Files selected for processing (2)
  • src/Dom/focus.ts
  • tests/focus.test.tsx

@zombieJ zombieJ merged commit be0eed1 into master Apr 7, 2026
12 checks passed
@zombieJ zombieJ deleted the fix/use-lock-focus-late-element branch April 7, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants