From 513d78681bd74547a5851934dff230809a4362b4 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sat, 19 Oct 2024 15:14:35 +0300 Subject: [PATCH] fix: After modifying style, it takes two clicks to select the next instance (#4314) ## Description Style Source looses focus after adding Token closes #4309 closes #4306 ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file --- apps/builder/app/builder/builder.tsx | 11 +++++++++++ apps/builder/app/canvas/shared/inert.ts | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) 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(); + }); +}