@@ -153,7 +158,7 @@ export const MemoSelectionActionBar = ({
- {/* Controlled Right Click context menu for single note on Desktop using absolute placement */}
- {memoContextMenu && (
+ {/* Keep the virtual trigger in the document viewport so fixed coordinates
+ are not offset by the memo pane's backdrop-filter containing block. */}
+ {memoContextMenu && typeof document !== "undefined" ? createPortal(
{ if (!open) setMemoContextMenu(null); }}>
@@ -1436,6 +1442,7 @@ export const MemoListPane = ({
-
- )}
+
,
+ document.body
+ ) : null}
{memoIdCopyNotice && (
{
expect(await auditVisibleTextContrast(page)).toEqual([]);
});
+ test("notebook move picker keeps highlighted options dark and selected labels clean", async ({ page }) => {
+ await page.goto("/");
+ await page.getByRole("button", { name: "选择笔记", exact: true }).click();
+
+ const actionBar = page.locator("[data-memo-selection-action-bar]");
+ const actionCard = actionBar.locator(":scope > div");
+ await expect(actionBar).toBeVisible();
+ await expect.poll(async () => (await actionCard.boundingBox())?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(120);
+
+ const notebookSelect = actionBar.getByRole("combobox");
+ await expect(notebookSelect).toContainText("功能演示");
+ await expect(notebookSelect).not.toContainText("└");
+ await notebookSelect.click();
+
+ const nestedNotebook = page.getByRole("option", { name: /功能演示/ });
+ await nestedNotebook.hover();
+ await expect.poll(() => nestedNotebook.evaluate((element) => getComputedStyle(element).backgroundColor))
+ .not.toBe("rgb(241, 245, 249)");
+ expect(await auditVisibleTextContrast(page)).toEqual([]);
+
+ await nestedNotebook.click();
+ await expect(notebookSelect).toHaveText("功能演示");
+ });
+
test("public share content remains readable in dark mode", async ({ page }) => {
const memoId = "memo_demo_overview";
const response = await page.request.post(`/api/v1/memos/${memoId}/share`);
diff --git a/tests/e2e/memo-list-actions.spec.ts b/tests/e2e/memo-list-actions.spec.ts
new file mode 100644
index 00000000..1c2c0d05
--- /dev/null
+++ b/tests/e2e/memo-list-actions.spec.ts
@@ -0,0 +1,21 @@
+import { expect, test } from "@playwright/test";
+
+test("anchors a memo's more-actions menu to its visible trigger", async ({ page }) => {
+ await page.goto("/");
+
+ const memoCard = page.locator('[data-memo-id="memo_demo_overview"]');
+ const trigger = memoCard.locator("[data-memo-actions-trigger]");
+ await memoCard.hover();
+ await trigger.click();
+
+ const menu = page.locator("[data-memo-actions-menu]");
+ await expect(menu).toBeVisible();
+
+ const triggerBox = await trigger.boundingBox();
+ const menuBox = await menu.boundingBox();
+ expect(triggerBox).not.toBeNull();
+ expect(menuBox).not.toBeNull();
+
+ expect(Math.abs(menuBox!.x - (triggerBox!.x + triggerBox!.width))).toBeLessThan(24);
+ expect(Math.abs(menuBox!.y - (triggerBox!.y + triggerBox!.height))).toBeLessThan(24);
+});