Skip to content

fix: 点击 toast 不再关闭打开中的对话框与弹层 - #37

Open
qqqqqf-q wants to merge 1 commit into
mainfrom
fix/memoh-174-toast-dismisses-dialog
Open

qqqqqf-q wants to merge 1 commit into
mainfrom
fix/memoh-174-toast-dismisses-dialog

Conversation

@qqqqqf-q

Copy link
Copy Markdown
Member

Summary

修复:任意对话框 / 弹层打开时,点击 toast 的关闭键(或 toast 上的复制、操作按钮)会把对话框一起关掉,对话框里未提交的输入随之丢失。

根因:Toaster 通过 <Teleport to="body"> 渲染在所有弹层之外。Reka DismissableLayer 在 document 上监听 pointerdown / focusin,target 不在弹层内就判定为外部交互并关闭弹层。Reka 自带的 Toast 用 DismissableLayerBranch 把 toast 区域登记为分支来避开这一判定,但 reka-ui 没有公开导出这个组件,自研的 Toaster 也一直没有对应处理。

改动:在 toaster 根节点 <ol> 上 @pointerdown.stop / @focusin.stop,让 toast 内的交互不再冒泡到 document,效果等同于 branch 机制。toast 自身的 handler(悬停暂停、关闭、操作、复制)照常执行;只是 document 级监听器不再收到来自 toast 的这两类事件。只改了 Toaster.vue 这一个文件,各弹层组件、toast 的外观和交互都没动。

影响面

所有基于 Reka DismissableLayer 的弹层(Dialog、Sheet、Popover、DropdownMenu 等)都走同一机制,修复对它们一并生效。副作用:如果某处 document 级的 pointerdown / focusin 监听器需要感知 toast 点击,它将收不到;目前没有已知调用方依赖这一点。

已知但不在本 PR 内:模态对话框打开时 toaster 会被 hideOthers 设置为 aria-hidden,屏幕阅读器读不到 toast。这个问题另行评估。

Related

Validation

在本地 Memoh Cloud 开发栈(Vite dev,packages/ui 源码直连)上用 Playwright 实测(Agent 执行)。场景:账单页 → 兑换邀请码对话框 → 输入无效码 → 弹出错误 toast。

  • 修复前:点 toast 的 ✕,对话框关闭(复现)
  • 修复后:
    • 点 toast 的 ✕:对话框保持打开,toast 正常消失
    • 点 toast 上的「复制错误编号」:对话框保持打开
    • 点对话框外的遮罩:对话框照常关闭
    • 无弹层时点 toast 的 ✕:toast 正常消失
  • eslint 通过
  • 没有单独实测 Sheet / Popover / DropdownMenu 的场景。它们与 Dialog 走同一个 DismissableLayer 判定路径,按机制推断同样修复

Screenshots

修复前:对话框内出现错误 toast(左)→ 点 ✕ 后对话框被一起关掉(右)

修复后:同一场景(左)→ 点 ✕ 后只有 toast 消失,对话框仍在(右)

Human QA

  • Human QA passed

Toaster 被 Teleport 到 body,处于所有弹层之外;Reka DismissableLayer
在 document 上监听 pointerdown / focusin,把点 toast 判定为外部交互,
导致点 toast 的关闭键会连带关掉 Dialog / Sheet / Popover。

在 toaster 根节点阻止这两个事件冒泡到 document,等效于 Reka 自带 Toast
使用的(未公开导出的)DismissableLayerBranch。

Refs: MEMOH-174
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T13:50:05.568084Z 09b4524 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09b45244cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

aria-label="Notifications"
tabindex="-1"
@pointerdown.stop
@focusin.stop

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the modal focus trap when filtering focusin

When a modal Dialog or Sheet is open and a user clicks a toast button, this modifier also hides the focusin event from Reka's trapped FocusScope, not only from DismissableLayer. Reka's maintainer confirms that the dialog scope normally prevents teleported outside elements from taking focus (source); after this change the toast button can instead become document.activeElement outside the modal—and inside the toaster subtree that the modal marks aria-hidden. A non-dismissing text action can leave focus there, while removing the focused toast can drop focus to the body, breaking the modal focus-containment contract. Suppress the dismissal specifically or retain/restore focus inside the modal rather than stopping focusin propagation globally.

Useful? React with 👍 / 👎.

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.

1 participant