-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
dom.ts
93 lines (81 loc) · 2.83 KB
/
dom.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Gets the position of an element relative to the whole page
const getAbsoluteElementPos = (element: HTMLElement) => {
const bodyRect = document.body.getBoundingClientRect()
const elemRect = element.getBoundingClientRect()
const top = elemRect.top - bodyRect.top
const left = elemRect.left - bodyRect.left
return {
top,
left,
}
}
// Hide it
const resetHover = () => {
const globalPopover = document.getElementById('twoslash-mouse-hover-info')
if (globalPopover) globalPopover.style.display = 'none'
}
// Get it
const findOrCreateTooltip = () => {
let globalPopover = document.getElementById('twoslash-mouse-hover-info')
if (!globalPopover) {
globalPopover = document.createElement('div')
globalPopover.style.position = 'absolute'
globalPopover.id = 'twoslash-mouse-hover-info'
document.body.appendChild(globalPopover)
}
return globalPopover
}
const getRootRect = (element: HTMLElement): DOMRect => {
if (element.nodeName.toLowerCase() === 'pre') {
return element.getBoundingClientRect();
}
return getRootRect(element.parentElement!);
}
/**
* Creates the main mouse over popup for LSP info using the DOM API.
* It is expected to be run inside a `useEffect` block inside your main
* exported component in Gatsby.
*
* @example
* import React, { useEffect } from "react"
* import { setupTwoslashHovers } from "gatsby-remark-shiki-twoslash/dom";
*
* export default () => {
* // Add a the hovers
* useEffect(setupTwoslashHovers, [])
*
* // Normal JSX
* return </>
* }
*
*/
export const setupTwoslashHovers = () => {
// prettier-ignore
const twoslashes = document.querySelectorAll(".shiki.twoslash .code-container code")
// Gets triggered on the spans inside the codeblocks
const hover = (event: Event) => {
const hovered = event.target as HTMLElement
if (hovered.nodeName !== 'DATA-LSP') return resetHover()
const message = hovered.getAttribute('lsp')!
const position = getAbsoluteElementPos(hovered)
// Create or re-use the current hover dic
const tooltip = findOrCreateTooltip()
// Use a textarea to un-htmlencode for presenting to the user
var txt = document.createElement('textarea')
txt.innerHTML = message
tooltip.textContent = txt.value
// Offset it a bit from the mouse and present it at an absolute position
const yOffset = 20
tooltip.style.display = 'block'
tooltip.style.top = `${position.top + yOffset}px`
tooltip.style.left = `${position.left}px`
// limit the width of the tooltip to the outer container (pre)
const rootRect = getRootRect(hovered);
const relativeLeft = position.left - rootRect.x;
tooltip.style.maxWidth = `${rootRect.width - relativeLeft}px`;
}
twoslashes.forEach((codeblock) => {
codeblock.addEventListener('mouseover', hover)
codeblock.addEventListener('mouseout', resetHover)
})
}