1- import { useCallback , useEffect , useState , type ReactNode } from "react" ;
1+ import { useEffect , useState , type ReactNode } from "react" ;
22import { useStore } from "@nanostores/react" ;
33import { TooltipProvider } from "@radix-ui/react-tooltip" ;
44import { usePublish , $publisher } from "~/shared/pubsub" ;
@@ -32,10 +32,10 @@ import {
3232 $authTokenPermissions ,
3333 $publisherHost ,
3434 $imageLoader ,
35- $textEditingInstanceSelector ,
3635 $isDesignMode ,
3736 $isContentMode ,
3837 $userPlanFeatures ,
38+ subscribeModifierKeys ,
3939} from "~/shared/nano-states" ;
4040import { $settings , type Settings } from "./shared/client-settings" ;
4141import { builderUrl , getCanvasUrl } from "~/shared/router-utils" ;
@@ -56,7 +56,6 @@ import {
5656import { CloneProjectDialog } from "~/shared/clone-project" ;
5757import type { TokenPermissions } from "@webstudio-is/authorization-token" ;
5858import { useToastErrors } from "~/shared/error/toast-error" ;
59- import { canvasApi } from "~/shared/canvas-api" ;
6059import { loadBuilderData , setBuilderData } from "~/shared/builder-data" ;
6160import { initBuilderApi } from "~/shared/builder-api" ;
6261import { updateWebstudioData } from "~/shared/instance-utils" ;
@@ -68,6 +67,7 @@ import {
6867 initCopyPaste ,
6968 initCopyPasteForContentEditMode ,
7069} from "~/shared/copy-paste/init-copy-paste" ;
70+ import { useInertHandlers } from "./shared/inert-handlers" ;
7171
7272registerContainers ( ) ;
7373
@@ -320,10 +320,12 @@ export const Builder = ({
320320 // @todo we need to forward the events from canvas to builder and avoid importing this
321321 // in both places
322322 initCopyPaste ( abortController ) ;
323+ subscribeModifierKeys ( { signal : abortController . signal } ) ;
323324 }
324325
325326 if ( isContentMode ) {
326327 initCopyPasteForContentEditMode ( abortController ) ;
328+ subscribeModifierKeys ( { signal : abortController . signal } ) ;
327329 }
328330
329331 return ( ) => {
@@ -344,57 +346,15 @@ export const Builder = ({
344346
345347 const canvasUrl = getCanvasUrl ( ) ;
346348
347- /**
348- * Prevents Lexical text editor from stealing focus during rendering.
349- * Sets the inert attribute on the canvas body element and disables the text editor.
350- *
351- * This must be done synchronously to avoid the following issue:
352- *
353- * 1. Text editor is in edit state.
354- * 2. User focuses on the builder (e.g., clicks any input).
355- * 3. The text editor blur event triggers, causing a rerender on data change (data saved in onBlur).
356- * 4. Text editor rerenders, stealing focus from the builder.
357- * 5. Inert attribute is set asynchronously, but focus is already lost.
358- *
359- * Synchronous focusing and setInert prevent the text editor from focusing on render.
360- * This cannot be handled inside the canvas because the text editor toolbar is in the builder and focus events in the canvas should be ignored.
361- *
362- * Use onPointerDown instead of onFocus because Radix focus lock triggers on text edit blur
363- * before the focusin event when editing text inside a Radix dialog.
364- */
365- const handlePointerDown = useCallback ( ( event : React . PointerEvent ) => {
366- // Ignore toolbar focus events. See the onFocus handler in text-toolbar.tsx
367- if ( false === event . defaultPrevented ) {
368- canvasApi . setInert ( ) ;
369- $textEditingInstanceSelector . set ( undefined ) ;
370- }
371- } , [ ] ) ;
372-
373- /**
374- * Prevent Radix from stealing focus during editing in the style sources
375- * For example, when the user select or create new style source item inside a dialog.
376- */
377- const handleKeyDown = useCallback ( ( event : React . KeyboardEvent ) => {
378- if ( event . target instanceof HTMLInputElement ) {
379- canvasApi . setInert ( ) ;
380- }
381- } , [ ] ) ;
382-
383- /**
384- * Prevent Radix from stealing focus during editing in the settings panel.
385- * For example, when the user modifies the text content of an H1 element inside a dialog.
386- */
387- const handleInput = useCallback ( ( ) => {
388- canvasApi . setInert ( ) ;
389- } , [ ] ) ;
349+ const inertHandlers = useInertHandlers ( ) ;
390350
391351 return (
392352 < TooltipProvider >
393353 < div
394354 style = { { display : "contents" } }
395- onPointerDown = { handlePointerDown }
396- onInput = { handleInput }
397- onKeyDown = { handleKeyDown }
355+ onPointerDown = { inertHandlers . onPointerDown }
356+ onInput = { inertHandlers . onInput }
357+ onKeyDown = { inertHandlers . onKeyDown }
398358 >
399359 < ChromeWrapper
400360 isPreviewMode = { isPreviewMode }
0 commit comments