-
Notifications
You must be signed in to change notification settings - Fork 2
/
cursor.ts
84 lines (70 loc) · 2.68 KB
/
cursor.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
/** @module cursor */
import { BasicExtension, InputSelection, PrismEditor } from "../index.js"
import { createTemplate, addTextareaListener } from "../core.js"
import { getLineBefore } from "../utils/index.js"
import { getLineEnd, scrollToEl } from "../utils/local.js"
import { defaultCommands } from "./commands.js"
/** Postion of the cursor relative to the editors overlays. */
export type CursorPosition = {
top: number
bottom: number
left: number
right: number
height: number
}
export interface Cursor extends BasicExtension {
/** Gets the cursor position relative to the editor overlays. */
getPosition(): CursorPosition
/** Scrolls the cursor into view. */
scrollIntoView(): void
/** The empty span element representing the cursor. */
element: HTMLSpanElement
}
const cursorTemplate = createTemplate(
'<div style=position:absolute;top:0;opacity:0;padding:inherit> <span></span> ',
)
/**
* Extension which can be used to calculate the position of the cursor and scroll it into view.
* This is used by the {@link defaultCommands} extension to keep the cursor in view while typing.
*/
export const cursorPosition = () => {
let cEditor: PrismEditor,
prevBefore = " ",
prevAfter = " "
const cursorContainer = cursorTemplate(),
[before, cursor, after] = <[Text, HTMLSpanElement, Text]>(<unknown>cursorContainer.childNodes),
selectionChange = ([start, end, direction]: InputSelection) => {
let { value, activeLine } = cEditor,
position = direction == "backward" ? start : end,
newBefore = getLineBefore(value, position),
newAfter = value.slice(position, getLineEnd(value, position))
if (!newBefore && !newAfter) newAfter = " "
if (prevBefore != newBefore) before.data = prevBefore = newBefore
if (prevAfter != newAfter) after.data = prevAfter = newAfter
if (cursorContainer.parentNode != activeLine) activeLine.prepend(cursorContainer)
},
scrollIntoView = () => scrollToEl(cEditor, cursor)
const self: Cursor = editor => {
editor.addListener("selectionChange", selectionChange)
cEditor = editor
editor.extensions.cursor = self
addTextareaListener(editor, "input", e => {
if (/history/.test((<InputEvent>e).inputType)) scrollIntoView()
})
if (editor.activeLine) selectionChange(editor.getSelection())
}
self.getPosition = () => {
const rect1 = cursor.getBoundingClientRect(),
rect2 = cEditor.overlays.getBoundingClientRect()
return {
top: rect1.y - rect2.y,
bottom: rect2.bottom - rect1.bottom,
left: rect1.x - rect2.x,
right: rect2.right - rect1.x,
height: rect1.height,
}
}
self.scrollIntoView = scrollIntoView
self.element = cursor
return self
}