diff --git a/apps/builder/app/builder/builder.tsx b/apps/builder/app/builder/builder.tsx index 9888f6527b46..127ead26dee8 100644 --- a/apps/builder/app/builder/builder.tsx +++ b/apps/builder/app/builder/builder.tsx @@ -345,6 +345,16 @@ export const Builder = ({ } }, []); + /** + * Prevent Radix from stealing focus during editing in the style sources + * For example, when the user select or create new style source item inside a dialog. + */ + const handleKeyDown = useCallback((event: React.KeyboardEvent) => { + if (event.target instanceof HTMLInputElement) { + canvasApi.setInert(); + } + }, []); + /** * Prevent Radix from stealing focus during editing in the settings panel. * For example, when the user modifies the text content of an H1 element inside a dialog. @@ -359,6 +369,7 @@ export const Builder = ({ style={{ display: "contents" }} onPointerDown={handlePointerDown} onInput={handleInput} + onKeyDown={handleKeyDown} > { + if (resetTimeoutHandle === undefined) { + return; + } document.body.removeAttribute("inert"); clearTimeout(resetTimeoutHandle); resetTimeoutHandle = undefined; }; +let lastPointerEventTime = Date.now(); // 1000 ms is a reasonable time for the preview to reset. // Anyway should never happen after user has finished preview changes (can happen during preview changes) const AUTO_DISPOSE_INERT_TIMEOUT = 1000; @@ -13,7 +17,15 @@ const AUTO_DISPOSE_INERT_TIMEOUT = 1000; // A brief delay to ensure mutation observers within the focus scope are activated by the preview changes. const DISPOSE_INERT_TIMEOUT = 300; +const PREVENT_INERT_TIMEOUT = 100; + const setAutoDisposeInert = (timeout: number) => { + // Some events in the builder can occur after clicking on the canvas (e.g., blur on an input field). + // In such cases, we should prevent 'inert' from being set and allow the selection to complete. + if (Date.now() - lastPointerEventTime < PREVENT_INERT_TIMEOUT) { + return; + } + document.body.setAttribute("inert", "true"); // To prevent a completely non-interactive canvas due to edge cases, @@ -31,3 +43,10 @@ const setAutoDisposeInert = (timeout: number) => { */ export const setInert = () => setAutoDisposeInert(AUTO_DISPOSE_INERT_TIMEOUT); export const resetInert = () => setAutoDisposeInert(DISPOSE_INERT_TIMEOUT); + +// window.self !== window.top means we are on canvas +if (typeof window !== "undefined" && window.self !== window.top) { + window.addEventListener("pointerdown", () => { + lastPointerEventTime = Date.now(); + }); +}