Repository navigation
Conversation
Toaster 被 Teleport 到 body,处于所有弹层之外;Reka DismissableLayer 在 document 上监听 pointerdown / focusin,把点 toast 判定为外部交互, 导致点 toast 的关闭键会连带关掉 Dialog / Sheet / Popover。 在 toaster 根节点阻止这两个事件冒泡到 document,等效于 Reka 自带 Toast 使用的(未公开导出的)DismissableLayerBranch。 Refs: MEMOH-174
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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 |
There was a problem hiding this comment.
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 👍 / 👎.
Summary
修复:任意对话框 / 弹层打开时,点击 toast 的关闭键(或 toast 上的复制、操作按钮)会把对话框一起关掉,对话框里未提交的输入随之丢失。
根因:
Toaster通过<Teleport to="body">渲染在所有弹层之外。RekaDismissableLayer在 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
packages/uigitlink bumpValidation
在本地 Memoh Cloud 开发栈(Vite dev,
packages/ui源码直连)上用 Playwright 实测(Agent 执行)。场景:账单页 → 兑换邀请码对话框 → 输入无效码 → 弹出错误 toast。eslint通过DismissableLayer判定路径,按机制推断同样修复Screenshots
修复前:对话框内出现错误 toast(左)→ 点 ✕ 后对话框被一起关掉(右)
修复后:同一场景(左)→ 点 ✕ 后只有 toast 消失,对话框仍在(右)
Human QA