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+ document . 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