@webcoder49/code-input 2.1.0 โ†’ 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcoder49/code-input",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Fully customisable, editable syntax-highlighted textareas.",
5
5
  "browser": "code-input.js",
6
6
  "scripts": {
package/plugins/README.md CHANGED
@@ -7,6 +7,14 @@
7
7
 
8
8
  ---
9
9
 
10
+ ### Auto-Close Brackets
11
+ Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
12
+ is activated for.
13
+
14
+ Files: [auto-close-brackets.js](./auto-close-brackets.js)
15
+
16
+ [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/qBgGGKR)
17
+
10
18
  ### Autocomplete
11
19
  Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
12
20
 
@@ -21,22 +29,29 @@ Files: [autodetect.js](./autodetect.js)
21
29
 
22
30
  [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/eYLyMae)
23
31
 
24
- ### Debounce Update
25
- Debounce the update and highlighting function ([What is Debouncing?](https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1))
32
+ ### Find and Replace
33
+ Add Find-and-Replace (Ctrl+F for find, Ctrl+H for replace by default, or when JavaScript triggers it) functionality to the code editor.
34
+
35
+ Files: [find-and-replace.js](./find-and-replace.js) / [find-and-replace.css](./find-and-replace.css)
36
+
37
+ [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/oNVVBBz)
38
+
39
+ ### Go To Line
40
+ Add a feature to go to a specific line when a line number is given (or column as well, in the format line no:column no) that appears when (optionally) Ctrl+G is pressed or when JavaScript triggers it.
26
41
 
27
- Files: [debounce-update.js](./debounce-update.js)
42
+ Files: [go-to-line.js](./go-to-line.js) / [go-to-line.css](./go-to-line.css)
28
43
 
29
- [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/GRXyxzV)
44
+ [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/YzBMOXP)
30
45
 
31
46
  ### Indent
32
- Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab. **Supports tab characters and custom numbers of spaces as indentation.**
47
+ Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab. **Supports tab characters and custom numbers of spaces as indentation, as well as (optionally) brackets typed affecting indentation.**
33
48
 
34
49
  Files: [indent.js](./indent.js)
35
50
 
36
51
  [๐Ÿš€ *CodePen Demo*](https://codepen.io/WebCoder49/pen/WNgdzar)
37
52
 
38
53
  ### Prism Line Numbers
39
- Allows code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)
54
+ Allow code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)
40
55
 
41
56
  Files: [prism-line-numbers.css](./prism-line-numbers.css) (NO JS FILE)
42
57
 
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
3
+ * is activated for.
4
+ * Files: auto-close-brackets.js
5
+ */
6
+ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
7
+ bracketPairs = [];
8
+ bracketsOpenedStack = []; // Each item [closing bracket string, opening bracket location] Innermost at right so can know which brackets should be ignored when retyped
9
+
10
+ /**
11
+ * Create an auto-close brackets plugin to pass into a template
12
+ * @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
13
+ */
14
+ constructor(bracketPairs={"(": ")", "[": "]", "{": "}", '"': '"'}) {
15
+ super([]); // No observed attributes
16
+
17
+ this.bracketPairs = bracketPairs;
18
+ }
19
+
20
+ /* Add keystroke events */
21
+ afterElementsAdded(codeInput) {
22
+ codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) });
23
+ codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); });
24
+ }
25
+
26
+ /* Deal with the automatic creation of closing bracket when opening brackets are typed, and the ability to "retype" a closing
27
+ bracket where one has already been placed. */
28
+ checkBrackets(codeInput, event) {
29
+ if(event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) {
30
+ // Check if a closing bracket is typed
31
+ for(let openingBracket in this.bracketPairs) {
32
+ let closingBracket = this.bracketPairs[openingBracket];
33
+ if(event.data == closingBracket) {
34
+ // "Retype" a closing bracket, i.e. just move caret
35
+ codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd += 1;
36
+ event.preventDefault();
37
+ break;
38
+ }
39
+ }
40
+ } else if(event.data in this.bracketPairs) {
41
+ // Opening bracket typed; Create bracket pair
42
+ let closingBracket = this.bracketPairs[event.data];
43
+ // Insert the closing bracket
44
+ document.execCommand("insertText", false, closingBracket);
45
+ // Move caret before the inserted closing bracket
46
+ codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1;
47
+ }
48
+ }
49
+
50
+ /* Deal with cases where a backspace deleting an opening bracket deletes the closing bracket straight after it as well */
51
+ checkBackspace(codeInput, event) {
52
+ if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) {
53
+ let closingBracket = this.bracketPairs[codeInput.textareaElement.value[codeInput.textareaElement.selectionStart-1]];
54
+ if(closingBracket != undefined && codeInput.textareaElement.value[codeInput.textareaElement.selectionStart] == closingBracket) {
55
+ // Opening bracket being deleted so delete closing bracket as well
56
+ codeInput.textareaElement.selectionEnd = codeInput.textareaElement.selectionStart + 1;
57
+ codeInput.textareaElement.selectionStart -= 1;
58
+ }
59
+ }
60
+ }
61
+ }
@@ -0,0 +1 @@
1
+ codeInput.plugins.AutoCloseBrackets=class extends codeInput.Plugin{bracketPairs=[];bracketsOpenedStack=[];constructor(a={"(":")","[":"]","{":"}",'"':"\""}){super([]),this.bracketPairs=a}afterElementsAdded(a){a.textareaElement.addEventListener("keydown",b=>{this.checkBackspace(a,b)}),a.textareaElement.addEventListener("beforeinput",b=>{this.checkBrackets(a,b)})}checkBrackets(a,b){if(b.data==a.textareaElement.value[a.textareaElement.selectionStart])for(let c in this.bracketPairs){let d=this.bracketPairs[c];if(b.data==d){a.textareaElement.selectionStart=a.textareaElement.selectionEnd+=1,b.preventDefault();break}}else if(b.data in this.bracketPairs){let c=this.bracketPairs[b.data];document.execCommand("insertText",!1,c),a.textareaElement.selectionStart=a.textareaElement.selectionEnd-=1}}checkBackspace(a,b){if("Backspace"==b.key&&a.textareaElement.selectionStart==a.textareaElement.selectionEnd){let b=this.bracketPairs[a.textareaElement.value[a.textareaElement.selectionStart-1]];b!=null&&a.textareaElement.value[a.textareaElement.selectionStart]==b&&(a.textareaElement.selectionEnd=a.textareaElement.selectionStart+1,a.textareaElement.selectionStart-=1)}}};
@@ -4,14 +4,14 @@
4
4
  */
5
5
  codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
6
6
  /**
7
- * Pass in a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
7
+ * Pass in a function to create a plugin that displays the popup that takes in (popup element, textarea, textarea.selectionEnd).
8
8
  * @param {function} updatePopupCallback a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
9
9
  */
10
10
  constructor(updatePopupCallback) {
11
11
  super([]); // No observed attributes
12
12
  this.updatePopupCallback = updatePopupCallback;
13
13
  }
14
- /* When a key is pressed, or scrolling occurs, update the autocomplete */
14
+ /* When a key is pressed, or scrolling occurs, update the popup position */
15
15
  updatePopup(codeInput, onlyScrolled) {
16
16
  let textarea = codeInput.textareaElement;
17
17
  let caretCoords = this.getCaretCoordinates(codeInput, textarea, textarea.selectionEnd, onlyScrolled);
@@ -23,20 +23,27 @@ codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
23
23
  this.updatePopupCallback(popupElem, textarea, textarea.selectionEnd);
24
24
  }
25
25
  }
26
- /* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
26
+ /* Create the popup element */
27
27
  afterElementsAdded(codeInput) {
28
28
  let popupElem = document.createElement("div");
29
29
  popupElem.classList.add("code-input_autocomplete_popup");
30
30
  codeInput.appendChild(popupElem);
31
31
 
32
- let testPosElem = document.createElement("pre");
33
- testPosElem.classList.add("code-input_autocomplete_testpos");
34
- codeInput.appendChild(testPosElem); // Styled like first pre, but first pre found to update
35
-
32
+ let testPosPre = document.createElement("pre");
33
+ testPosPre.setAttribute("aria-hidden", "true"); // Hide for screen readers
34
+ if(codeInput.template.preElementStyled) {
35
+ testPosPre.classList.add("code-input_autocomplete_testpos");
36
+ codeInput.appendChild(testPosPre); // Styled like first pre, but first pre found to update
37
+ } else {
38
+ let testPosCode = document.createElement("code");
39
+ testPosCode.classList.add("code-input_autocomplete_testpos");
40
+ testPosPre.appendChild(testPosCode);
41
+ codeInput.appendChild(testPosPre); // Styled like first pre, but first pre found to update
42
+ }
43
+
36
44
  let textarea = codeInput.textareaElement;
37
- textarea.addEventListener("keyup", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
38
- textarea.addEventListener("click", this.updatePopup.bind(this, codeInput, false)); // Override this+args in bind - not just scrolling
39
- textarea.addEventListener("scroll", this.updatePopup.bind(this, codeInput, true)); // Override this+args in bind - just scrolling
45
+ textarea.addEventListener("input", () => { this.updatePopup(codeInput, false)});
46
+ textarea.addEventListener("click", () => { this.updatePopup(codeInput, false)});
40
47
  }
41
48
  /**
42
49
  * Return the coordinates of the caret in a code-input
@@ -44,7 +51,7 @@ codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
44
51
  * @param {HTMLElement} textarea
45
52
  * @param {Number} charIndex
46
53
  * @param {boolean} onlyScrolled True if no edits have been made to the text and the caret hasn't been repositioned
47
- * @returns
54
+ * @returns {Object} {"top": CSS top value in pixels, "left": CSS left value in pixels}
48
55
  */
49
56
  getCaretCoordinates(codeInput, textarea, charIndex, onlyScrolled) {
50
57
  let afterSpan;
@@ -1 +1 @@
1
- codeInput.plugins.Autocomplete=class extends codeInput.Plugin{constructor(a){super([]),this.updatePopupCallback=a}updatePopup(a,b){let c=a.textareaElement,d=this.getCaretCoordinates(a,c,c.selectionEnd,b),e=a.querySelector(".code-input_autocomplete_popup");e.style.top=d.top+"px",e.style.left=d.left+"px",b||this.updatePopupCallback(e,c,c.selectionEnd)}afterElementsAdded(a){let b=document.createElement("div");b.classList.add("code-input_autocomplete_popup"),a.appendChild(b);let c=document.createElement("pre");c.classList.add("code-input_autocomplete_testpos"),a.appendChild(c);let d=a.textareaElement;d.addEventListener("keyup",this.updatePopup.bind(this,a,!1)),d.addEventListener("click",this.updatePopup.bind(this,a,!1)),d.addEventListener("scroll",this.updatePopup.bind(this,a,!0))}getCaretCoordinates(a,b,c,d){let e;if(d){let d=a.querySelector(".code-input_autocomplete_testpos").querySelectorAll("span");if(2>d.length)return this.getCaretCoordinates(a,b,c,!1);e=d[1]}else{let d=a.querySelector(".code-input_autocomplete_testpos"),f=document.createElement("span");for(f.textContent=b.value.substring(0,c),e=document.createElement("span"),e.textContent=".";d.firstChild;)d.removeChild(d.firstChild);d.appendChild(f),d.appendChild(e)}return{top:e.offsetTop-b.scrollTop,left:e.offsetLeft-b.scrollLeft}}updatePopupCallback=function(){}};
1
+ codeInput.plugins.Autocomplete=class extends codeInput.Plugin{constructor(a){super([]),this.updatePopupCallback=a}updatePopup(a,b){let c=a.textareaElement,d=this.getCaretCoordinates(a,c,c.selectionEnd,b),e=a.querySelector(".code-input_autocomplete_popup");e.style.top=d.top+"px",e.style.left=d.left+"px",b||this.updatePopupCallback(e,c,c.selectionEnd)}afterElementsAdded(a){let b=document.createElement("div");b.classList.add("code-input_autocomplete_popup"),a.appendChild(b);let c=document.createElement("pre");if(c.setAttribute("aria-hidden","true"),a.template.preElementStyled)c.classList.add("code-input_autocomplete_testpos"),a.appendChild(c);else{let b=document.createElement("code");b.classList.add("code-input_autocomplete_testpos"),c.appendChild(b),a.appendChild(c)}let d=a.textareaElement;d.addEventListener("input",()=>{this.updatePopup(a,!1)}),d.addEventListener("click",()=>{this.updatePopup(a,!1)})}getCaretCoordinates(a,b,c,d){let e;if(d){let d=a.querySelector(".code-input_autocomplete_testpos").querySelectorAll("span");if(2>d.length)return this.getCaretCoordinates(a,b,c,!1);e=d[1]}else{let d=a.querySelector(".code-input_autocomplete_testpos"),f=document.createElement("span");for(f.textContent=b.value.substring(0,c),e=document.createElement("span"),e.textContent=".";d.firstChild;)d.removeChild(d.firstChild);d.appendChild(f),d.appendChild(e)}return{top:e.offsetTop-b.scrollTop,left:e.offsetLeft-b.scrollLeft}}updatePopupCallback=function(){}};
@@ -13,16 +13,16 @@ codeInput.plugins.Autodetect = class extends codeInput.Plugin {
13
13
  resultElement.className = ""; // CODE
14
14
  resultElement.parentElement.className = ""; // PRE
15
15
  }
16
- /* Get new language class and set `lang` attribute */
16
+ /* Get new language class and set `language` attribute */
17
17
  afterHighlight(codeInput) {
18
- let resultElement = codeInput.codeElement;
19
- let langClass = resultElement.className || resultElement.parentElement.className;
18
+ let langClass = codeInput.codeElement.className || codeInput.preElement.className;
20
19
  let lang = langClass.match(/lang(\w|-)*/i)[0]; // Get word starting with lang...; Get outer bracket
21
20
  lang = lang.split("-")[1];
22
21
  if(lang == "undefined") {
22
+ codeInput.removeAttribute("language");
23
23
  codeInput.removeAttribute("lang");
24
24
  } else {
25
- codeInput.setAttribute("lang", lang);
25
+ codeInput.setAttribute("language", lang);
26
26
  }
27
27
  }
28
28
  }
@@ -1 +1 @@
1
- codeInput.plugins.Autodetect=class extends codeInput.Plugin{constructor(){super([])}beforeHighlight(a){let b=a.codeElement;b.className="",b.parentElement.className=""}afterHighlight(a){let b=a.codeElement,c=b.className||b.parentElement.className,d=c.match(/lang(\w|-)*/i)[0];d=d.split("-")[1],"undefined"==d?a.removeAttribute("lang"):a.setAttribute("lang",d)}};
1
+ codeInput.plugins.Autodetect=class extends codeInput.Plugin{constructor(){super([])}beforeHighlight(a){let b=a.codeElement;b.className="",b.parentElement.className=""}afterHighlight(a){let b=a.codeElement.className||a.preElement.className,c=b.match(/lang(\w|-)*/i)[0];c=c.split("-")[1],"undefined"==c?(a.removeAttribute("language"),a.removeAttribute("lang")):a.setAttribute("language",c)}};
@@ -0,0 +1,145 @@
1
+ /* Find functionality matches */
2
+ .code-input_find-and-replace_find-match {
3
+ color: inherit;
4
+ text-shadow: none!important;
5
+ background-color: #ffff00!important;
6
+ }
7
+ .code-input_find-and-replace_find-match-focused, .code-input_find-and-replace_find-match-focused * {
8
+ background-color: #ff8800!important;
9
+ color: black!important;
10
+ }
11
+ .code-input_find-and-replace_start-newline::before {
12
+ content: "โคถ";
13
+ }
14
+
15
+ /* Find-and-replace dialog */
16
+
17
+ @keyframes code-input_find-and-replace_roll-in {
18
+ 0% {opacity: 0; transform: translateY(-34px);}
19
+ 100% {opacity: 1; transform: translateY(0px);}
20
+ }
21
+
22
+ @keyframes code-input_find-and-replace_roll-out {
23
+ 0% {opacity: 1;top: 0;}
24
+ 100% {opacity: 0;top: -34px;}
25
+ }
26
+
27
+ .code-input_find-and-replace_dialog {
28
+ position: absolute;
29
+ top: 0;
30
+ right: 14px;
31
+ padding: 6px;
32
+ padding-top: 8px;
33
+ border: solid 1px #00000044;
34
+ background-color: white;
35
+ border-radius: 6px;
36
+ box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
37
+ }
38
+
39
+ .code-input_find-and-replace_dialog:not(.code-input_find-and-replace_hidden-dialog) {
40
+ animation: code-input_find-and-replace_roll-in .2s;
41
+ opacity: 1;
42
+ pointer-events: all;
43
+ }
44
+
45
+ .code-input_find-and-replace_dialog.code-input_find-and-replace_hidden-dialog {
46
+ animation: code-input_find-and-replace_roll-out .2s;
47
+ opacity: 0;
48
+ pointer-events: none;
49
+ }
50
+
51
+ .code-input_find-and-replace_dialog input::placeholder {
52
+ font-size: 80%;
53
+ }
54
+
55
+ .code-input_find-and-replace_dialog input {
56
+ position: relative;
57
+ width: 240px; height: 32px; top: -3px;
58
+ font-size: large;
59
+ color: #000000aa;
60
+ border: 0;
61
+ }
62
+
63
+ .code-input_find-and-replace_dialog input:hover {
64
+ outline: none;
65
+ }
66
+
67
+ .code-input_find-and-replace_dialog input.code-input_find-and-replace_error {
68
+ color: #ff0000aa;
69
+ }
70
+
71
+ .code-input_find-and-replace_dialog button, .code-input_find-and-replace_dialog input[type="checkbox"] {
72
+ display: inline-block;
73
+ line-height: 24px;
74
+ font-size: 22px;
75
+ cursor: pointer;
76
+ appearance: none;
77
+ width: min-content;
78
+
79
+ margin: 5px;
80
+ padding: 5px;
81
+ border: 0;
82
+ background-color: #dddddd;
83
+
84
+ text-align: center;
85
+ color: black;
86
+ vertical-align: top;
87
+ }
88
+
89
+ .code-input_find-and-replace_dialog input[type="checkbox"].code-input_find-and-replace_case-sensitive-checkbox::before {
90
+ content: "Aa";
91
+ }
92
+ .code-input_find-and-replace_dialog input[type="checkbox"].code-input_find-and-replace_reg-exp-checkbox::before {
93
+ content: ".*";
94
+ }
95
+
96
+ .code-input_find-and-replace_dialog button:hover, .code-input_find-and-replace_dialog input[type="checkbox"]:hover {
97
+ background-color: #bbbbbb;
98
+ }
99
+
100
+ .code-input_find-and-replace_dialog input[type="checkbox"]:checked {
101
+ background-color: #222222;
102
+ color: white;
103
+ }
104
+
105
+ .code-input_find-and-replace_match-description {
106
+ display: block; /* So not on same line as other */
107
+ color: #444444;
108
+ }
109
+
110
+ .code-input_find-and-replace_dialog details summary, .code-input_find-and-replace_dialog button {
111
+ cursor: pointer;
112
+ }
113
+
114
+
115
+ .code-input_find-and-replace_dialog button.code-input_find-and-replace_button-hidden {
116
+ opacity: 0;
117
+ pointer-events: none;
118
+ }
119
+
120
+ /* Cancel icon */
121
+ .code-input_find-and-replace_dialog span {
122
+ display: block;
123
+ float: right;
124
+ margin: 5px;
125
+ padding: 5px;
126
+
127
+ width: 24px;
128
+ line-height: 24px;
129
+ font-family: system-ui;
130
+ font-size: 22px;
131
+ font-weight: 500;
132
+ text-align: center;
133
+ border-radius: 50%;
134
+ color: black;
135
+ opacity: 0.6;
136
+ }
137
+
138
+ .code-input_find-and-replace_dialog span:before {
139
+ content: "\00d7";
140
+ }
141
+
142
+ .code-input_find-and-replace_dialog span:hover {
143
+ opacity: .8;
144
+ background-color: #00000018;
145
+ }