diff --git a/packages/app/component-tests/browser-pane.spec.ts b/packages/app/component-tests/browser-pane.spec.ts index c943d5f811f..1688f513880 100644 --- a/packages/app/component-tests/browser-pane.spec.ts +++ b/packages/app/component-tests/browser-pane.spec.ts @@ -101,6 +101,35 @@ story("keeps a still of the page under floating content that covers it", async ( await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible() }) +story("keeps a still of the page under an open dialog instead of blanking the pane", async ({ page }) => { + const root = page.getByTestId("browser-pane-fixture") + const still = root.locator("#browser-panel img") + const native = root.getByTestId("native-Alpha") + const toggle = root.getByRole("button", { name: "Toggle dialog", exact: true }) + await root.getByRole("button", { name: "Hold capture", exact: true }).click() + + // Read in the same task as the click: the page must not hide before its still exists. + const visible = await toggle.evaluate((element) => { + element.dispatchEvent(new MouseEvent("click", { bubbles: true })) + + return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible") + }) + + expect(visible).toBe("true") + await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible() + await expect(native).toHaveAttribute("data-visible", "true") + await expect(still).toHaveCount(0) + + await root.getByRole("button", { name: "Release capture", exact: true }).click() + await expect(native).toHaveAttribute("data-visible", "false") + await expect(still).toBeVisible() + + await toggle.click() + await expect(native).toHaveAttribute("data-visible", "true") + await expect(still).toHaveCount(0) + await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible() +}) + story("comments on a picked element over a still of the page", async ({ page }) => { const root = page.getByTestId("browser-pane-fixture") const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true }) diff --git a/packages/app/src/runtime/extension/embeds.tsx b/packages/app/src/runtime/extension/embeds.tsx index c4e72ad8578..cf1e474f422 100644 --- a/packages/app/src/runtime/extension/embeds.tsx +++ b/packages/app/src/runtime/extension/embeds.tsx @@ -157,8 +157,10 @@ function EmbedView(props: EmbedProps & { input: Input; bridge: Bridge }) { const top = Math.round(rect.top * zoom) const right = Math.round(rect.right * zoom) const bottom = Math.round(rect.bottom * zoom) - const shown = props.visible && store.visible && !props.input.dialog() - const cover = !!props.frozen || covered(rect) + const shown = props.visible && store.visible + // A dialog's scrim covers the box like floating content does, so it dims a still of the page + // instead of a blank box, and the page does not blink out and back as the dialog opens and closes. + const cover = !!props.frozen || props.input.dialog() || covered(rect) if (shown && cover) freeze(id) @@ -227,7 +229,7 @@ function EmbedView(props: EmbedProps & { input: Input; bridge: Bridge }) { // The native views are not clipped by the retained panel's DOM. Hide before the next // animation frame so closing the panel cannot leave the embed above the app. - if (!props.visible || !store.visible || props.input.dialog() || !props.id) return hide() + if (!props.visible || !store.visible || !props.id) return hide() schedule(300) }, ), diff --git a/packages/gui-extensions/src/browser/panel.fixture.tsx b/packages/gui-extensions/src/browser/panel.fixture.tsx index 88868f1f832..b20c7b35975 100644 --- a/packages/gui-extensions/src/browser/panel.fixture.tsx +++ b/packages/gui-extensions/src/browser/panel.fixture.tsx @@ -72,6 +72,8 @@ type PaneFixtureState = { error: string | undefined layouts: Record covered: boolean + /** Whether a host dialog is open over the app. */ + dialog: boolean captures: number holdCapture: boolean picker: Record @@ -131,6 +133,7 @@ export function mountBrowserPane(input: PaneHost) { error: undefined, layouts: {}, covered: false, + dialog: false, captures: 0, holdCapture: false, picker: {}, @@ -184,7 +187,7 @@ export function mountBrowserPane(input: PaneHost) { } // SAFETY: the host embeds call only `embed` and `capture` on their bridge (`runtime/extension/embeds.tsx`). - const embeds = input.createEmbeds({ bridge: bridge as Bridge, zoom: () => 1, dialog: () => false }) + const embeds = input.createEmbeds({ bridge: bridge as Bridge, zoom: () => 1, dialog: () => store.dialog }) const t = (key: string, params?: Readonly>>) => (messages.get(key) ?? language.t(key)).replace(/\{\{(\w+)\}\}/g, (_, name: string) => @@ -468,6 +471,7 @@ export function mountBrowserPane(input: PaneHost) { +