@@ -28,6 +28,7 @@ export function MonacoEditor({
2828 const initialValueRef = useRef ( value )
2929 const onChangeRef = useRef ( onChange )
3030 const lastValueRef = useRef ( value )
31+ const hoverPointerClientYRef = useRef < number | null > ( null )
3132
3233 useEffect ( ( ) => {
3334 onChangeRef . current = onChange
@@ -50,6 +51,9 @@ export function MonacoEditor({
5051 'ui-monospace, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Yu Gothic", "YuGothic", monospace' ,
5152 fontLigatures : false ,
5253 fontSize : 13 ,
54+ hover : {
55+ above : false ,
56+ } ,
5357 lineDecorationsWidth : 10 ,
5458 lineHeight : 20 ,
5559 lineNumbers : "off" ,
@@ -72,11 +76,61 @@ export function MonacoEditor({
7276 lastValueRef . current = nextValue
7377 onChangeRef . current ( nextValue )
7478 } )
79+ const hoverBelowOffset = editor . getOption ( monaco . editor . EditorOption . lineHeight )
80+ let hoverAnimationFrame = 0
81+ let adjustedHoverWidget : HTMLElement | null = null
82+ const getVisibleHoverWidget = ( ) => {
83+ const hover = container . querySelector < HTMLElement > ( ".monaco-hover:not(.hidden)" )
84+ return hover ?. closest < HTMLElement > ( ".monaco-resizable-hover" ) ?? null
85+ }
86+ const placeHoverBelowPointer = ( ) => {
87+ hoverAnimationFrame = 0
88+ const pointerClientY = hoverPointerClientYRef . current
89+ if ( pointerClientY === null ) return
90+
91+ const hoverWidget = getVisibleHoverWidget ( )
92+ if ( ! hoverWidget ) {
93+ adjustedHoverWidget = null
94+ return
95+ }
96+ if ( hoverWidget === adjustedHoverWidget ) return
97+
98+ const hoverRect = hoverWidget . getBoundingClientRect ( )
99+ if ( hoverRect . height === 0 || hoverRect . bottom > pointerClientY + 1 ) return
100+ if ( window . innerHeight - pointerClientY < hoverRect . height + hoverBelowOffset ) return
101+
102+ const containerRect = container . getBoundingClientRect ( )
103+ const nextTop = pointerClientY - containerRect . top + hoverBelowOffset
104+ hoverWidget . style . top = `${ Math . max ( 0 , Math . ceil ( nextTop ) ) } px`
105+ adjustedHoverWidget = hoverWidget
106+ }
107+ const scheduleHoverPlacement = ( ) => {
108+ if ( hoverAnimationFrame !== 0 ) return
109+ hoverAnimationFrame = window . requestAnimationFrame ( placeHoverBelowPointer )
110+ }
111+ const handlePointerMove = ( event : PointerEvent ) => {
112+ hoverPointerClientYRef . current = event . clientY
113+ if ( getVisibleHoverWidget ( ) ) return
114+ scheduleHoverPlacement ( )
115+ }
116+ container . addEventListener ( "pointermove" , handlePointerMove , true )
117+ const hoverObserver = new MutationObserver ( scheduleHoverPlacement )
118+ hoverObserver . observe ( container , {
119+ attributeFilter : [ "class" , "style" ] ,
120+ attributes : true ,
121+ childList : true ,
122+ subtree : true ,
123+ } )
75124
76125 modelRef . current = model
77126 editorRef . current = editor
78127
79128 return ( ) => {
129+ container . removeEventListener ( "pointermove" , handlePointerMove , true )
130+ hoverObserver . disconnect ( )
131+ if ( hoverAnimationFrame !== 0 ) {
132+ window . cancelAnimationFrame ( hoverAnimationFrame )
133+ }
80134 subscription . dispose ( )
81135 editor . dispose ( )
82136 model . dispose ( )
0 commit comments