Skip to content

Commit 291c783

Browse files
committed
Fix playground Monaco hover positioning
1 parent 8092b41 commit 291c783

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

playgrounds/tskm/src/components/MonacoEditor.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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()

playgrounds/tskm/src/styles.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ button {
161161
overflow: hidden;
162162
}
163163

164+
.pane:has(.monaco-code-editor) .pane__body {
165+
overflow: visible;
166+
}
167+
164168
.pane-note {
165169
color: #667064;
166170
font-size: 0.78rem;
@@ -402,7 +406,7 @@ button {
402406
.monaco-code-editor {
403407
height: 100%;
404408
min-height: 240px;
405-
overflow: hidden;
409+
overflow: visible;
406410
background: #fffefb;
407411
}
408412

0 commit comments

Comments
 (0)