diff --git a/e2e/sidecar.e2e.ts b/e2e/sidecar.e2e.ts index ee839d2a0..a3f9af4c4 100644 --- a/e2e/sidecar.e2e.ts +++ b/e2e/sidecar.e2e.ts @@ -1325,6 +1325,55 @@ test("a removed subject block moves its comment to document orphan chrome", asyn await expect(page.getByRole("dialog", { name: "Orphaned comments" })).toContainText(QUOTED); }); +test("an open comment card hides the formatting toolbar of a lingering selection", async ({ join, seed }) => { + await seed(TWO_BLOCKS); + let page = await join("ana"); + await page.setViewportSize({ width: 1_280, height: 800 }); + await content(page).locator("p").nth(1).selectText(); + let toolbar = page.getByRole("toolbar", { name: "Text formatting" }); + await expect(toolbar).toBeVisible(); + + let card = await thread(page); + await expect(card).toBeVisible(); + await expect(toolbar).toBeHidden(); +}); + +test("the orphaned comments popover shows its whole card beneath the pill", async ({ join, seed }) => { + await seed(TWO_BLOCKS); + let page = await join("ana"); + await page.setViewportSize({ width: 1_280, height: 800 }); + let first = content(page).locator("p").first(); + await first.selectText(); + await page.keyboard.press("Backspace"); + await page.keyboard.press("Backspace"); + + // A selection's formatting toolbar would sit over the card it opens. + await content(page).locator("p").first().selectText(); + let toolbar = page.getByRole("toolbar", { name: "Text formatting" }); + await expect(toolbar).toBeVisible(); + + let opener = page.getByRole("button", { name: "1 orphaned comments" }); + await opener.click(); + let popover = page.getByRole("dialog", { name: "Orphaned comments" }); + await expect(popover).toBeVisible(); + await expect(toolbar).toBeHidden(); + // The card sizes against the document, not the pill that holds it. + let reply = popover.getByRole("textbox", { name: "Reply", exact: true }); + await expect(reply).toBeInViewport({ ratio: 1 }); + await expect(popover.getByText(QUOTED, { exact: true })).toBeInViewport({ ratio: 1 }); + await expect.poll(() => popover.evaluate(element => element.scrollHeight - element.clientHeight)) + .toBeLessThanOrEqual(1); + let card = await popover.boundingBox(); + let pill = await opener.boundingBox(); + expect(card!.width).toBeGreaterThanOrEqual(280); + expect(card!.y).toBeGreaterThanOrEqual(pill!.y + pill!.height); + expect(Math.abs(card!.x + card!.width - (pill!.x + pill!.width))).toBeLessThanOrEqual(1); + + await page.keyboard.press("Escape"); + await expect(popover).toHaveCount(0); + await expect(opener).toBeFocused(); +}); + test("a compact orphan sheet owns focus and restores its opener", async ({ join, seed }) => { await seed(TWO_BLOCKS); let page = await join("ana", { diff --git a/packages/editor/src/styles.css b/packages/editor/src/styles.css index 2b071a589..df47825da 100644 --- a/packages/editor/src/styles.css +++ b/packages/editor/src/styles.css @@ -1474,11 +1474,12 @@ transition-duration: var(--drawer-close-dur); } +/* Full height so the card's percentages resolve against the document, not the pill. */ .plan-comment-orphans { - pointer-events: auto; position: absolute; top: 0.5rem; right: 0.5rem; + bottom: 0.5rem; } .plan-comment-orphan-button { @@ -1488,8 +1489,11 @@ } .plan-comment-orphan-card { - top: 2.5rem; + --orphan-card-offset: 2.5rem; + top: var(--orphan-card-offset); right: 0; + width: min(20rem, calc(100cqi - 1rem)); + max-height: calc(100% - var(--orphan-card-offset)); } /* The scrim every phone sheet shares, lighter while this sheet rests at its medium detent. */ @@ -5438,6 +5442,11 @@ body:has(.plan-comment-sheet-popup[data-swiping]) animation: plan-bubble-enter var(--duration-fast) var(--ease-out); } +/* An open comment card owns the reader's attention; a lingering selection's toolbar would cover it. */ +:root:has(.plan-comment-card.motion-comment-surface:not(.is-closing)) .plan-formatting-toolbar { + display: none; +} + @media (prefers-reduced-motion: reduce) { .plan-formatting-toolbar { animation: none;