diff --git a/packages/blocks/src/v8/preview/StudioBridge.ts b/packages/blocks/src/v8/preview/StudioBridge.ts index a1ab582f..8e06e6d3 100644 --- a/packages/blocks/src/v8/preview/StudioBridge.ts +++ b/packages/blocks/src/v8/preview/StudioBridge.ts @@ -11,7 +11,10 @@ * In a frame it answers only messages from the parent window whose origin is * one of `trustedOrigins`. On a page that isn't framed it answers no message; * there it only listens for the "." shortcut (`editShortcut`), which opens the - * page in Studio's editor, as v7's `LiveControls` did. Studio signs the editor + * page in Studio's editor, as v7's `LiveControls` did. A key typed in a field + * is left alone, also in a field inside a shadow root (a third-party widget's + * form), whose events reach the document with the host as their target, so the + * script reads the field from the event's composed path. Studio signs the editor * in: `/choose-editor` sends a visitor who isn't signed in to Studio's login * and back. * The binding passes Studio's origins in (`@decocms/blocks` holds no list of @@ -117,7 +120,7 @@ if(window.self!==window.top){ }); }else if(s.hasAttribute("data-shortcut")){ document.addEventListener("keydown",function(e){ - var t=e.target; + var t=e.composedPath?e.composedPath()[0]:e.target; if(e.defaultPrevented||(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.tagName==="SELECT"||t.isContentEditable)))return; if(e.key!=="."&&!(e.ctrlKey&&e.shiftKey&&e.key==="E"))return; e.preventDefault(); diff --git a/packages/blocks/src/v8/preview/preview.test.tsx b/packages/blocks/src/v8/preview/preview.test.tsx index 269af19a..a6ebb26f 100644 --- a/packages/blocks/src/v8/preview/preview.test.tsx +++ b/packages/blocks/src/v8/preview/preview.test.tsx @@ -419,6 +419,27 @@ describe("StudioBridge", () => { input.remove(); }); + it("leaves typing in a field inside a shadow root alone (a third-party widget's form)", () => { + const open = vi.spyOn(window, "open").mockImplementation(() => null); + unmount = mountBridge({ site: "shop", trustedOrigins: STUDIO, editShortcut: true }, false); + const host = document.createElement("div"); + document.body.appendChild(host); + const input = document.createElement("input"); + host.attachShadow({ mode: "open" }).appendChild(input); + // The document sees the host as the target; the email's "." must still type. + const e = new KeyboardEvent("keydown", { + key: ".", + bubbles: true, + cancelable: true, + composed: true, + }); + input.dispatchEvent(e); + expect(e.defaultPrevented).toBe(false); + expect(press({ key: ".", ctrlKey: true }, host).defaultPrevented).toBe(true); + expect(open).toHaveBeenCalledTimes(1); + host.remove(); + }); + /** The choose-editor URL a Ctrl+"." opened (a plain "." navigates the tab, which jsdom can't follow). */ function opened(open: { mock: { calls: unknown[][] } }, call = 0): Record { const url = new URL(open.mock.calls[call][0] as string);