[Dialog] 支持焦点落点可配置、Enter 键确认与焦点循环(含 DialogPlugin 对齐)

Đang mở
#4,430 3 bình luận 1 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@RylanBot đang làm issue này rồi.

Từ ngày 28/10/2026.

  • #4448 của @RylanBot — đang mở

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
18/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
react, typescript
Lĩnh vực
frontend

Hướng nghiên cứu

Start with dialog/hooks/useDialogEsc.js, where activateDialog() currently focuses only the .t-dialog__ctx container, then dialog/DialogCard.js (renderConfirmBtn hardcodes the primary theme and renderFooter passes custom footers through unchanged), and config-provider/ConfigContext.js for the dialog defaults. Done means defaultFocus, Enter-to-confirm with its textarea, IME and confirmLoading guards, and Tab focus trap work the same for Dialog and DialogPlugin, with a stable data-role="confirm" marker on the primary button.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

🐞 bug 💪🏻 enhancement in progress
这个功能解决了什么问题
环境
  • tdesign-react: 1.18.2
  • React: 18
  • 浏览器: Chrome 140 / Safari 18(均复现)
背景与诉求
  1. 不同弹窗诉求不同:二次确认希望焦点落「确定」,危险操作(删除/锁定)希望落「取消」,表单类弹窗希望落输入框。落点应由参数决定,并支持全站默认值。
  2. 可配置的键盘确认:只要弹窗打开,按 Enter 就应等同于点击「确定」。当前焦点默认落在弹窗容器上,回车完全无响应,键盘链路断裂。
期望行为
  1. 焦点落点可配置:提供 defaultFocus,支持 'confirm' | 'cancel' | 'none' | (() => HTMLElement | null);组件默认值建议保持现状('none')以兼容存量,但通过 globalConfig.dialog.defaultFocus 可全站统一策略。
  2. Enter 触发确认,与焦点解耦:弹窗打开期间按 Enter 等同于点击「确定」,不要求焦点位于主按钮;Esc 仍关闭。需内置边界:焦点在 textarea/可换行输入控件、IME 组合输入过程中不触发,confirmLoading 期间不重复触发。可用 confirmOnEnter 开关。
  3. Tab 焦点循环:Tab / Shift+Tab 在弹窗内循环,不逃逸;关闭后焦点归还触发元素(restoreFocus)。
  4. 上述能力在声明式 Dialog 与命令式 DialogPlugin 上行为一致。
你建议的方案是什么
建议 API(P0,其余能力可另开 issue)
interface DialogProps {
  /** 打开后焦点落点;组件默认建议保持现状('none'),由业务按需指定 */
  defaultFocus?: 'confirm' | 'cancel' | 'none' | (() => HTMLElement | null);
  /** Enter 触发确认,默认 true */
  confirmOnEnter?: boolean;
  /** 焦点循环,默认 true */
  focusTrap?: boolean;
  /** 关闭后焦点归还触发元素,默认 true */
  restoreFocus?: boolean;
}

配套要求:

  • globalConfig.dialog 暴露 defaultFocus / confirmOnEnter / focusTrap 的默认值,便于全站统一;
  • 为「主操作按钮」提供稳定语义标记(例如统一 data-role="confirm",自定义 footer 下由业务显式声明),让 defaultFocus: 'confirm' 与 Enter 确认在任何 footer 形态下都能命中,而不是靠类名匹配。
根因(源码定位)
  1. dialog/hooks/useDialogEsc.js 的 activateDialog() 只做 lastDialog.current.focus(),把焦点交给容器 .t-dialog__ctx,既不涉及按钮,也没有任何落点配置。
  2. 组件层只监听 Esc,无 Enter、无 focus trap;autofocus 在 Dialog 内不生效(参考 #2794),消费方只能在 onOpened 里补刀,且开场动画期间 TDesign 会抢焦点,时序脆弱。
  3. dialog/DialogCard.js 的 renderConfirmBtn 硬编码 theme: "primary"(theme="danger" 弹窗也渲染 primary),renderFooter 对自定义 footer 直接 parseTNode,不提供「主操作按钮」语义标记。于是:
    • 默认 footer:主按钮是 .t-button--theme-primary;
    • 自定义 footer:业务常用 <Button theme="danger">,类名是 .t-button--theme-danger。
      我们的补丁只认 --theme-primary 时,一批自定义 footer 弹窗(如分支锁定)主按钮依然拿不到焦点——只要组件不给语义,消费方就只能不断猜选择器。
  4. config-provider/ConfigContext.js 中 dialog.confirmBtnTheme 已声明默认值,但全量检索 es/ 无任何消费方,属未生效配置。
我们当前的 workaround(供参考,非长期方案)

在业务封装层:① onOpened 里按类名猜主按钮并 focus();② 在 document 上挂 keydown 捕获实现 Tab 循环;③ 用 :focus 补焦点环(程序化 focus 不命中 :focus-visible,Button 无可见焦点);④ 对 DialogPlugin.confirm 做同构包装复制同一套逻辑。

Enter 确认目前只能自己绑,还得自行判断 textarea、IME 组合输入、confirmLoading 等边界,规则与组件内部(如 Esc 的 dialogStack)不同步就会出现误触发或漏触发。这套补丁必须随组件实现变化持续维护,因此希望能力下沉到组件层。

Ngôn ngữ chính
TypeScript
Star
973
Fork
379
Merge trung bình
4 ngày 7 giờ
Pull request đã merge (30 ngày)
15

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Tencent/tdesign-react

Tất cả issue của Tencent/tdesign-react

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.