Skip to content

Commit 07e14b2

Browse files
authored
Merge pull request #15 from WebCoder49/autocomplete
Autocomplete popup
2 parents ef1d27d + 140cac4 commit 07e14b2

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ The next step is to set up a `template` to link `code-input` to your syntax-high
8484
<!--...-->
8585
<script src="plugins/autodetect.js"></script>
8686
<script src="plugins/indent.js"></script>
87+
<script src="plugins/autocomplete.js"></script>
8788
<!--...-->
8889
<script>
8990
codeInput.registerTemplate("syntax-highlighted",

code-input.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ code-input {
99
top: 0;
1010
left: 0;
1111
display: block;
12+
/* Only scroll inside elems */
13+
overflow: hidden;
1214

1315
/* Normal inline styles */
1416
padding: 8px;

plugins/autocomplete.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
code-input .code-input_autocomplete_popup {
2+
display: block;
3+
position: absolute;
4+
margin-top: 1em; /* Popup shows under the caret */
5+
z-index: 100;
6+
}
7+
8+
9+
code-input .code-input_autocomplete_testpos {
10+
opacity: 0;
11+
}

plugins/autocomplete.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
*
3+
*/
4+
codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
5+
/**
6+
* Pass in a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
7+
* @param {function} updatePopupCallback
8+
*/
9+
constructor(updatePopupCallback) {
10+
super();
11+
this.updatePopupCallback = updatePopupCallback;
12+
}
13+
/* When a key is pressed, or scrolling occurs, update the autocomplete */
14+
updatePopup(codeInput, onlyScrolled) {
15+
let textarea = codeInput.querySelector("textarea");
16+
let caretCoords = this.getCaretCoordinates(codeInput, textarea, textarea.selectionEnd, onlyScrolled);
17+
let popupElem = codeInput.querySelector(".code-input_autocomplete_popup");
18+
popupElem.style.top = caretCoords.top + "px";
19+
popupElem.style.left = caretCoords.left + "px";
20+
21+
if(!onlyScrolled) {
22+
this.updatePopupCallback(popupElem, textarea, textarea.selectionEnd);
23+
}
24+
}
25+
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
26+
afterElementsAdded(codeInput) {
27+
let popupElem = document.createElement("div");
28+
popupElem.classList.add("code-input_autocomplete_popup");
29+
codeInput.appendChild(popupElem);
30+
31+
let testPosElem = document.createElement("pre");
32+
testPosElem.classList.add("code-input_autocomplete_testpos");
33+
codeInput.appendChild(testPosElem); // Styled like first pre, but first pre found to update
34+
35+
let textarea = codeInput.querySelector("textarea");
36+
textarea.addEventListener("keyup", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
37+
textarea.addEventListener("click", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
38+
textarea.addEventListener("scroll", this.updatePopup.bind(this, codeInput, true)); // Override this+args in bind - just scrolling
39+
}
40+
/**
41+
* Return the coordinates of the caret in a code-input
42+
* @param {codeInput.CodeInput} codeInput
43+
* @param {HTMLElement} textarea
44+
* @param {Number} charIndex
45+
* @param {boolean} onlyScrolled True if no edits have been made to the text and the caret hasn't been repositioned
46+
* @returns
47+
*/
48+
getCaretCoordinates(codeInput, textarea, charIndex, onlyScrolled) {
49+
let afterSpan;
50+
if(onlyScrolled) {
51+
// No edits to text; don't update element - span at index 1 is after span
52+
let spans = codeInput.querySelector(".code-input_autocomplete_testpos").querySelectorAll("span");
53+
if(spans.length < 2) {
54+
// Hasn't saved text in test pre to find pos
55+
// Need to regenerate text in test pre
56+
return this.getCaretCoordinates(codeInput, textarea, charIndex, false);
57+
}
58+
afterSpan = spans[1];
59+
} else {
60+
/* Inspired by https://github.com/component/textarea-caret-position */
61+
let testPosElem = codeInput.querySelector(".code-input_autocomplete_testpos");
62+
63+
let beforeSpan = document.createElement("span");
64+
beforeSpan.textContent = textarea.value.substring(0, charIndex);
65+
afterSpan = document.createElement("span");
66+
afterSpan.textContent = "."; // Placeholder
67+
68+
// Clear test pre - https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
69+
while (testPosElem.firstChild) {
70+
testPosElem.removeChild(testPosElem.firstChild);
71+
}
72+
testPosElem.appendChild(beforeSpan);
73+
testPosElem.appendChild(afterSpan);
74+
}
75+
return {"top": afterSpan.offsetTop - textarea.scrollTop, "left": afterSpan.offsetLeft - textarea.scrollLeft};
76+
}
77+
observedAttributes = [];
78+
updatePopupCallback = function() {};
79+
}

0 commit comments

Comments
 (0)