Skip to content

Commit f011e4f

Browse files
authored
fix(ui): code field adjusts its height to its content dynamically. Scrolling over the container is not prevented. (#8209)
Closes #8051. - The scrolling problem reported in the issue is solved with Monaco's `alwaysConsumeMouseWheel` property. - In addition to that, it is necessary to dynamically adjust the height of the editor so that it fits its content and does not require scrolling. - Additionally, I disabled the `overviewRuler` which is the indicator strip on the side (above the scrollbar) that makes no sense when there is no scroll. **Gotchas** - Unfortunately, there is a bit of CLS since the editor doesn't know the height of its content before rendering. In Lexical these things are possible since it has a lifecycle that allows interaction before or after rendering, but this is not the case with Monaco. - I've noticed that sometimes when I press enter the letters in the editor flicker or move with a small, rapid shake. Maybe it has to do with the new height being calculated as an effect. ## Before https://github.com/user-attachments/assets/0747f79d-a3ac-42ae-8454-0bf46dc43f34 ## After https://github.com/user-attachments/assets/976ab97c-9d20-4e93-afb5-023083a6608b
1 parent c0aad3c commit f011e4f

File tree

6 files changed

+69
-9
lines changed

6 files changed

+69
-9
lines changed

packages/ui/src/elements/CodeEditor/CodeEditor.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22
import EditorImport from '@monaco-editor/react'
3-
import React from 'react'
3+
import React, { useState } from 'react'
44

55
import type { Props } from './types.js'
66

@@ -13,8 +13,8 @@ const Editor = (EditorImport.default || EditorImport) as unknown as typeof Edito
1313
const baseClass = 'code-editor'
1414

1515
const CodeEditor: React.FC<Props> = (props) => {
16-
const { className, height, options, readOnly, ...rest } = props
17-
16+
const { className, options, readOnly, ...rest } = props
17+
const [dynamicHeight, setDynamicHeight] = useState(20)
1818
const { theme } = useTheme()
1919

2020
const classes = [
@@ -29,21 +29,38 @@ const CodeEditor: React.FC<Props> = (props) => {
2929
return (
3030
<Editor
3131
className={classes}
32-
height={height}
33-
loading={<ShimmerEffect height={height} />}
32+
loading={<ShimmerEffect height={dynamicHeight} />}
3433
options={{
3534
detectIndentation: true,
35+
hideCursorInOverviewRuler: true,
3636
minimap: {
3737
enabled: false,
3838
},
39+
overviewRulerBorder: false,
3940
readOnly: Boolean(readOnly),
41+
scrollbar: {
42+
alwaysConsumeMouseWheel: false,
43+
},
4044
scrollBeyondLastLine: false,
4145
tabSize: 2,
4246
wordWrap: 'on',
4347
...options,
4448
}}
4549
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
4650
{...rest}
51+
// Since we are not building an IDE and the container
52+
// can already have scrolling, we want the height of the
53+
// editor to fit its content.
54+
// See: https://github.com/microsoft/monaco-editor/discussions/3677
55+
height={dynamicHeight}
56+
onChange={(value, ev) => {
57+
rest.onChange?.(value, ev)
58+
setDynamicHeight(value.split('\n').length * 18 + 2)
59+
}}
60+
onMount={(editor, monaco) => {
61+
rest.onMount?.(editor, monaco)
62+
setDynamicHeight(editor.getValue().split('\n').length * 18 + 2)
63+
}}
4764
/>
4865
)
4966
}

packages/ui/src/elements/CodeEditor/index.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@ const LazyEditor = lazy(() => import('./CodeEditor.js'))
1010
export type { Props }
1111

1212
export const CodeEditor: React.FC<Props> = (props) => {
13-
const { height = '35vh' } = props
14-
1513
return (
16-
<Suspense fallback={<ShimmerEffect height={height} />}>
17-
<LazyEditor {...props} height={height} />
14+
<Suspense fallback={<ShimmerEffect />}>
15+
<LazyEditor {...props} />
1816
</Suspense>
1917
)
2018
}

test/fields/collections/Lexical/blocks.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,13 @@ export const TabBlock: Block = {
281281
},
282282
],
283283
}
284+
285+
export const CodeBlock: Block = {
286+
fields: [
287+
{
288+
name: 'code',
289+
type: 'code',
290+
},
291+
],
292+
slug: 'code',
293+
}

test/fields/collections/Lexical/e2e/blocks/e2e.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,5 +1016,27 @@ describe('lexicalBlocks', () => {
10161016
timeout: POLL_TOPASS_TIMEOUT,
10171017
})
10181018
})
1019+
1020+
test('dynamic height of code editor is correctly calculated', async () => {
1021+
await navigateToLexicalFields()
1022+
1023+
const codeEditor = page.locator('.code-editor')
1024+
1025+
await codeEditor.scrollIntoViewIfNeeded()
1026+
await expect(codeEditor).toBeVisible()
1027+
1028+
const height = (await codeEditor.boundingBox()).height
1029+
1030+
await expect(() => {
1031+
expect(height).toBe(56)
1032+
}).toPass()
1033+
await codeEditor.click()
1034+
await page.keyboard.press('Enter')
1035+
1036+
const height2 = (await codeEditor.boundingBox()).height
1037+
await expect(() => {
1038+
expect(height2).toBe(74)
1039+
}).toPass()
1040+
})
10191041
})
10201042
})

test/fields/collections/Lexical/generateLexicalRichText.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ export function generateLexicalRichText(): TypedEditorState<
303303
blockType: 'tabBlock',
304304
},
305305
},
306+
{
307+
format: '',
308+
type: 'block',
309+
version: 2,
310+
fields: {
311+
id: '666c9e0b189d72626ea301fa',
312+
blockName: '',
313+
blockType: 'code',
314+
code: 'Some code\nhello\nworld',
315+
},
316+
},
306317
],
307318
direction: 'ltr',
308319
},

test/fields/collections/Lexical/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020

2121
import { lexicalFieldsSlug } from '../../slugs.js'
2222
import {
23+
CodeBlock,
2324
ConditionalLayoutBlock,
2425
RadioButtonsBlock,
2526
RelationshipBlock,
@@ -80,6 +81,7 @@ const editorConfig: ServerEditorConfig = {
8081
RadioButtonsBlock,
8182
ConditionalLayoutBlock,
8283
TabBlock,
84+
CodeBlock,
8385
],
8486
inlineBlocks: [
8587
{

0 commit comments

Comments
 (0)