From 45a3956d69ae14eaf564fd5275085ff5052fcbc9 Mon Sep 17 00:00:00 2001 From: guitavano Date: Mon, 9 Nov 2026 15:10:40 -0300 Subject: [PATCH] fix(blocks): keep the "." shortcut out of fields inside a shadow root StudioBridge's "." editor shortcut skipped keys typed in an input by checking e.target, but a keydown from a field inside a shadow root (a third-party widget's form) reaches the document retargeted to the shadow host. Typing "." in that field navigated the shopper to Studio's editor chooser. Read the field from the event's composed path instead. Co-Authored-By: Claude Opus 5.5 --- .../blocks/src/v8/preview/StudioBridge.ts | 7 +++++-- .../blocks/src/v8/preview/preview.test.tsx | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) 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);