Skip to content

Conversation

Linkodt
Copy link

@Linkodt Linkodt commented Oct 2, 2025

close ant-design/ant-design#55177

Summary by CodeRabbit

  • 新功能
    • Tour 组件新增可选键盘导航(通过 keyboard 属性开启,默认关闭),支持 ArrowLeft/ArrowRight 切换步骤与 Esc 关闭,包含边界保护并与现有交互兼容。
  • 文档
    • 新增 Keyboard 演示页面与示例,展示启用键盘导航、多目标步骤及演示用组件的使用方式。

Copy link

vercel bot commented Oct 2, 2025

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

Project Deployment Preview Comments Updated (UTC)
tour Ready Ready Preview Comment Oct 2, 2025 7:37am

Copy link

coderabbitai bot commented Oct 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

本次变更为 Tour 组件新增可选 keyboard 属性并实现全局键盘处理,支持左右箭头切换步骤与 Esc 关闭;同步更新类型定义;新增示例 docs/examples/keyboard.tsx 与文档页面 docs/demo/keyboard.md 引用该示例。

Changes

Cohort / File(s) Change Summary
Docs: Demo 页面
docs/demo/keyboard.md
新增文档页面,包含 frontmatter 并嵌入示例 ../examples/keyboard.tsx 的代码。
Docs: 示例代码
docs/examples/keyboard.tsx
新增示例组件:创建三个可聚焦按钮并通过 refs 作为 Tour 步骤目标;在 Tour 中启用 keyboard,设置 stepsdefaultCurrent;导出默认 App
组件逻辑:Tour 键盘支持
src/Tour.tsx
在 Tour props 中新增 keyboard(默认 false);新增统一 handleClose;实现 handleKeyDown 处理 ArrowLeft/ArrowRight/Escape 并在 keyboard 为真时挂载/卸载全局 keydown 监听;移除重复本地 handleClose
类型:接口更新
src/interface.ts
TourProps 中新增可选属性 keyboard?: boolean

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 用户
  participant Win as Window
  participant Tour as Tour 组件
  participant State as 状态(current, open, steps)
  participant Host as onClose 回调

  User->>Win: 键盘按键 (← / → / Esc)
  Win->>Tour: keydown 事件(仅在 keyboard = true 时)
  alt ArrowLeft
    Tour->>State: current = max(0, current - 1)
    Tour-->>User: 阻止默认/冒泡
  else ArrowRight
    Tour->>State: current = min(last, current + 1)
    Tour-->>User: 阻止默认/冒泡
  else Escape
    Tour->>State: open = false
    Tour->>Host: onClose(current)
    Tour-->>User: 阻止默认/冒泡
  end
  note right of Tour: 全局监听在组件挂载/更新时注册,卸载时清理
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

小兔轻敲左与右,步步顺风又明快;
Esc 一按灯归位,旅途短暂停片刻。
键声牵引指向处,指南更稳更乖巧。 🐇⌨️

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 该标题明确概述了主要改动,即为 Tour 组件添加键盘操作支持,清晰简洁地传达了此次变更的核心内容。
Linked Issues Check ✅ Passed 实现了使用 Esc 键关闭 Tour、使用左右方向键在步骤间导航的功能,并通过新增 keyboard 属性控制全局键盘事件,完全满足 #55177 对键盘操作和可访问性的需求。
Out of Scope Changes Check ✅ Passed 本次提交仅添加了与键盘操作相关的功能、示例示例和文档,未包含与 #55177 目标无关的改动,保持了变更范围的聚焦。
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

Summary of Changes

Hello @Linkodt, 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!

This pull request significantly enhances the accessibility and usability of the Tour component by introducing comprehensive keyboard navigation support. Users can now seamlessly navigate through tour steps using arrow keys and dismiss the tour with the Escape key, providing a more inclusive and efficient user experience. A new demo has also been added to illustrate this feature.

Highlights

  • Keyboard Navigation Support: Introduced a new keyboard prop to the Tour component, allowing users to enable or disable keyboard navigation.
  • Enhanced User Controls: Implemented keyboard controls for the Tour component: ArrowLeft to go to the previous step, ArrowRight to go to the next step, and Escape to close the tour.
  • New Demo Page: Added a new demo page and example (docs/demo/keyboard.md and docs/examples/keyboard.tsx) to showcase the new keyboard operation functionality.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@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 keyboard navigation for the Tour component, which is a great feature enhancement. The implementation is mostly solid, including the addition of a new demo page. However, I've identified a potential bug in the keyboard event handler related to stale closures due to missing dependencies in a useCallback. I've provided a detailed comment with a suggested fix that also improves code readability. Additionally, I've noted a minor design consideration regarding the use of a global event listener that could be improved for more complex use cases.

src/Tour.tsx Outdated
Comment on lines 168 to 196
const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
if (!mergedOpen) {
return;
}
if (e.key === 'ArrowLeft') {
if (mergedCurrent <= 0) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent - 1);
return;
}
if (e.key === 'ArrowRight') {
if (mergedCurrent >= steps.length - 1) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent + 1);
return;
}
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
handleClose();
return;
}
}, [mergedCurrent, mergedOpen, steps.length]);

Choose a reason for hiding this comment

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

high

The implementation of handleKeyDown has a potential bug and can be improved for readability.

  1. Stale Closures: The useCallback hook for handleKeyDown is missing onInternalChange and handleClose in its dependency array. This can lead to bugs where stale versions of these functions are called if the onChange or onClose props change. Adding them to the dependency array is necessary for correctness.

  2. Readability: The chain of if statements for key handling can be refactored into a switch statement. This improves readability and maintainability.

Important Note on Performance: Adding the missing dependencies will fix the bug, but since onInternalChange and handleClose are not memoized, handleKeyDown will be recreated on every render. This will cause the useEffect at line 198 to remove and re-add the global event listener on every render, which can be a performance concern. The ideal solution would be to also wrap onInternalChange and handleClose in useCallback where they are defined.

  const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
    if (!mergedOpen) {
      return;
    }

    switch (e.key) {
      case 'ArrowLeft':
        if (mergedCurrent > 0) {
          e.preventDefault();
          e.stopPropagation();
          onInternalChange(mergedCurrent - 1);
        }
        break;
      case 'ArrowRight':
        if (mergedCurrent < steps.length - 1) {
          e.preventDefault();
          e.stopPropagation();
          onInternalChange(mergedCurrent + 1);
        }
        break;
      case 'Escape':
        e.preventDefault();
        e.stopPropagation();
        handleClose();
        break;
      default:
        // Do nothing for other keys
        break;
    }
  }, [mergedCurrent, mergedOpen, steps.length, onInternalChange, handleClose]);

Comment on lines 198 to 205
React.useEffect(() => {
if (keyboard) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown, keyboard]);

Choose a reason for hiding this comment

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

medium

Attaching the event listener directly to document can lead to issues if multiple Tour components are rendered and open at the same time, as they would all react to the keyboard events. While it's unlikely to have multiple tours open simultaneously, a more robust implementation might involve a mechanism to ensure only the top-most or most recently interacted-with tour handles the keyboard events. This is a common pattern in component libraries for managing global listeners for modals and dialogs. This is something to consider for future improvements to prevent potential conflicts in more complex applications.

Copy link

@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.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/interface.ts (1)

92-92: 建议添加 JSDoc 注释以改进 API 文档。

虽然类型定义正确,但建议为这个新的公共 API 属性添加 JSDoc 注释,说明其用途、默认值和行为。这将改善开发者体验和 IDE 自动完成提示。

应用此差异添加文档注释:

+  /** Enable keyboard navigation (ArrowLeft/Right for steps, Escape to close). @default false */
   keyboard?: boolean;
src/Tour.tsx (3)

168-196: 建议检查修饰键以改善用户体验。

当前实现会拦截所有 ArrowLeft/Right 按键,这可能干扰用户在输入框中使用 Ctrl+Arrow 进行单词导航等操作。建议仅在没有修饰键时处理方向键。

应用此差异添加修饰键检查:

   const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
     if (!mergedOpen) {
       return;
     }
+    // 忽略带有修饰键的按键事件
+    if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) {
+      return;
+    }
     if (e.key === 'ArrowLeft') {

198-205: 频繁的监听器重新注册可能影响性能。

由于 handleKeyDown 依赖于频繁变化的状态(mergedCurrent),useEffect 会在每次步骤切换时移除并重新添加监听器。虽然功能正常,但可以通过使用 ref 存储最新状态来优化。

优化方案:

+  const stateRef = React.useRef({ mergedCurrent, mergedOpen, stepsLength: steps.length });
+  
+  React.useEffect(() => {
+    stateRef.current = { mergedCurrent, mergedOpen, stepsLength: steps.length };
+  }, [mergedCurrent, mergedOpen, steps.length]);
+
-  const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
-    if (!mergedOpen) {
+  const handleKeyDown = React.useCallback(() => {
+    const { mergedOpen, mergedCurrent, stepsLength } = stateRef.current;
+    if (!mergedOpen) {
       return;
     }
     // ... 其余逻辑使用 ref 中的值
-  }, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange]);
+  }, [handleClose, onInternalChange]);

这样监听器只在 keyboard prop 变化时重新注册。


61-205: 建议增强键盘导航的可访问性。

当前实现功能正常,但可以通过以下方式改善可访问性和用户体验:

  1. 焦点管理:Tour 打开时,考虑将焦点移至 Tour 内容,关闭时恢复焦点到之前的元素
  2. 视觉提示:在 Tour 界面上添加键盘快捷键提示(如 "使用 ← → 导航,ESC 关闭")
  3. 焦点陷阱:当 keyboard={true} 时,考虑实现焦点陷阱(focus trap),防止 Tab 键跳出 Tour 区域
  4. ARIA 属性:添加 aria-keyshortcuts 属性说明可用的键盘快捷键

示例:在 Tour 内容中添加键盘提示:

// 在 TourStep 组件中添加
<div className={`${prefixCls}-keyboard-hint`} aria-live="polite">
  使用   键导航,按 ESC 关闭
</div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c25065a and e891e70.

📒 Files selected for processing (4)
  • docs/demo/keyboard.md (1 hunks)
  • docs/examples/keyboard.tsx (1 hunks)
  • src/Tour.tsx (2 hunks)
  • src/interface.ts (1 hunks)
🔇 Additional comments (4)
docs/demo/keyboard.md (1)

1-8: 文档结构正确!

文档格式符合规范,正确引用了示例代码。

src/Tour.tsx (3)

61-61: 默认值设置合理!

keyboard 默认设为 false 是明智的选择,采用渐进增强的方式,不会影响现有用户。


162-165: handleClose 实现正确!

函数正确地关闭 Tour 并通知父组件当前索引,逻辑清晰。


228-228: 正确使用 handleClose!

handleClose 传递给 TourSteponClose prop 是正确的,确保了关闭按钮和 Escape 键行为的一致性。

Comment on lines +29 to +46
<Tour
defaultCurrent={0}
keyboard={true}
steps={[
{
title: 'One',
target: () => firstBtnRef.current,
},
{
title: 'Two',
target: () => secondBtnRef.current,
},
{
title: 'Three',
target: () => thirdBtnRef.current,
},
]}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

示例缺少 Tour 可见性控制。

当前示例中 Tour 组件始终处于打开状态(依赖 defaultCurrent 但没有 open prop 控制)。这可能让用户难以理解如何在实际应用中初始打开和关闭 Tour。

建议添加状态控制和触发按钮来演示完整的使用场景:

+import React, { useRef, useState } from 'react';
-import React, { useRef } from 'react';
 import Tour from '../../src/index';
 import './basic.less';

 const App = () => {
+  const [open, setOpen] = useState(false);
   const firstBtnRef = useRef<HTMLButtonElement>(null);
   const secondBtnRef = useRef<HTMLButtonElement>(null);
   const thirdBtnRef = useRef<HTMLButtonElement>(null);
   return (
     <React.StrictMode>
     <div style={{ margin: 20 }}>
+      <button onClick={() => setOpen(true)}>开始导览</button>
       <div>
         <button
           className="ant-target"
           ref={firstBtnRef}
         >
           One
         </button>
         {/* ... */}
       </div>

       <Tour
+        open={open}
+        onClose={() => setOpen(false)}
         defaultCurrent={0}
         keyboard={true}
         steps={[
           {/* ... */}
         ]}
       />
     </div>
     </React.StrictMode>
   );
 };
📝 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.

Suggested change
<Tour
defaultCurrent={0}
keyboard={true}
steps={[
{
title: 'One',
target: () => firstBtnRef.current,
},
{
title: 'Two',
target: () => secondBtnRef.current,
},
{
title: 'Three',
target: () => thirdBtnRef.current,
},
]}
/>
import React, { useRef, useState } from 'react';
import Tour from '../../src/index';
import './basic.less';
const App = () => {
const [open, setOpen] = useState(false);
const firstBtnRef = useRef<HTMLButtonElement>(null);
const secondBtnRef = useRef<HTMLButtonElement>(null);
const thirdBtnRef = useRef<HTMLButtonElement>(null);
return (
<React.StrictMode>
<div style={{ margin: 20 }}>
<button onClick={() => setOpen(true)}>开始导览</button>
<div>
<button
className="ant-target"
ref={firstBtnRef}
>
One
</button>
{/* ...other buttons... */}
</div>
<Tour
open={open}
onClose={() => setOpen(false)}
defaultCurrent={0}
keyboard={true}
steps={[
{
title: 'One',
target: () => firstBtnRef.current,
},
{
title: 'Two',
target: () => secondBtnRef.current,
},
{
title: 'Three',
target: () => thirdBtnRef.current,
},
]}
/>
</div>
</React.StrictMode>
);
};
export default App;
🤖 Prompt for AI Agents
In docs/examples/keyboard.tsx around lines 29 to 46, the Tour is always shown
because it only uses defaultCurrent and lacks open state control; add a boolean
state (e.g., const [open, setOpen] = useState(false)) and pass open={open} and
onClose={() => setOpen(false)} to the Tour, add a trigger button that calls
setOpen(true) to open the tour, and ensure any initial step control still uses
defaultCurrent or useCurrent as needed so the example demonstrates opening,
closing, and triggering the Tour in a real app.

Comment on lines 198 to 205
React.useEffect(() => {
if (keyboard) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown, keyboard]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

多个 Tour 实例可能导致键盘事件冲突。

如果页面上同时挂载多个启用 keyboard 的 Tour 实例,所有实例的监听器都会触发,可能导致意外的行为或冲突。建议添加逻辑确保只有当前活动的 Tour 实例处理键盘事件。

可能的解决方案:

  1. 使用事件目标检查或 z-index 比较确定哪个 Tour 应该处理事件
  2. 使用全局状态管理确保只有一个 Tour 监听键盘事件
  3. 在文档中明确说明不支持多个同时打开的 Tour 使用键盘导航

示例修复(检查 Tour 是否打开):

   const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
-    if (!mergedOpen) {
+    if (!mergedOpen || !keyboard) {
       return;
     }

并在文档中说明此限制。

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Tour.tsx around lines 198-205, multiple mounted Tour instances that
enable keyboard can all add the same keydown listener causing conflicts; modify
the effect to only attach the listener when this Tour is the active/open one
(e.g., check isOpen/active state or an activeTourId from context/global store)
and include that same condition in the cleanup so only the active instance
handles key events; alternatively register a single global keyboard handler that
routes events to the active Tour by id; update docs to state that keyboard
navigation only applies to the active/open Tour instance.

Copy link

vercel bot commented Oct 4, 2025

@Linkodt is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

@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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/Tour.tsx (3)

161-165: 考虑使用 useCallback 包裹以优化性能。

handleClose 函数逻辑正确,但未使用 useCallback 包裹,这会导致每次渲染时创建新的函数引用。由于 handleKeyDown 依赖此函数,这会导致键盘监听器在每次渲染时重新注册,可能影响性能。

应用此差异来包裹函数:

-  const handleClose = () => {
+  const handleClose = React.useCallback(() => {
     setMergedOpen(false);
     onClose?.(mergedCurrent);
-  };
+  }, [mergedCurrent, onClose]);

173-199: 可以使用 switch 语句提高可读性。

当前使用多个 if 语句处理不同按键。使用 switch 语句可以使代码结构更清晰、更易维护。

应用此差异重构为 switch 语句:

-    if (e.key === 'ArrowLeft') {
+    switch (e.key) {
+      case 'ArrowLeft':
-      if (mergedCurrent <= 0) {
-        return;
-      }
-      e.preventDefault();
-      e.stopPropagation();
-      onInternalChange(mergedCurrent - 1);
-      return;
-    }
-    if (e.key === 'ArrowRight') {
-      if (mergedCurrent >= steps.length - 1) {
-        return;
-      }
-      e.preventDefault();
-      e.stopPropagation();
-      onInternalChange(mergedCurrent + 1);
-      return;
-    }
-    if (e.key === 'Escape') {
-      if (!mergedClosable) {
-        return;
-      }
-      e.preventDefault();
-      e.stopPropagation();
-      handleClose();
-      return;
+        if (mergedCurrent > 0) {
+          e.preventDefault();
+          e.stopPropagation();
+          onInternalChange(mergedCurrent - 1);
+        }
+        break;
+      case 'ArrowRight':
+        if (mergedCurrent < steps.length - 1) {
+          e.preventDefault();
+          e.stopPropagation();
+          onInternalChange(mergedCurrent + 1);
+        }
+        break;
+      case 'Escape':
+        if (mergedClosable) {
+          e.preventDefault();
+          e.stopPropagation();
+          handleClose();
+        }
+        break;
+      default:
+        // 其他按键不处理
+        break;
     }

168-168: 优化 AbortController 的初始化。

第 168 行初始化的 AbortController 在第一次 effect 运行时立即被中止(第 203 行),从未被实际使用。可以将其初始化为 null 以避免创建不必要的对象。

应用此差异优化初始化:

-  const keyboardController = React.useRef(new AbortController());
+  const keyboardController = React.useRef<AbortController | null>(null);

然后在 effect 中添加 null 检查:

   React.useEffect(() => {
-    keyboardController.current.abort();
+    if (keyboardController.current) {
+      keyboardController.current.abort();
+    }
     keyboardController.current = new AbortController();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5658930 and 2a6a8fd.

📒 Files selected for processing (1)
  • src/Tour.tsx (2 hunks)
🔇 Additional comments (2)
src/Tour.tsx (2)

61-61: LGTM!

keyboard 属性的添加方式正确,默认值为 false 保证了向后兼容性。


202-213: 事件监听器设置正确。

使用 AbortController 管理事件监听器的模式是正确的,能够确保在组件卸载或依赖变化时正确清理监听器。

Comment on lines +169 to +200
const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
if (!mergedOpen) {
return;
}
if (e.key === 'ArrowLeft') {
if (mergedCurrent <= 0) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent - 1);
return;
}
if (e.key === 'ArrowRight') {
if (mergedCurrent >= steps.length - 1) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent + 1);
return;
}
if (e.key === 'Escape') {
if (!mergedClosable) {
return;
}
e.preventDefault();
e.stopPropagation();
handleClose();
return;
}
}, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

缺少 mergedClosable 依赖导致闭包陈旧。

handleKeyDown 在第 192 行使用了 mergedClosable,但该变量未包含在依赖数组中。这会导致回调捕获过时的 mergedClosable 值,当可关闭状态变化时,Escape 键行为不会更新。

应用此差异修复依赖数组:

-  }, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange]);
+  }, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange, mergedClosable]);
📝 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.

Suggested change
const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
if (!mergedOpen) {
return;
}
if (e.key === 'ArrowLeft') {
if (mergedCurrent <= 0) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent - 1);
return;
}
if (e.key === 'ArrowRight') {
if (mergedCurrent >= steps.length - 1) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent + 1);
return;
}
if (e.key === 'Escape') {
if (!mergedClosable) {
return;
}
e.preventDefault();
e.stopPropagation();
handleClose();
return;
}
}, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange]);
const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
if (!mergedOpen) {
return;
}
if (e.key === 'ArrowLeft') {
if (mergedCurrent <= 0) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent - 1);
return;
}
if (e.key === 'ArrowRight') {
if (mergedCurrent >= steps.length - 1) {
return;
}
e.preventDefault();
e.stopPropagation();
onInternalChange(mergedCurrent + 1);
return;
}
if (e.key === 'Escape') {
if (!mergedClosable) {
return;
}
e.preventDefault();
e.stopPropagation();
handleClose();
return;
}
}, [mergedCurrent, mergedOpen, steps.length, handleClose, onInternalChange, mergedClosable]);
🤖 Prompt for AI Agents
In src/Tour.tsx around lines 169 to 200, the handleKeyDown callback uses
mergedClosable (line ~192) but it is missing from the dependency array, causing
a stale-closure bug; update the useCallback dependency list to include
mergedClosable so the callback is recreated when closable state changes,
ensuring Escape key behavior reflects the latest mergedClosable value.

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.

feature: 希望 Tour 像 Modal 一样支持键盘 Esc 关闭
1 participant