diff --git a/src/bricks/effects/InsertAtCursorEffect.ts b/src/bricks/effects/InsertAtCursorEffect.ts index 7b45c1908b..f6eef253ed 100644 --- a/src/bricks/effects/InsertAtCursorEffect.ts +++ b/src/bricks/effects/InsertAtCursorEffect.ts @@ -73,8 +73,6 @@ class InsertAtCursorEffect extends EffectABC { return; } - console.debug("InsertAtCursorEffect", { text, root, focused: focus.get() }); - const element = isDocument(root) ? focus.get() : root; if (!element) { diff --git a/src/contentScript/sidebarController.tsx b/src/contentScript/sidebarController.tsx index b549f090eb..305411dd98 100644 --- a/src/contentScript/sidebarController.tsx +++ b/src/contentScript/sidebarController.tsx @@ -542,35 +542,40 @@ export function sidePanelOnClose(callback: () => void): void { } export function initSidebarFocusEvents(): void { - sidebarShowEvents.add(() => { - const sidebar = getSidebarElement(); - - if (!sidebar) { - // Should always exist because sidebarShowEvents is called on Sidebar App initialization - return; - } - - const closeSignal = sidePanelOnCloseSignal(); + if (!isMV3()) { + // Add listeners to track keep track of focus with the MV2 sidebar. When the user interacts + // with the MV2 sidebar, the sidebar gets set as the document.activeElement. Required for brick + // functionality such as InsertAtCursorEffect + sidebarShowEvents.add(() => { + const sidebar = getSidebarElement(); + + if (!sidebar) { + // Should always exist because sidebarShowEvents is called on Sidebar App initialization + return; + } - // Can't detect clicks in the sidebar itself. So need to just watch for enter/leave the sidebar element - sidebar.addEventListener( - "mouseenter", - () => { - // If the user clicks into the sidebar and then leaves the sidebar, don't set the focus to the sidebar - // when they re-enter the sidebar - if (document.activeElement !== sidebar) { - focusController.save(); - } - }, - { passive: true, capture: true, signal: closeSignal }, - ); + const closeSignal = sidePanelOnCloseSignal(); + + // Can't detect clicks in the sidebar itself. So need to just watch for enter/leave the sidebar element + sidebar.addEventListener( + "mouseenter", + () => { + // If the user clicks into the sidebar and then leaves the sidebar, don't set the focus to the sidebar + // when they re-enter the sidebar + if (document.activeElement !== sidebar) { + focusController.save(); + } + }, + { passive: true, capture: true, signal: closeSignal }, + ); - sidebar.addEventListener( - "mouseleave", - () => { - focusController.clear(); - }, - { passive: true, capture: true, signal: closeSignal }, - ); - }); + sidebar.addEventListener( + "mouseleave", + () => { + focusController.clear(); + }, + { passive: true, capture: true, signal: closeSignal }, + ); + }); + } } diff --git a/src/contentScript/textEditorDom.ts b/src/contentScript/textEditorDom.ts index f09d161afd..9a7a063869 100644 --- a/src/contentScript/textEditorDom.ts +++ b/src/contentScript/textEditorDom.ts @@ -53,8 +53,6 @@ export async function insertAtCursorWithCustomEditorSupport({ "contentScript context required for editor JavaScript integrations", ); - console.debug("insertAtCursorWithCustomEditorSupport", element); - // `textFieldEdit` handles focus required to insert the text. But, force focus to enable the user to keep typing window.focus(); element.focus(); diff --git a/src/utils/focusController.ts b/src/utils/focusController.ts index fc94571223..dff74ebd0a 100644 --- a/src/utils/focusController.ts +++ b/src/utils/focusController.ts @@ -15,43 +15,24 @@ * along with this program. If not, see . */ -import { uuidv4 } from "@/types/helpers"; -import type { UUID } from "@/types/stringTypes"; - -const FOCUS_CONTROLLER_UUID_SYMBOL = Symbol.for("focus-controller-uuid"); - declare global { - interface Window { - [FOCUS_CONTROLLER_UUID_SYMBOL]?: UUID; - } + // eslint-disable-next-line no-var, @shopify/prefer-module-scope-constants -- It must be a var for types to work + var PB_FOCUS_CONTROLLER: boolean; } -class FocusController { - /** - * The last saved focused element - * @private - */ - private focusedElement: HTMLElement | undefined; - - /** - * Nonce to detect multiple imports of focusController - * @private - */ - private readonly nonce = uuidv4(); +let focusedElement: HTMLElement | undefined; - constructor() { - // eslint-disable-next-line security/detect-object-injection -- symbol - if (window[FOCUS_CONTROLLER_UUID_SYMBOL]) { - console.warn( - // eslint-disable-next-line security/detect-object-injection -- symbol - `focusController(${this.nonce}): ${window[FOCUS_CONTROLLER_UUID_SYMBOL]} already added to window`, - ); - } else { - // eslint-disable-next-line security/detect-object-injection -- symbol - window[FOCUS_CONTROLLER_UUID_SYMBOL] = this.nonce; - } - } +if (globalThis.PB_FOCUS_CONTROLLER) { + // If you're seeing this error message, it's likely this file was imported from multiple content scripts. + // This focusController module should only be imported from the main content script. + console.error( + "focusController: there should only be one focusController instance per window", + ); +} else { + globalThis.PB_FOCUS_CONTROLLER = true; +} +const focusController = { /** * Saves the focus of the current focused element so that it can be restored later * @note This doesn't behave as you'd expect across iframes @@ -60,18 +41,18 @@ class FocusController { // Only HTMLElements can have their focus restored, but we're currently ignoring this distinction const active = document.activeElement as HTMLElement; - if (this.focusedElement != null && this.focusedElement !== active) { + if (focusedElement != null && focusedElement !== active) { console.warn( - `focusController(${this.nonce}): the previously-saved focus is being overridden`, + "focusController: the previously-saved focus is being overridden", { - previous: this.focusedElement, + previous: focusedElement, active, }, ); } - this.focusedElement = active; - } + focusedElement = active; + }, /** * Restores the focus to the last saved item, if it hasn't already been restored @@ -82,27 +63,22 @@ class FocusController { (document.activeElement as HTMLElement)?.blur?.(); // `focusedElement === HTMLElement`: Restore focus if it's an HTMLElement, otherwise silently ignore it - this.focusedElement?.focus?.(); + focusedElement?.focus?.(); - this.focusedElement = undefined; - } + focusedElement = undefined; + }, /** Clear saved value without restoring focus */ clear(): void { - this.focusedElement = undefined; - } + focusedElement = undefined; + }, /** * Gets the last saved item or the current active item, if there is no saved item */ get(): HTMLElement { - return this.focusedElement ?? (document.activeElement as HTMLElement); - } -} - -/** - * Singleton instance of the focus controller - */ -const focusController = new FocusController(); + return focusedElement ?? (document.activeElement as HTMLElement); + }, +} as const; export default focusController;