cps-dialog: focus ring on the dialog frame covers the dialog and blocks all clicks inside it

未关闭 适合新手
#948 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
72/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
angular, css, playwright, scss, typescript
领域
frontend

调研方向

从 projects/cps-ui-kit/styles/_mixins.scss 中的 focus-ring mixin,以及 cps-dialog.component.scss 第 107 行附近的 :focus-visible 规则开始。复现方法是:打开可拖动对话框示例,点击标题栏,按下 ArrowDown,然后检查 elementFromPoint 返回的是对话框而不是按钮。完成标准是:焦点环的伪元素不再拦截点击,焦点环外观保持不变,并且与 focus-ring 测试放在一起的 Playwright 规格能成功点击「Close dialog」。

由索引模型根据 Issue 内容生成。

描述

bug

Summary

When a cps-dialog has keyboard focus on its frame, the focus ring drawn around the dialog covers the whole dialog and takes every pointer event. Buttons, inputs and links inside the dialog stop responding to clicks until the focus moves elsewhere.

The state is easy to reach: click on the dialog's header, then press a key. The click focuses the dialog frame (div.cps-dialog[tabindex="-1"]), and the key press makes the browser treat that focus as keyboard focus (:focus-visible matches), which turns the ring on.

Environment

  • cps-ui-kit 21.33.0. The source of 22.3.0 has the same CSS for this ring, but I did not run that version.
  • Reproduced in Chromium and WebKit (automated browsers).
  • Reproduced on the kit's own documentation site, so it does not depend on any application code. I do not know which kit version the site is currently built with.

Steps to reproduce

  1. Open https://absaoss.github.io/cps-shared-ui/dialog/examples and click the Draggable dialog button.
  2. In the dialog, click Disable closing. This stops Esc from closing the dialog, which would otherwise hide the problem. (Skipping this step and pressing ArrowDown in step 4 gives the same result.)
  3. Click on the dialog's header, in a bare part of it.
  4. Press Esc (or ArrowDown).
  5. Click anything inside the dialog, for example the Close dialog button.

Expected

The dialog shows its focus ring, and everything inside it still responds to clicks.

Actual

The dialog shows its focus ring, but clicks on everything inside it do nothing. The cursor behaves as if the dialog were one inert block.

Observed in the browser after step 4:

  • document.activeElement is div.cps-dialog, and .matches(':focus-visible') is true.
  • document.elementFromPoint(...) at the centre of the Close dialog button returns div.cps-dialog, not the button.
  • getComputedStyle(dialog, '::before') is content: "", inset: -1px, border: 1px solid, pointer-events: auto. ::after is the same with inset: -2px.
  • Playwright's click on the button fails with: <div role="dialog" tabindex="-1" data-testid="cps-dialog" ... class="cps-dialog ..."> intercepts pointer events.

Cause

The dialog frame's focus ring comes from the focus-ring mixin:

  • projects/cps-ui-kit/src/lib/services/cps-dialog/internal/components/cps-dialog/cps-dialog.component.scss, around line 107: &:focus-visible { @include focus-ring(0.0625rem, 0.125rem, inherit); }
  • projects/cps-ui-kit/styles/_mixins.scss, @mixin focus-ring: it sets position: relative on the element and draws the ring with ::before and ::after, both position: absolute with a negative inset and a border.

On a small control that is harmless. On the dialog frame the two pseudo-elements are as large as the whole dialog. They are positioned, so they paint above the dialog's non-positioned content, and because pointer-events is left at its default they become the hit-test target for every point inside the dialog.

Esc is only one way in. A dialog whose closeOnEscape is false (or disableClose is set) stays open after Esc, so it is the clearest repro, but any key press after the header click does it: ArrowDown works on the docs site in both browsers. In WebKit, Shift alone does not turn the ring on, but ArrowDown and Esc do.

Suggested fix

The ring is decorative, so it should never be a pointer target. The smallest change is in the mixin, which also protects every other component that uses it:

&::before,
&::after {
  content: '';
  position: absolute;
  pointer-events: none; // the ring is decorative and must never take clicks
  border-radius: $radius;
}

If changing the mixin is too broad, put the same pointer-events: none on the ring's pseudo-elements for the dialog frame only.

The ring must stay visible; only its pointer behaviour changes. Other uses of the mixin on large containers (for example the resizable handles in the same file, around lines 195 and 285) may be worth checking for the same effect. I did not test those.

Acceptance criteria

  • After clicking the dialog header and pressing Esc (with closeOnEscape: false) or ArrowDown, buttons and inputs inside the dialog can be clicked.
  • The focus ring is still drawn exactly as before.
  • A Playwright test covers it, next to the existing focus-ring specs under playwright/cps-ui-kit/components/: open the draggable dialog example, click the header, press ArrowDown, then click Close dialog and expect the dialog to close.

Workaround for applications

.cps-dialog:focus-visible::before,
.cps-dialog:focus-visible::after {
  pointer-events: none;
}
主要语言
TypeScript
星标
9
派生
8
平均合并
1 天 6 小时
30 天内合并 PR
25

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

AbsaOSS/cps-shared-ui 的其他 Issue

查看 AbsaOSS/cps-shared-ui 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。