Skip to content

Commit

Permalink
#7827: Restore MV3 support (#7831)
Browse files Browse the repository at this point in the history
---------
Co-authored-by: Todd Schiller <[email protected]>
  • Loading branch information
fregante authored Mar 6, 2024
1 parent c315c5d commit 5df7760
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 83 deletions.
2 changes: 0 additions & 2 deletions src/bricks/effects/InsertAtCursorEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
63 changes: 34 additions & 29 deletions src/contentScript/sidebarController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
);
});
}
}
2 changes: 0 additions & 2 deletions src/contentScript/textEditorDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
76 changes: 26 additions & 50 deletions src/utils/focusController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

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
Expand All @@ -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
Expand All @@ -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;

0 comments on commit 5df7760

Please sign in to comment.