@webcoder49/code-input 2.1.0 → 2.5.0

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.
Files changed (44) hide show
  1. package/CONTRIBUTING.md +11 -1
  2. package/README.md +26 -11
  3. package/code-input.css +126 -29
  4. package/code-input.d.ts +153 -11
  5. package/code-input.js +218 -193
  6. package/code-input.min.css +1 -1
  7. package/code-input.min.js +1 -1
  8. package/package.json +1 -1
  9. package/plugins/README.md +28 -6
  10. package/plugins/auto-close-brackets.js +61 -0
  11. package/plugins/auto-close-brackets.min.js +1 -0
  12. package/plugins/autocomplete.js +21 -12
  13. package/plugins/autocomplete.min.js +1 -1
  14. package/plugins/autodetect.js +4 -4
  15. package/plugins/autodetect.min.js +1 -1
  16. package/plugins/find-and-replace.css +145 -0
  17. package/plugins/find-and-replace.js +746 -0
  18. package/plugins/find-and-replace.min.css +1 -0
  19. package/plugins/find-and-replace.min.js +1 -0
  20. package/plugins/go-to-line.css +77 -0
  21. package/plugins/go-to-line.js +175 -0
  22. package/plugins/go-to-line.min.css +1 -0
  23. package/plugins/go-to-line.min.js +1 -0
  24. package/plugins/indent.js +166 -15
  25. package/plugins/indent.min.js +1 -1
  26. package/plugins/prism-line-numbers.css +10 -9
  27. package/plugins/prism-line-numbers.min.css +1 -1
  28. package/plugins/select-token-callbacks.js +289 -0
  29. package/plugins/select-token-callbacks.min.js +1 -0
  30. package/plugins/special-chars.css +1 -5
  31. package/plugins/special-chars.js +65 -61
  32. package/plugins/special-chars.min.css +2 -2
  33. package/plugins/special-chars.min.js +1 -1
  34. package/plugins/test.js +1 -2
  35. package/plugins/test.min.js +1 -1
  36. package/tests/hljs.html +55 -0
  37. package/tests/i18n.html +197 -0
  38. package/tests/prism-match-braces-compatibility.js +215 -0
  39. package/tests/prism-match-braces-compatibility.min.js +1 -0
  40. package/tests/prism.html +54 -0
  41. package/tests/tester.js +593 -0
  42. package/tests/tester.min.js +21 -0
  43. package/plugins/debounce-update.js +0 -40
  44. package/plugins/debounce-update.min.js +0 -1
@@ -0,0 +1 @@
1
+ .code-input_find-and-replace_find-match{color:inherit;text-shadow:none!important;background-color:#ff0!important}.code-input_find-and-replace_find-match-focused,.code-input_find-and-replace_find-match-focused *{background-color:#f80!important;color:#000!important}.code-input_find-and-replace_start-newline::before{content:"⤶"}@keyframes code-input_find-and-replace_roll-in{0%{opacity:0;transform:translateY(-34px)}100%{opacity:1;transform:translateY(0)}}@keyframes code-input_find-and-replace_roll-out{0%{opacity:1;top:0}100%{opacity:0;top:-34px}}.code-input_find-and-replace_dialog{position:absolute;top:0;right:14px;padding:6px;padding-top:8px;border:solid 1px #00000044;background-color:#fff;border-radius:6px;box-shadow:0 .2em 1em .2em rgba(0,0,0,.16)}.code-input_find-and-replace_dialog:not(.code-input_find-and-replace_hidden-dialog){animation:code-input_find-and-replace_roll-in .2s;opacity:1;pointer-events:all}.code-input_find-and-replace_dialog.code-input_find-and-replace_hidden-dialog{animation:code-input_find-and-replace_roll-out .2s;opacity:0;pointer-events:none}.code-input_find-and-replace_dialog input::placeholder{font-size:80%}.code-input_find-and-replace_dialog input{position:relative;width:240px;height:32px;top:-3px;font-size:large;color:#000000aa;border:0}.code-input_find-and-replace_dialog input:hover{outline:0}.code-input_find-and-replace_dialog input.code-input_find-and-replace_error{color:#ff0000aa}.code-input_find-and-replace_dialog button,.code-input_find-and-replace_dialog input[type=checkbox]{display:inline-block;line-height:24px;font-size:22px;cursor:pointer;appearance:none;width:min-content;margin:5px;padding:5px;border:0;background-color:#ddd;text-align:center;color:#000;vertical-align:top}.code-input_find-and-replace_dialog input[type=checkbox].code-input_find-and-replace_case-sensitive-checkbox::before{content:"Aa"}.code-input_find-and-replace_dialog input[type=checkbox].code-input_find-and-replace_reg-exp-checkbox::before{content:".*"}.code-input_find-and-replace_dialog button:hover,.code-input_find-and-replace_dialog input[type=checkbox]:hover{background-color:#bbb}.code-input_find-and-replace_dialog input[type=checkbox]:checked{background-color:#222;color:#fff}.code-input_find-and-replace_match-description{display:block;color:#444}.code-input_find-and-replace_dialog button,.code-input_find-and-replace_dialog details summary{cursor:pointer}.code-input_find-and-replace_dialog button.code-input_find-and-replace_button-hidden{opacity:0;pointer-events:none}.code-input_find-and-replace_dialog span{display:block;float:right;margin:5px;padding:5px;width:24px;line-height:24px;font-family:system-ui;font-size:22px;font-weight:500;text-align:center;border-radius:50%;color:#000;opacity:.6}.code-input_find-and-replace_dialog span:before{content:"\00d7"}.code-input_find-and-replace_dialog span:hover{opacity:.8;background-color:#00000018}
@@ -0,0 +1 @@
1
+ codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;findMatchesOnValueChange=!0;instructions={start:"Search for matches in your code.",none:"No matches",oneFound:"1 match found.",matchIndex:(a,b)=>`${a} of ${b} matches.`,error:a=>`Error: ${a}`,infiniteLoopError:"Causes an infinite loop",closeDialog:"Close Dialog and Return to Editor",findPlaceholder:"Find",findCaseSensitive:"Match Case Sensitive",findRegExp:"Use JavaScript Regular Expression",replaceTitle:"Replace",replacePlaceholder:"Replace with",findNext:"Find Next Occurrence",findPrevious:"Find Previous Occurrence",replaceActionShort:"Replace",replaceAction:"Replace This Occurrence",replaceAllActionShort:"Replace All",replaceAllAction:"Replace All Occurrences"};constructor(a=!0,b=!0,c={}){super([]),this.useCtrlF=a,this.useCtrlH=b,this.addTranslations(this.instructions,c)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?this.instructions.start:0>=a.findMatchState.numMatches?this.instructions.none:1==a.findMatchState.numMatches?this.instructions.oneFound:this.instructions.matchIndex(a.findMatchState.focusedMatchID+1,a.findMatchState.numMatches)}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent=this.instructions.error(c[c.length-1]))}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),a.replaceInput.focus(),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),this.findMatchesOnValueChange=!1,a.findInput.focus(),a.findInput.selectionStart=0,a.findInput.selectionEnd=a.findInput.value.length,document.execCommand("insertText",!1,a.findInput.value),this.findMatchesOnValueChange=!0,a.textarea.focus(),a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){let c;if(null==a.pluginData.findAndReplace||null==a.pluginData.findAndReplace.dialog){const d=a.textareaElement;c=document.createElement("div");const e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code");h.setAttribute("aria-live","assertive");const i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");if(q.setAttribute("tabindex",0),q.setAttribute("title",this.instructions.closeDialog),l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),c.appendChild(l),c.appendChild(e),c.appendChild(g),c.appendChild(f),c.appendChild(h),j.appendChild(k),j.appendChild(i),c.appendChild(j),c.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder=this.instructions.findPlaceholder,f.setAttribute("type","checkbox"),f.title=this.instructions.findCaseSensitive,f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title=this.instructions.findRegExp,g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText=this.instructions.replaceTitle,i.spellcheck=!1,i.placeholder=this.instructions.replacePlaceholder,m.innerText="\u2193",m.title=this.instructions.findNext,n.innerText="\u2191",n.title=this.instructions.findPrevious,o.className="code-input_find-and-replace_button-hidden",o.innerText=this.instructions.replaceActionShort,o.title=this.instructions.replaceAction,o.addEventListener("focus",()=>{j.setAttribute("open",!0)}),p.className="code-input_find-and-replace_button-hidden",p.innerText=this.instructions.replaceAllActionShort,p.title=this.instructions.replaceAllAction,p.addEventListener("focus",()=>{j.setAttribute("open",!0)}),m.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.nextMatch(),this.updateMatchDescription(c)}),n.addEventListener("click",()=>{event.preventDefault(),c.findMatchState.previousMatch(),this.updateMatchDescription(c)}),o.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceOnce(i.value),c.focus()}),p.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),c.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),c.codeInput=a,c.textarea=d,c.findInput=e,c.findCaseSensitiveCheckbox=f,c.findRegExpCheckbox=g,c.matchDescription=h,c.replaceInput=i,c.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("input",()=>{c.classList.contains("code-input_find-and-replace_hidden-dialog")?this.showPrompt(c.codeInput,!0):!c.replaceDropdown.hasAttribute("open")&&c.replaceDropdown.setAttribute("open",!0)}),c.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(c,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(c,a,b)}),e.addEventListener("input",()=>{this.findMatchesOnValueChange&&this.updateFindMatches(c),c.classList.contains("code-input_find-and-replace_hidden-dialog")&&this.showPrompt(c.codeInput,!1)}),f.addEventListener("click",()=>{this.updateFindMatches(c)}),g.addEventListener("click",()=>{this.updateFindMatches(c)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(c,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(c,a,b)}),q.addEventListener("keypress",b=>{("Space"==b.key||"Enter"==b.key)&&this.cancelPrompt(c,a,b)}),a.dialogContainerElement.appendChild(c),a.pluginData.findAndReplace={dialog:c},e.focus(),b&&j.setAttribute("open",!0),c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}}else c=a.pluginData.findAndReplace.dialog,c.classList.remove("code-input_find-and-replace_hidden-dialog"),c.removeAttribute("inert"),c.setAttribute("tabindex",0),c.removeAttribute("aria-hidden"),c.findInput.focus(),b?c.replaceDropdown.setAttribute("open",!0):c.replaceDropdown.removeAttribute("open");if(c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}this.updateFindMatches(c)}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError(this.instructions.infiniteLoopError);let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0)}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
@@ -0,0 +1,77 @@
1
+ @keyframes code-input_go-to-line_roll-in {
2
+ 0% {opacity: 0; transform: translateY(-34px);}
3
+ 100% {opacity: 1; transform: translateY(0px);}
4
+ }
5
+
6
+ @keyframes code-input_go-to-line_roll-out {
7
+ 0% {opacity: 1; transform: translateY(0px);}
8
+ 100% {opacity: 0; transform: translateY(-34px);}
9
+ }
10
+
11
+ .code-input_go-to-line_dialog {
12
+ position: absolute;
13
+ top: 0; right: 14px;
14
+ height: 28px;
15
+ padding: 6px;
16
+ padding-top: 8px;
17
+ border: solid 1px #00000044;
18
+ background-color: white;
19
+ border-radius: 6px;
20
+ box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
21
+ }
22
+
23
+ .code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog) {
24
+ animation: code-input_go-to-line_roll-in .2s;
25
+ opacity: 1;
26
+ pointer-events: all;
27
+ }
28
+
29
+ .code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog {
30
+ animation: code-input_go-to-line_roll-out .2s;
31
+ opacity: 0;
32
+ pointer-events: none;
33
+ }
34
+
35
+ .code-input_go-to-line_dialog input::placeholder {
36
+ font-size: 80%;
37
+ }
38
+
39
+ .code-input_go-to-line_dialog input {
40
+ position: relative;
41
+ width: 240px; height: 32px; top: -3px;
42
+ font-size: large;
43
+ color: #000000aa;
44
+ border: 0;
45
+ }
46
+
47
+ .code-input_go-to-line_dialog input.code-input_go-to-line_error {
48
+ color: #ff0000aa;
49
+ }
50
+
51
+ .code-input_go-to-line_dialog input:focus {
52
+ outline: none;
53
+ }
54
+
55
+ /* Cancel icon */
56
+ .code-input_go-to-line_dialog span {
57
+ display: inline-block;
58
+ width: 24px;
59
+ line-height: 24px;
60
+ font-family: system-ui;
61
+ font-size: 22px;
62
+ font-weight: 500;
63
+ text-align: center;
64
+ border-radius: 50%;
65
+ color: black;
66
+ opacity: 0.6;
67
+ vertical-align: top;
68
+ }
69
+
70
+ .code-input_go-to-line_dialog span:before {
71
+ content: "\00d7";
72
+ }
73
+
74
+ .code-input_go-to-line_dialog span:hover {
75
+ opacity: .8;
76
+ background-color: #00000018;
77
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Add basic Go-To-Line (Ctrl+G by default) functionality to the code editor.
3
+ * Files: go-to-line.js / go-to-line.css
4
+ */
5
+ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
6
+ useCtrlG = false;
7
+
8
+ instructions = {
9
+ closeDialog: "Close Dialog and Return to Editor",
10
+ input: "Line:Column / Line no. then Enter",
11
+ };
12
+
13
+ /**
14
+ * Create a go-to-line command plugin to pass into a template
15
+ * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
16
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
17
+ */
18
+ constructor(useCtrlG = true, instructionTranslations = {}) {
19
+ super([]); // No observed attributes
20
+ this.useCtrlG = useCtrlG;
21
+ this.addTranslations(this.instructions, instructionTranslations);
22
+ }
23
+
24
+ /* Add keystroke events */
25
+ afterElementsAdded(codeInput) {
26
+ const textarea = codeInput.textareaElement;
27
+ if(this.useCtrlG) {
28
+ textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
29
+ }
30
+ }
31
+
32
+ /* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */
33
+ checkPrompt(dialog, event) {
34
+ // Line number(:column number)
35
+ const lines = dialog.textarea.value.split('\n');
36
+ const maxLineNo = lines.length;
37
+ const lineNo = Number(dialog.input.value.split(':')[0]);
38
+ let columnNo = 0; // Means go to start of indented line
39
+ let maxColumnNo = 1;
40
+ const querySplitByColons = dialog.input.value.split(':');
41
+ if(querySplitByColons.length > 2) return dialog.input.classList.add('code-input_go-to-line_error');
42
+
43
+ if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
44
+
45
+ if (dialog.input.value) {
46
+ if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || lineNo > maxLineNo) {
47
+ return dialog.input.classList.add('code-input_go-to-line_error');
48
+ } else {
49
+ // Check if line:column
50
+ if(querySplitByColons.length >= 2) {
51
+ columnNo = Number(querySplitByColons[1]);
52
+ maxColumnNo = lines[lineNo-1].length;
53
+ }
54
+ if(columnNo < 0 || columnNo > maxColumnNo) {
55
+ return dialog.input.classList.add('code-input_go-to-line_error');
56
+ } else {
57
+ dialog.input.classList.remove('code-input_go-to-line_error');
58
+ }
59
+ }
60
+ }
61
+
62
+ if (event.key == 'Enter') {
63
+ this.goTo(dialog.textarea, lineNo, columnNo);
64
+ this.cancelPrompt(dialog, event);
65
+ }
66
+ }
67
+
68
+ /* Called with a dialog box keyup event to close and clear the dialog box */
69
+ cancelPrompt(dialog, event) {
70
+ event.preventDefault();
71
+ dialog.codeInput.handleEventsFromTextarea = false;
72
+ dialog.textarea.focus();
73
+ dialog.codeInput.handleEventsFromTextarea = true;
74
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
75
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
76
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
77
+
78
+ // Remove dialog after animation
79
+ dialog.classList.add('code-input_go-to-line_hidden-dialog');
80
+ dialog.input.value = "";
81
+ }
82
+
83
+ /**
84
+ * Show a search-like dialog prompting line number.
85
+ * @param {codeInput.CodeInput} codeInput the `<code-input>` element.
86
+ */
87
+ showPrompt(codeInput) {
88
+ if(codeInput.pluginData.goToLine == undefined || codeInput.pluginData.goToLine.dialog == undefined) {
89
+ const textarea = codeInput.textareaElement;
90
+
91
+ const dialog = document.createElement('div');
92
+ const input = document.createElement('input');
93
+ const cancel = document.createElement('span');
94
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
95
+ cancel.setAttribute("title", this.instructions.closeDialog);
96
+
97
+ dialog.appendChild(input);
98
+ dialog.appendChild(cancel);
99
+
100
+ dialog.className = 'code-input_go-to-line_dialog';
101
+ input.spellcheck = false;
102
+ input.placeholder = this.instructions.input;
103
+ dialog.codeInput = codeInput;
104
+ dialog.textarea = textarea;
105
+ dialog.input = input;
106
+
107
+ input.addEventListener('keypress', (event) => {
108
+ /* Stop enter from submitting form */
109
+ if (event.key == 'Enter') event.preventDefault();
110
+ });
111
+
112
+ input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
113
+ cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
114
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, event); });
115
+
116
+ codeInput.dialogContainerElement.appendChild(dialog);
117
+ codeInput.pluginData.goToLine = {dialog: dialog};
118
+ input.focus();
119
+ } else {
120
+ codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
121
+ codeInput.pluginData.goToLine.dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
122
+ codeInput.pluginData.goToLine.dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
123
+ codeInput.pluginData.goToLine.dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
124
+ codeInput.pluginData.goToLine.dialog.input.focus();
125
+ }
126
+ }
127
+
128
+ /* Set the cursor on the first non-space char of textarea's nth line, or to the columnNo-numbered character in the line if it's not 0; and scroll it into view */
129
+ goTo(textarea, lineNo, columnNo = 0) {
130
+ let fontSize;
131
+ let lineHeight;
132
+ let scrollAmount;
133
+ let topPadding;
134
+ let cursorPos = -1;
135
+ let lines = textarea.value.split('\n');
136
+
137
+ if (lineNo > 0 && lineNo <= lines.length) {
138
+ if (textarea.computedStyleMap) {
139
+ fontSize = textarea.computedStyleMap().get('font-size').value;
140
+ lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
141
+ } else {
142
+ fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
143
+ lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
144
+ }
145
+
146
+ // scroll amount and initial top padding (3 lines above, if possible)
147
+ scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
148
+ topPadding = (lineHeight - fontSize) / 2;
149
+
150
+ if (lineNo > 1) {
151
+ // cursor positon just after n - 1 full lines
152
+ cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
153
+ }
154
+
155
+ // scan first non-space char in nth line
156
+ if (columnNo == 0) {
157
+ do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
158
+ } else {
159
+ cursorPos += 1 + columnNo - 1;
160
+ }
161
+
162
+ textarea.scrollTop = scrollAmount - topPadding;
163
+ textarea.setSelectionRange(cursorPos, cursorPos);
164
+ textarea.click();
165
+ }
166
+ }
167
+
168
+ /* Event handler for keydown event that makes Ctrl+G open go to line dialog */
169
+ checkCtrlG(codeInput, event) {
170
+ if (event.ctrlKey && event.key == 'g') {
171
+ event.preventDefault();
172
+ this.showPrompt(codeInput);
173
+ }
174
+ }
175
+ }
@@ -0,0 +1 @@
1
+ @keyframes code-input_go-to-line_roll-in{0%{opacity:0;transform:translateY(-34px)}100%{opacity:1;transform:translateY(0)}}@keyframes code-input_go-to-line_roll-out{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-34px)}}.code-input_go-to-line_dialog{position:absolute;top:0;right:14px;height:28px;padding:6px;padding-top:8px;border:solid 1px #00000044;background-color:#fff;border-radius:6px;box-shadow:0 .2em 1em .2em rgba(0,0,0,.16)}.code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog){animation:code-input_go-to-line_roll-in .2s;opacity:1;pointer-events:all}.code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog{animation:code-input_go-to-line_roll-out .2s;opacity:0;pointer-events:none}.code-input_go-to-line_dialog input::placeholder{font-size:80%}.code-input_go-to-line_dialog input{position:relative;width:240px;height:32px;top:-3px;font-size:large;color:#000000aa;border:0}.code-input_go-to-line_dialog input.code-input_go-to-line_error{color:#ff0000aa}.code-input_go-to-line_dialog input:focus{outline:0}.code-input_go-to-line_dialog span{display:inline-block;width:24px;line-height:24px;font-family:system-ui;font-size:22px;font-weight:500;text-align:center;border-radius:50%;color:#000;opacity:.6;vertical-align:top}.code-input_go-to-line_dialog span:before{content:"\00d7"}.code-input_go-to-line_dialog span:hover{opacity:.8;background-color:#00000018}
@@ -0,0 +1 @@
1
+ codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;instructions={closeDialog:"Close Dialog and Return to Editor",input:"Line:Column / Line no. then Enter"};constructor(a=!0,b={}){super([]),this.useCtrlG=a,this.addTranslations(this.instructions,b)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.codeInput.handleEventsFromTextarea=!1,a.textarea.focus(),a.codeInput.handleEventsFromTextarea=!0,a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");e.setAttribute("tabindex",0),e.setAttribute("title",this.instructions.closeDialog),c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder=this.instructions.input,c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),e.addEventListener("keypress",a=>{("Space"==a.key||"Enter"==a.key)&&this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.removeAttribute("inert"),a.pluginData.goToLine.dialog.setAttribute("tabindex",0),a.pluginData.goToLine.dialog.removeAttribute("aria-hidden"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};
package/plugins/indent.js CHANGED
@@ -1,23 +1,34 @@
1
1
  /**
2
- * Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
2
+ * Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it
3
3
  * possible to indent/unindent multiple lines using Tab/Shift+Tab
4
4
  * Files: indent.js
5
5
  */
6
6
  codeInput.plugins.Indent = class extends codeInput.Plugin {
7
7
 
8
- numSpaces;
8
+ bracketPairs = {}; // No bracket-auto-indentation used when {}
9
9
  indentation = "\t";
10
10
  indentationNumChars = 1;
11
+ tabIndentationEnabled = true; // Can be disabled for accessibility reasons to allow keyboard navigation
12
+ escTabToChangeFocus = true;
13
+ escJustPressed = false; // Becomes true when Escape key is pressed and false when another key is pressed
14
+
15
+ instructions = {
16
+ tabForIndentation: "Tab and Shift-Tab currently for indentation. Press Esc to enable keyboard navigation.",
17
+ tabForNavigation: "Tab and Shift-Tab currently for keyboard navigation. Type to return to indentation.",
18
+ };
11
19
 
12
20
  /**
13
21
  * Create an indentation plugin to pass into a template
14
- * @param {Boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
22
+ * @param {boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
15
23
  * @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
24
+ * @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}"}. All brackets must only be one character, and this can be left as null to remove bracket-based indentation behaviour.
25
+ * @param {boolean} escTabToChangeFocus Whether pressing the Escape key before Tab and Shift-Tab should make this keypress focus on a different element (Tab's default behaviour). You should always either enable this or use this plugin's disableTabIndentation and enableTabIndentation methods linked to other keyboard shortcuts, for accessibility.
26
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
16
27
  */
17
- constructor(defaultSpaces=false, numSpaces=4) {
28
+ constructor(defaultSpaces=false, numSpaces=4, bracketPairs={"(": ")", "[": "]", "{": "}"}, escTabToChangeFocus=true, instructionTranslations = {}) {
18
29
  super([]); // No observed attributes
19
30
 
20
- this.numSpaces = numSpaces;
31
+ this.bracketPairs = bracketPairs;
21
32
  if(defaultSpaces) {
22
33
  this.indentation = "";
23
34
  for(let i = 0; i < numSpaces; i++) {
@@ -25,19 +36,81 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
25
36
  }
26
37
  this.indentationNumChars = numSpaces;
27
38
  }
39
+
40
+ this.escTabToChangeFocus = true;
41
+
42
+ this.addTranslations(this.instructions, instructionTranslations);
28
43
  }
29
44
 
30
- /* Add keystroke events */
45
+ /**
46
+ * Make the Tab key
47
+ */
48
+ disableTabIndentation() {
49
+ this.tabIndentationEnabled = false;
50
+ }
51
+
52
+ enableTabIndentation() {
53
+ this.tabIndentationEnabled = true;
54
+ }
55
+
56
+ /* Add keystroke events, and get the width of the indentation in pixels. */
31
57
  afterElementsAdded(codeInput) {
58
+
32
59
  let textarea = codeInput.textareaElement;
60
+ textarea.addEventListener('focus', (event) => { if(this.escTabToChangeFocus) codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, true); })
33
61
  textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
62
+ textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
63
+
64
+ // Get the width of the indentation in pixels
65
+ let testIndentationWidthPre = document.createElement("pre");
66
+ testIndentationWidthPre.setAttribute("aria-hidden", "true"); // Hide for screen readers
67
+ let testIndentationWidthSpan = document.createElement("span");
68
+ if(codeInput.template.preElementStyled) {
69
+ testIndentationWidthPre.appendChild(testIndentationWidthSpan);
70
+ testIndentationWidthPre.classList.add("code-input_autocomplete_test-indentation-width");
71
+ codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
72
+ } else {
73
+ let testIndentationWidthCode = document.createElement("code");
74
+ testIndentationWidthCode.appendChild(testIndentationWidthSpan);
75
+ testIndentationWidthCode.classList.add("code-input_autocomplete_test-indentation-width");
76
+ testIndentationWidthPre.appendChild(testIndentationWidthCode);
77
+ codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
78
+ }
79
+
80
+ testIndentationWidthSpan.innerHTML = codeInput.escapeHtml(this.indentation);
81
+ let indentationWidthPx = testIndentationWidthSpan.offsetWidth;
82
+ codeInput.removeChild(testIndentationWidthPre);
83
+
84
+ codeInput.pluginData.indent = {indentationWidthPx: indentationWidthPx};
34
85
  }
35
86
 
36
- /* Event handlers */
87
+ /* Deal with the Tab key causing indentation, and Tab+Selection indenting / Shift+Tab+Selection unindenting lines, and the mechanism through which Tab can be used to switch focus instead (accessibility). */
37
88
  checkTab(codeInput, event) {
38
- if(event.key != "Tab") {
89
+ if(!this.tabIndentationEnabled) return;
90
+ if(this.escTabToChangeFocus) {
91
+ // Accessibility - allow Tab for keyboard navigation when Esc pressed right before it.
92
+ if(event.key == "Escape") {
93
+ this.escJustPressed = true;
94
+ codeInput.setKeyboardNavInstructions(this.instructions.tabForNavigation, false);
95
+ return;
96
+ } else if(event.key != "Tab") {
97
+ if(event.key == "Shift") {
98
+ return; // Shift+Tab after Esc should still be keyboard navigation
99
+ }
100
+ codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, false);
101
+ this.escJustPressed = false;
102
+ return;
103
+ }
104
+
105
+ if(!this.enableTabIndentation || this.escJustPressed) {
106
+ codeInput.setKeyboardNavInstructions("", false);
107
+ this.escJustPressed = false;
108
+ return;
109
+ }
110
+ } else if(event.key != "Tab") {
39
111
  return;
40
112
  }
113
+
41
114
  let inputElement = codeInput.textareaElement;
42
115
  event.preventDefault(); // stop normal
43
116
 
@@ -91,18 +164,39 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
91
164
  // move cursor
92
165
  inputElement.selectionStart = selectionStartI;
93
166
  inputElement.selectionEnd = selectionEndI;
167
+
168
+ // move scroll position to follow code
169
+ const textDirection = getComputedStyle(codeInput).direction;
170
+ if(textDirection == "rtl") {
171
+ if(event.shiftKey) {
172
+ // Scroll right
173
+ codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
174
+ } else {
175
+ // Scroll left
176
+ codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
177
+ }
178
+ } else {
179
+ if(event.shiftKey) {
180
+ // Scroll left
181
+ codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
182
+ } else {
183
+ // Scroll right
184
+ codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
185
+ }
186
+ }
94
187
  }
95
188
 
96
- codeInput.update(inputElement.value);
189
+ codeInput.value = inputElement.value;
97
190
  }
98
191
 
192
+ /* Deal with new lines retaining indentation */
99
193
  checkEnter(codeInput, event) {
100
194
  if(event.key != "Enter") {
101
195
  return;
102
196
  }
103
197
  event.preventDefault(); // Stop normal \n only
104
198
 
105
- let inputElement = codeInput.querySelector("textarea");
199
+ let inputElement = codeInput.textareaElement;
106
200
  let lines = inputElement.value.split("\n");
107
201
  let letterI = 0;
108
202
  let currentLineI = lines.length - 1;
@@ -135,6 +229,39 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
135
229
  lines[currentLineI] = lines[currentLineI].substring(0, cursorPosInLine);
136
230
  }
137
231
 
232
+ let bracketThreeLinesTriggered = false;
233
+ let furtherIndentation = "";
234
+ if(this.bracketPairs != null) {
235
+ for(let openingBracket in this.bracketPairs) {
236
+ if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
237
+ let closingBracket = this.bracketPairs[openingBracket];
238
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
239
+ // Create new line and then put textAfterCursor on yet another line:
240
+ // {
241
+ // |CARET|
242
+ // }
243
+ bracketThreeLinesTriggered = true;
244
+ for (let i = 0; i < numberIndents+1; i++) {
245
+ furtherIndentation += this.indentation;
246
+ }
247
+ } else {
248
+ // Just create new line:
249
+ // {
250
+ // |CARET|
251
+ numberIndents++;
252
+ }
253
+ break;
254
+ } else {
255
+ // Check whether brackets cause unindent
256
+ let closingBracket = this.bracketPairs[openingBracket];
257
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
258
+ numberIndents--;
259
+ break;
260
+ }
261
+ }
262
+ }
263
+ }
264
+
138
265
  // insert our indents and any text from the previous line that might have been after the line break
139
266
  for (let i = 0; i < numberIndents; i++) {
140
267
  newLine += this.indentation;
@@ -143,6 +270,10 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
143
270
  // save the current cursor position
144
271
  let selectionStartI = inputElement.selectionStart;
145
272
 
273
+ if(bracketThreeLinesTriggered) {
274
+ document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line
275
+ numberIndents += 1; // Reflects the new indent
276
+ }
146
277
  document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation
147
278
 
148
279
  // move cursor to new position
@@ -153,17 +284,18 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
153
284
  // Scroll down to cursor if necessary
154
285
  let paddingTop = Number(getComputedStyle(inputElement).paddingTop.replace("px", ""));
155
286
  let lineHeight = Number(getComputedStyle(inputElement).lineHeight.replace("px", ""));
156
- let inputHeight = Number(getComputedStyle(inputElement).height.replace("px", ""));
287
+ let inputHeight = Number(getComputedStyle(codeInput).height.replace("px", ""));
157
288
  if(currentLineI*lineHeight + lineHeight*2 + paddingTop >= inputElement.scrollTop + inputHeight) { // Cursor too far down
158
- inputElement.scrollBy(0, Number(getComputedStyle(inputElement).lineHeight.replace("px", "")))
289
+ codeInput.scrollBy(0, Number(getComputedStyle(inputElement).lineHeight.replace("px", "")));
159
290
  }
160
291
 
161
- codeInput.update(inputElement.value);
292
+ codeInput.value = inputElement.value;
162
293
  }
163
294
 
295
+ /* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */
164
296
  checkBackspace(codeInput, event) {
165
297
  if(event.key != "Backspace" || this.indentationNumChars == 1) {
166
- return; // Normal backspace
298
+ return; // Normal backspace when indentation of 1
167
299
  }
168
300
 
169
301
  let inputElement = codeInput.textareaElement;
@@ -175,4 +307,23 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
175
307
  document.execCommand("delete", false, "");
176
308
  }
177
309
  }
178
- }
310
+
311
+ /* Deal with the typing of closing brackets causing a decrease in indentation */
312
+ checkCloseBracket(codeInput, event) {
313
+ if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
314
+ return;
315
+ }
316
+
317
+ for(let openingBracket in this.bracketPairs) {
318
+ let closingBracket = this.bracketPairs[openingBracket];
319
+ if(event.data == closingBracket) {
320
+ // Closing bracket unindents line
321
+ if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
322
+ // Indentation before cursor = delete it
323
+ codeInput.textareaElement.selectionStart -= this.indentationNumChars;
324
+ document.execCommand("delete", false, "");
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
@@ -1 +1 @@
1
- codeInput.plugins.Indent=class extends codeInput.Plugin{numSpaces;indentation="\t";indentationNumChars=1;constructor(a=!1,b=4){if(super([]),this.numSpaces=b,a){this.indentation="";for(let a=0;a<b;a++)this.indentation+=" ";this.indentationNumChars=b}}afterElementsAdded(a){let b=a.textareaElement;b.addEventListener("keydown",b=>{this.checkTab(a,b),this.checkEnter(a,b),this.checkBackspace(a,b)})}checkTab(a,b){var c=Math.max;if("Tab"==b.key){let d=a.textareaElement;if(b.preventDefault(),!b.shiftKey&&d.selectionStart==d.selectionEnd)document.execCommand("insertText",!1,this.indentation);else{let a=d.value.split("\n"),e=0,f=d.selectionStart,g=d.selectionEnd;for(let h=0;h<a.length;h++)(f<=e+a[h].length&&g>=e+1||f==g&&f<=e+a[h].length+1&&g>=e)&&(b.shiftKey?a[h].substring(0,this.indentationNumChars)==this.indentation&&(d.selectionStart=e,d.selectionEnd=e+this.indentationNumChars,document.execCommand("delete",!1,""),f>e&&(f=c(f-this.indentationNumChars,e)),g-=this.indentationNumChars,e-=this.indentationNumChars):(d.selectionStart=e,d.selectionEnd=e,document.execCommand("insertText",!1,this.indentation),f>e&&(f+=this.indentationNumChars),g+=this.indentationNumChars,e+=this.indentationNumChars)),e+=a[h].length+1;d.selectionStart=f,d.selectionEnd=g}a.update(d.value)}}checkEnter(a,b){if("Enter"!=b.key)return;b.preventDefault();let c=a.querySelector("textarea"),d=c.value.split("\n"),e=0,f=d.length-1,g="",h=0;for(let g=0;g<d.length;g++)if(e+=d[g].length+1,c.selectionEnd<=e){f=g;break}let j=d[f].length-(e-c.selectionEnd)+1;for(let c=0;c<j&&d[f].substring(c,c+this.indentationNumChars)==this.indentation;c+=this.indentationNumChars)h++;let k="";j!=d[f].length&&(k=d[f].substring(j),d[f]=d[f].substring(0,j));for(let c=0;c<h;c++)g+=this.indentation;let l=c.selectionStart;document.execCommand("insertText",!1,"\n"+g),c.selectionStart=l+h*this.indentationNumChars+1,c.selectionEnd=c.selectionStart;let m=+getComputedStyle(c).paddingTop.replace("px",""),n=+getComputedStyle(c).lineHeight.replace("px",""),o=+getComputedStyle(c).height.replace("px","");f*n+2*n+m>=c.scrollTop+o&&c.scrollBy(0,+getComputedStyle(c).lineHeight.replace("px","")),a.update(c.value)}checkBackspace(a,b){if("Backspace"==b.key&&1!=this.indentationNumChars){let c=a.textareaElement;c.selectionStart==c.selectionEnd&&a.value.substring(c.selectionStart-this.indentationNumChars,c.selectionStart)==this.indentation&&(c.selectionStart-=this.indentationNumChars,b.preventDefault(),document.execCommand("delete",!1,""))}}};
1
+ codeInput.plugins.Indent=class extends codeInput.Plugin{bracketPairs={};indentation="\t";indentationNumChars=1;tabIndentationEnabled=!0;escTabToChangeFocus=!0;escJustPressed=!1;instructions={tabForIndentation:"Tab and Shift-Tab currently for indentation. Press Esc to enable keyboard navigation.",tabForNavigation:"Tab and Shift-Tab currently for keyboard navigation. Type to return to indentation."};constructor(a=!1,b=4,c={"(":")","[":"]","{":"}"},d=!0,e={}){if(super([]),this.bracketPairs=c,a){this.indentation="";for(let a=0;a<b;a++)this.indentation+=" ";this.indentationNumChars=b}this.escTabToChangeFocus=!0,this.addTranslations(this.instructions,e)}disableTabIndentation(){this.tabIndentationEnabled=!1}enableTabIndentation(){this.tabIndentationEnabled=!0}afterElementsAdded(a){let b=a.textareaElement;b.addEventListener("focus",()=>{this.escTabToChangeFocus&&a.setKeyboardNavInstructions(this.instructions.tabForIndentation,!0)}),b.addEventListener("keydown",b=>{this.checkTab(a,b),this.checkEnter(a,b),this.checkBackspace(a,b)}),b.addEventListener("beforeinput",b=>{this.checkCloseBracket(a,b)});let c=document.createElement("pre");c.setAttribute("aria-hidden","true");let d=document.createElement("span");if(a.template.preElementStyled)c.appendChild(d),c.classList.add("code-input_autocomplete_test-indentation-width"),a.appendChild(c);else{let b=document.createElement("code");b.appendChild(d),b.classList.add("code-input_autocomplete_test-indentation-width"),c.appendChild(b),a.appendChild(c)}d.innerHTML=a.escapeHtml(this.indentation);let e=d.offsetWidth;a.removeChild(c),a.pluginData.indent={indentationWidthPx:e}}checkTab(a,b){var c=Math.max;if(this.tabIndentationEnabled){if(this.escTabToChangeFocus){if("Escape"==b.key)return this.escJustPressed=!0,void a.setKeyboardNavInstructions(this.instructions.tabForNavigation,!1);if("Tab"!=b.key)return"Shift"==b.key?void 0:(a.setKeyboardNavInstructions(this.instructions.tabForIndentation,!1),void(this.escJustPressed=!1));if(!this.enableTabIndentation||this.escJustPressed)return a.setKeyboardNavInstructions("",!1),void(this.escJustPressed=!1)}else if("Tab"!=b.key)return;let d=a.textareaElement;if(b.preventDefault(),!b.shiftKey&&d.selectionStart==d.selectionEnd)document.execCommand("insertText",!1,this.indentation);else{let e=d.value.split("\n"),f=0,g=d.selectionStart,h=d.selectionEnd;for(let a=0;a<e.length;a++)(g<=f+e[a].length&&h>=f+1||g==h&&g<=f+e[a].length+1&&h>=f)&&(b.shiftKey?e[a].substring(0,this.indentationNumChars)==this.indentation&&(d.selectionStart=f,d.selectionEnd=f+this.indentationNumChars,document.execCommand("delete",!1,""),g>f&&(g=c(g-this.indentationNumChars,f)),h-=this.indentationNumChars,f-=this.indentationNumChars):(d.selectionStart=f,d.selectionEnd=f,document.execCommand("insertText",!1,this.indentation),g>f&&(g+=this.indentationNumChars),h+=this.indentationNumChars,f+=this.indentationNumChars)),f+=e[a].length+1;d.selectionStart=g,d.selectionEnd=h;const i=getComputedStyle(a).direction;"rtl"==i?b.shiftKey?a.scrollBy(a.pluginData.indent.indentationWidthPx,0):a.scrollBy(-a.pluginData.indent.indentationWidthPx,0):b.shiftKey?a.scrollBy(-a.pluginData.indent.indentationWidthPx,0):a.scrollBy(a.pluginData.indent.indentationWidthPx,0)}a.value=d.value}}checkEnter(a,b){if("Enter"!=b.key)return;b.preventDefault();let c=a.textareaElement,d=c.value.split("\n"),e=0,f=d.length-1,g="",h=0;for(let g=0;g<d.length;g++)if(e+=d[g].length+1,c.selectionEnd<=e){f=g;break}let j=d[f].length-(e-c.selectionEnd)+1;for(let c=0;c<j&&d[f].substring(c,c+this.indentationNumChars)==this.indentation;c+=this.indentationNumChars)h++;let k="";j!=d[f].length&&(k=d[f].substring(j),d[f]=d[f].substring(0,j));let l=!1,m="";if(null!=this.bracketPairs)for(let a in this.bracketPairs)if(d[f][d[f].length-1]==a){let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){l=!0;for(let a=0;a<h+1;a++)m+=this.indentation}else h++;break}else{let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){h--;break}}for(let c=0;c<h;c++)g+=this.indentation;let n=c.selectionStart;l&&(document.execCommand("insertText",!1,"\n"+m),h+=1),document.execCommand("insertText",!1,"\n"+g),c.selectionStart=n+h*this.indentationNumChars+1,c.selectionEnd=c.selectionStart;let o=+getComputedStyle(c).paddingTop.replace("px",""),p=+getComputedStyle(c).lineHeight.replace("px",""),q=+getComputedStyle(a).height.replace("px","");f*p+2*p+o>=c.scrollTop+q&&a.scrollBy(0,+getComputedStyle(c).lineHeight.replace("px","")),a.value=c.value}checkBackspace(a,b){if("Backspace"==b.key&&1!=this.indentationNumChars){let c=a.textareaElement;c.selectionStart==c.selectionEnd&&a.value.substring(c.selectionStart-this.indentationNumChars,c.selectionStart)==this.indentation&&(c.selectionStart-=this.indentationNumChars,b.preventDefault(),document.execCommand("delete",!1,""))}}checkCloseBracket(a,b){if(a.textareaElement.selectionStart==a.textareaElement.selectionEnd)for(let c in this.bracketPairs){let d=this.bracketPairs[c];b.data==d&&a.value.substring(a.textareaElement.selectionStart-this.indentationNumChars,a.textareaElement.selectionStart)==this.indentation&&(a.textareaElement.selectionStart-=this.indentationNumChars,document.execCommand("delete",!1,""))}}};
@@ -7,14 +7,15 @@
7
7
  /* Update padding to match line-numbers plugin */
8
8
  code-input.line-numbers textarea, code-input.line-numbers.code-input_pre-element-styled pre,
9
9
  .line-numbers code-input textarea, .line-numbers code-input.code-input_pre-element-styled pre {
10
- padding-top: 1em!important;
11
- padding-bottom: 1em!important;
12
- padding-right: 1em!important;
13
- padding-left: 3.8em!important;
10
+ padding-left: max(3.8em, var(--padding, 16px))!important;
14
11
  }
15
12
 
16
- /* Remove unnecessary, interfering padding values */
17
- code-input.line-numbers, code-input.line-numbers.code-input_pre-element-styled code,
18
- .line-numbers code-input, .line-numbers code-input.code-input_pre-element-styled code{
19
- padding: 0;
20
- }
13
+ /* Ensure pre code/textarea just wide enough to give 100% width with line numbers */
14
+ code-input.line-numbers, .line-numbers code-input {
15
+ grid-template-columns: calc(100% - max(0em, calc(3.8em - var(--padding, 16px))));
16
+ }
17
+
18
+ /* Make keyboard navigation still fill width */
19
+ code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions {
20
+ width: calc(100% + max(3.8em, var(--padding, 16px)))!important;
21
+ }
@@ -1 +1 @@
1
- .line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-top:1em!important;padding-bottom:1em!important;padding-right:1em!important;padding-left:3.8em!important}.line-numbers code-input,.line-numbers code-input.code-input_pre-element-styled code,code-input.line-numbers,code-input.line-numbers.code-input_pre-element-styled code{padding:0}
1
+ .line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-left:max(3.8em,var(--padding,16px))!important}.line-numbers code-input,code-input.line-numbers{grid-template-columns:calc(100% - max(0em,calc(3.8em - var(--padding,16px))))}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions{width:calc(100% + max(3.8em,var(--padding,16px)))!important}