Skip to content

Commit e2a03f1

Browse files
committed
fix(FR-2874): defer BAICodeEditor reveal until Monaco CSS commits
The earlier `onMount` overlay still let Monaco's `.inputarea` flash through. Root cause: `editor.main.css` is loaded by Monaco's AMD `css!` plugin asynchronously, while `onMount` fires synchronously inside `monaco.editor.create()` — *before* the browser has committed the new stylesheet. Flipping the overlay off in `onMount` therefore still exposes one paint of the unstyled native textarea. Wait two `requestAnimationFrame`s in `onMount` before flipping `isEditorReady`: the first lets the stylesheet commit, the second lets the browser paint the new styles. Then drop the overlay. Also switch the hidden state from `visibility: hidden` to `opacity: 0` + `pointer-events: none` (and the overlay to `z-index: 1`) so the unstyled textarea can't be tabbed into during the brief window.
1 parent 11e5c8b commit e2a03f1

1 file changed

Lines changed: 29 additions & 10 deletions

File tree

react/src/components/BAICodeEditor.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,25 @@ const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
5757
const { isDarkMode } = useThemeMode();
5858
const { token } = theme.useToken();
5959

60-
// Monaco's `loading` prop only covers the AMD loader phase. Once the loader
61-
// finishes, @monaco-editor/react mounts a container whose initial DOM
62-
// (a raw `<textarea>` used for input capture) renders before the Monaco
63-
// instance has applied its own stylesheet, producing a brief flash of an
64-
// unstyled textarea. Track the post-mount transition explicitly and keep
65-
// the skeleton on top until `onMount` fires.
66-
const [isEditorMounted, setIsEditorMounted] = useState(false);
60+
// Monaco's `.inputarea` is a real `<textarea>` Monaco uses for keyboard
61+
// input. Its "invisible" styling (opacity 0, 1×1 px, etc.) lives in
62+
// `editor.main.css`, which is loaded via Monaco's own AMD `css!` plugin
63+
// asynchronously. There is a window where Monaco's JS has already
64+
// appended the textarea to the DOM but the stylesheet has not been
65+
// committed by the browser yet — so it renders with default browser
66+
// textarea chrome and flashes visibly.
67+
//
68+
// `onMount` fires synchronously inside `monaco.editor.create`, *before*
69+
// the next paint, so flipping the overlay off there can still expose
70+
// the unstyled textarea for one frame. Defer the reveal by two rAFs:
71+
// the first lets Monaco's stylesheet commit; the second lets the new
72+
// styles paint. Only then do we drop the skeleton overlay.
73+
const [isEditorReady, setIsEditorReady] = useState(false);
6774
const handleMount: OnMount = (editor, monaco) => {
68-
setIsEditorMounted(true);
6975
onMount?.(editor, monaco);
76+
requestAnimationFrame(() => {
77+
requestAnimationFrame(() => setIsEditorReady(true));
78+
});
7079
};
7180

7281
const [script, setScript] = useControllableState_deprecated<string>({
@@ -96,7 +105,16 @@ const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
96105
}}
97106
>
98107
<Suspense fallback={loadingFallback}>
99-
<div style={{ visibility: isEditorMounted ? 'visible' : 'hidden' }}>
108+
<div
109+
style={{
110+
opacity: isEditorReady ? 1 : 0,
111+
// Stay in layout so Monaco can measure the container, but suppress
112+
// input/focus while we still consider the editor "not ready" —
113+
// otherwise tabbing into the unstyled native textarea is possible.
114+
pointerEvents: isEditorReady ? 'auto' : 'none',
115+
transition: isEditorReady ? 'opacity 60ms linear' : 'none',
116+
}}
117+
>
100118
<MonacoEditor
101119
language={MONACO_LANGUAGE_MAP[language]}
102120
height={height}
@@ -117,11 +135,12 @@ const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
117135
{...editorProps}
118136
/>
119137
</div>
120-
{!isEditorMounted && (
138+
{!isEditorReady && (
121139
<div
122140
style={{
123141
position: 'absolute',
124142
inset: 0,
143+
zIndex: 1,
125144
background: token.colorBgContainer,
126145
}}
127146
>

0 commit comments

Comments
 (0)