@webcoder49/code-input 2.2.1 → 2.5.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/code-input.js CHANGED
@@ -158,14 +158,14 @@ var codeInput = {
158
158
  /**
159
159
  * Constructor to create a custom template instance. Pass this into `codeInput.registerTemplate` to use it.
160
160
  * I would strongly recommend using the built-in simpler template `codeInput.templates.prism` or `codeInput.templates.hljs`.
161
- * @param {Function} highlight - a callback to highlight the code, that takes an HTML `<code>` element inside a `<pre>` element as a parameter
161
+ * @param {(codeElement: HTMLCodeElement, codeInput?: codeInput.CodeInput) => void} highlight - a callback to highlight the code, that takes an HTML `<code>` element inside a `<pre>` element as a parameter
162
162
  * @param {boolean} preElementStyled - is the `<pre>` element CSS-styled as well as the `<code>` element? If true, `<pre>` element's scrolling is synchronised; if false, `<code>` element's scrolling is synchronised.
163
163
  * @param {boolean} isCode - is this for writing code? If true, the code-input's lang HTML attribute can be used, and the `<code>` element will be given the class name 'language-[lang attribute's value]'.
164
164
  * @param {boolean} includeCodeInputInHighlightFunc - Setting this to true passes the `<code-input>` element as a second argument to the highlight function.
165
165
  * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.Plugin`
166
166
  * @returns {codeInput.Template} template object
167
167
  */
168
- constructor(highlight = function () { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = []) {
168
+ constructor(highlight = function (codeElement) { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = []) {
169
169
  this.highlight = highlight;
170
170
  this.preElementStyled = preElementStyled;
171
171
  this.isCode = isCode;
@@ -377,6 +377,17 @@ var codeInput = {
377
377
  });
378
378
  }
379
379
 
380
+ /**
381
+ * Replace the keys in destination with any source
382
+ * @param {Object} destination Where to place the translated strings, already filled with the keys pointing to English strings.
383
+ * @param {Object} source The same keys, or some of them, mapped to translated strings.
384
+ */
385
+ addTranslations(destination, source) {
386
+ for(const key in source) {
387
+ destination[key] = source[key];
388
+ }
389
+ }
390
+
380
391
  /**
381
392
  * Runs before code is highlighted.
382
393
  * @param {codeInput.CodeInput} codeInput - The codeInput element
@@ -482,6 +493,8 @@ var codeInput = {
482
493
  * to syntax-highlight it. */
483
494
 
484
495
  needsHighlight = false; // Just inputted
496
+ handleEventsFromTextarea = true; // Turn to false when unusual internal events are called on the textarea
497
+ originalAriaDescription;
485
498
 
486
499
  /**
487
500
  * Highlight the code as soon as possible
@@ -494,17 +507,6 @@ var codeInput = {
494
507
  * Call an animation frame
495
508
  */
496
509
  animateFrame() {
497
- // Synchronise the size of the pre/code and textarea elements
498
- if(this.template.preElementStyled) {
499
- this.style.backgroundColor = getComputedStyle(this.preElement).backgroundColor;
500
- this.textareaElement.style.height = getComputedStyle(this.preElement).height;
501
- this.textareaElement.style.width = getComputedStyle(this.preElement).width;
502
- } else {
503
- this.style.backgroundColor = getComputedStyle(this.codeElement).backgroundColor;
504
- this.textareaElement.style.height = getComputedStyle(this.codeElement).height;
505
- this.textareaElement.style.width = getComputedStyle(this.codeElement).width;
506
- }
507
-
508
510
  // Synchronise the contents of the pre/code and textarea elements
509
511
  if(this.needsHighlight) {
510
512
  this.update();
@@ -520,11 +522,7 @@ var codeInput = {
520
522
  update() {
521
523
  let resultElement = this.codeElement;
522
524
  let value = this.value;
523
-
524
- // Handle final newlines
525
- if (value[value.length - 1] == "\n" || value.length == 0) {
526
- value += " ";
527
- }
525
+ value += "\n"; // Placeholder for next line
528
526
 
529
527
  // Update code
530
528
  resultElement.innerHTML = this.escapeHtml(value);
@@ -534,9 +532,49 @@ var codeInput = {
534
532
  if (this.template.includeCodeInputInHighlightFunc) this.template.highlight(resultElement, this);
535
533
  else this.template.highlight(resultElement);
536
534
 
535
+ this.syncSize();
536
+
537
+ // If editing here, scroll to the caret by focusing, though this shouldn't count as a focus event
538
+ if(this.textareaElement === document.activeElement) {
539
+ this.handleEventsFromTextarea = false;
540
+ this.textareaElement.blur();
541
+ this.textareaElement.focus();
542
+ this.handleEventsFromTextarea = true;
543
+ }
544
+
537
545
  this.pluginEvt("afterHighlight");
538
546
  }
539
547
 
548
+ /**
549
+ * Set the size of the textarea element to the size of the pre/code element.
550
+ */
551
+ syncSize() {
552
+ // Synchronise the size of the pre/code and textarea elements
553
+ if(this.template.preElementStyled) {
554
+ this.style.backgroundColor = getComputedStyle(this.preElement).backgroundColor;
555
+ this.textareaElement.style.height = getComputedStyle(this.preElement).height;
556
+ this.textareaElement.style.width = getComputedStyle(this.preElement).width;
557
+ } else {
558
+ this.style.backgroundColor = getComputedStyle(this.codeElement).backgroundColor;
559
+ this.textareaElement.style.height = getComputedStyle(this.codeElement).height;
560
+ this.textareaElement.style.width = getComputedStyle(this.codeElement).width;
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Show some instructions to the user only if they are using keyboard navigation - for example, a prompt on how to navigate with the keyboard if Tab is repurposed.
566
+ * @param {string} instructions The instructions to display only if keyboard navigation is being used. If it's blank, no instructions will be shown.
567
+ * @param {boolean} includeAriaDescriptionFirst Whether to include the aria-description of the code-input element before the keyboard navigation instructions for a screenreader. Keep this as true when the textarea is first focused.
568
+ */
569
+ setKeyboardNavInstructions(instructions, includeAriaDescriptionFirst) {
570
+ this.dialogContainerElement.querySelector(".code-input_keyboard-navigation-instructions").innerText = instructions;
571
+ if(includeAriaDescriptionFirst) {
572
+ this.textareaElement.setAttribute("aria-description", this.originalAriaDescription + ". " + instructions);
573
+ } else {
574
+ this.textareaElement.setAttribute("aria-description", instructions);
575
+ }
576
+ }
577
+
540
578
  /**
541
579
  * HTML-escape an arbitrary string.
542
580
  * @param {string} text - The original, unescaped text
@@ -593,7 +631,7 @@ var codeInput = {
593
631
 
594
632
  // First-time attribute sync
595
633
  let lang = this.getAttribute("language") || this.getAttribute("lang");
596
- let placeholder = this.getAttribute("placeholder") || this.getAttribute("lang") || "";
634
+ let placeholder = this.getAttribute("placeholder") || this.getAttribute("language") || this.getAttribute("lang") || "";
597
635
  let value = this.unescapeHtml(this.innerHTML) || this.getAttribute("value") || "";
598
636
  // Value attribute deprecated, but included for compatibility
599
637
 
@@ -607,6 +645,22 @@ var codeInput = {
607
645
  }
608
646
  textarea.innerHTML = this.innerHTML;
609
647
  textarea.setAttribute("spellcheck", "false");
648
+
649
+ // Disable focusing on the code-input element - only allow the textarea to be focusable
650
+ textarea.setAttribute("tabindex", this.getAttribute("tabindex") || 0);
651
+ this.setAttribute("tabindex", -1);
652
+ // Save aria-description so keyboard navigation guidance can be added.
653
+ this.originalAriaDescription = this.getAttribute("aria-description") || "Code input field";
654
+
655
+ // Accessibility - detect when mouse focus to remove focus outline + keyboard navigation guidance that could irritate users.
656
+ this.addEventListener("mousedown", () => {
657
+ this.classList.add("code-input_mouse-focused");
658
+ });
659
+ textarea.addEventListener("blur", () => {
660
+ if(this.handleEventsFromTextarea) {
661
+ this.classList.remove("code-input_mouse-focused");
662
+ }
663
+ });
610
664
 
611
665
  this.innerHTML = ""; // Clear Content
612
666
 
@@ -628,6 +682,8 @@ var codeInput = {
628
682
  let code = document.createElement("code");
629
683
  let pre = document.createElement("pre");
630
684
  pre.setAttribute("aria-hidden", "true"); // Hide for screen readers
685
+ pre.setAttribute("tabindex", "-1"); // Hide for keyboard navigation
686
+ pre.setAttribute("inert", true); // Hide for keyboard navigation
631
687
 
632
688
  // Save elements internally
633
689
  this.preElement = pre;
@@ -647,12 +703,22 @@ var codeInput = {
647
703
  this.append(dialogContainerElement);
648
704
  this.dialogContainerElement = dialogContainerElement;
649
705
 
706
+ let keyboardNavigationInstructions = document.createElement("div");
707
+ keyboardNavigationInstructions.classList.add("code-input_keyboard-navigation-instructions");
708
+ dialogContainerElement.append(keyboardNavigationInstructions);
709
+
650
710
  this.pluginEvt("afterElementsAdded");
651
711
 
652
712
  this.dispatchEvent(new CustomEvent("code-input_load"));
653
713
 
654
714
  this.value = value;
655
715
  this.animateFrame();
716
+
717
+ const resizeObserver = new ResizeObserver((elements) => {
718
+ // The only element that could be resized is this code-input element.
719
+ this.syncSize();
720
+ });
721
+ resizeObserver.observe(this);
656
722
  }
657
723
 
658
724
  /**
@@ -737,7 +803,7 @@ var codeInput = {
737
803
  if (this.template.preElementStyled) this.classList.add("code-input_pre-element-styled");
738
804
  else this.classList.remove("code-input_pre-element-styled");
739
805
  // Syntax Highlight
740
- this.needsHighlight = true;
806
+ this.scheduleHighlight();
741
807
 
742
808
  break;
743
809
 
@@ -753,13 +819,15 @@ var codeInput = {
753
819
  if (code.classList.contains(`language-${newValue}`)) break; // Already updated
754
820
  }
755
821
 
822
+ if(oldValue !== null) {
823
+ // Case insensitive
824
+ oldValue = oldValue.toLowerCase();
756
825
 
757
- // Case insensitive
758
- oldValue = oldValue.toLowerCase();
759
-
760
- // Remove old language class and add new
761
- code.classList.remove("language-" + oldValue); // From codeElement
762
- code.parentElement.classList.remove("language-" + oldValue); // From preElement
826
+ // Remove old language class and add new
827
+ code.classList.remove("language-" + oldValue); // From codeElement
828
+ code.parentElement.classList.remove("language-" + oldValue); // From preElement
829
+ }
830
+ // Add new language class
763
831
  code.classList.remove("language-none"); // Prism
764
832
  code.parentElement.classList.remove("language-none"); // Prism
765
833
 
@@ -769,7 +837,7 @@ var codeInput = {
769
837
 
770
838
  if (mainTextarea.placeholder == oldValue) mainTextarea.placeholder = newValue;
771
839
 
772
- this.needsHighlight = true;
840
+ this.scheduleHighlight();
773
841
 
774
842
  break;
775
843
  default:
@@ -806,23 +874,33 @@ var codeInput = {
806
874
  * @override
807
875
  */
808
876
  addEventListener(type, listener, options = undefined) {
809
- // Save a copy of the callback where `this` refers to the code-input element
810
- let boundCallback = listener.bind(this);
877
+ // Save a copy of the callback where `this` refers to the code-input element.
878
+ let boundCallback = function (evt) {
879
+ if (typeof listener === 'function') {
880
+ listener(evt);
881
+ } else if (listener && listener.handleEvent) {
882
+ listener.handleEvent(evt);
883
+ }
884
+ }.bind(this);
811
885
  this.boundEventCallbacks[listener] = boundCallback;
812
886
 
813
887
  if (codeInput.textareaSyncEvents.includes(type)) {
814
- // Synchronise with textarea
888
+ // Synchronise with textarea, only when handleEventsFromTextarea is true
889
+ // This callback is modified to only run when the handleEventsFromTextarea is set.
890
+ let conditionalBoundCallback = function(evt) { if(this.handleEventsFromTextarea) boundCallback(evt); }.bind(this);
891
+ this.boundEventCallbacks[listener] = conditionalBoundCallback;
892
+
815
893
  if (options === undefined) {
816
894
  if(this.textareaElement == null) {
817
895
  this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback); });
818
896
  } else {
819
- this.textareaElement.addEventListener(type, boundCallback);
897
+ this.textareaElement.addEventListener(type, conditionalBoundCallback);
820
898
  }
821
899
  } else {
822
900
  if(this.textareaElement == null) {
823
901
  this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback, options); });
824
902
  } else {
825
- this.textareaElement.addEventListener(type, boundCallback, options);
903
+ this.textareaElement.addEventListener(type, conditionalBoundCallback, options);
826
904
  }
827
905
  }
828
906
  } else {
@@ -885,7 +963,7 @@ var codeInput = {
885
963
  // Save in editable textarea element
886
964
  this.textareaElement.value = val;
887
965
  // Trigger highlight
888
- this.needsHighlight = true;
966
+ this.scheduleHighlight();
889
967
  return val;
890
968
  }
891
969
 
@@ -972,16 +1050,12 @@ var codeInput = {
972
1050
  * has loaded (or now if it has already loaded)
973
1051
  */
974
1052
  runOnceWindowLoaded(callback, codeInputElem) {
975
- if(codeInput.windowLoaded) {
1053
+ if(document.readyState == "complete") {
976
1054
  callback(); // Fully loaded
977
1055
  } else {
978
1056
  window.addEventListener("load", callback);
979
1057
  }
980
- },
981
- windowLoaded: false
1058
+ }
982
1059
  }
983
- window.addEventListener("load", function() {
984
- codeInput.windowLoaded = true;
985
- });
986
1060
 
987
- customElements.define("code-input", codeInput.CodeInput);
1061
+ customElements.define("code-input", codeInput.CodeInput);
@@ -1 +1 @@
1
- code-input{display:block;overflow-y:auto;overflow-x:auto;position:relative;top:0;left:0;margin:8px;--padding:16px;height:250px;font-size:inherit;font-family:monospace;line-height:1.5;tab-size:2;caret-color:#a9a9a9;white-space:pre;padding:0!important;display:grid;grid-template-columns:100%;grid-template-rows:100%}code-input:not(.code-input_loaded){margin:0!important;margin-bottom:calc(-1 * var(--padding,16px))!important;padding:var(--padding,16px)!important;border:0}code-input textarea,code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{margin:0!important;padding:var(--padding,16px)!important;border:0;min-width:calc(100% - var(--padding,16px) * 2);min-height:calc(100% - var(--padding,16px) * 2);overflow:hidden;resize:none;grid-row:1;grid-column:1;display:block}code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{height:max-content;width:max-content}code-input.code-input_pre-element-styled pre code,code-input:not(.code-input_pre-element-styled) pre{margin:0!important;padding:0!important;width:100%;height:100%}code-input pre,code-input pre *,code-input textarea{font-size:inherit!important;font-family:inherit!important;line-height:inherit!important;tab-size:inherit!important}code-input pre,code-input textarea{grid-column:1;grid-row:1}code-input textarea{z-index:1}code-input pre{z-index:0}code-input:not(.code-input_loaded) pre,code-input:not(.code-input_loaded) textarea{opacity:0}code-input:not(.code-input_loaded)::before{color:#ccc}code-input textarea{color:transparent;background:0 0;caret-color:inherit!important}code-input textarea::placeholder{color:#d3d3d3}code-input pre,code-input textarea{white-space:inherit;word-spacing:normal;word-break:normal;word-wrap:normal}code-input textarea{resize:none;outline:0!important}code-input:not(.code-input_registered)::before{content:"Use codeInput.registerTemplate to set up.";display:block;color:grey}code-input .code-input_dialog-container{z-index:2;position:sticky;grid-row:1;grid-column:1;top:0;left:0;width:100%;height:0;text-align:left}
1
+ code-input{display:block;overflow-y:auto;overflow-x:auto;position:relative;top:0;left:0;margin:8px;--padding:16px;height:250px;font-size:inherit;font-family:monospace;line-height:1.5;tab-size:2;caret-color:#a9a9a9;white-space:pre;padding:0!important;display:grid;grid-template-columns:100%;grid-template-rows:100%}code-input:not(.code-input_loaded){padding:var(--padding,16px)!important}code-input textarea,code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{margin:0!important;padding:var(--padding,16px)!important;border:0;min-width:calc(100% - var(--padding) * 2);min-height:calc(100% - var(--padding) * 2);box-sizing:content-box;overflow:hidden;resize:none;grid-row:1;grid-column:1;display:block}code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{height:max-content;width:max-content}code-input.code-input_pre-element-styled pre code,code-input:not(.code-input_pre-element-styled) pre{margin:0!important;padding:0!important;width:100%;height:100%}code-input pre,code-input pre *,code-input textarea{font-size:inherit!important;font-family:inherit!important;line-height:inherit!important;tab-size:inherit!important;text-align:inherit!important}code-input textarea[dir=auto]+pre{unicode-bidi:plaintext}code-input textarea[dir=ltr]+pre{direction:ltr}code-input textarea[dir=rtl]+pre{direction:rtl}code-input pre,code-input textarea{grid-column:1;grid-row:1}code-input textarea{z-index:1}code-input pre{z-index:0}code-input textarea{color:transparent;background:0 0;caret-color:inherit!important}code-input textarea::placeholder{color:#d3d3d3}code-input pre,code-input textarea{white-space:inherit;word-spacing:normal;word-break:normal;word-wrap:normal}code-input textarea{resize:none;outline:0!important}code-input:has(textarea:focus):not(.code-input_mouse-focused){outline:2px solid #000}code-input:not(.code-input_registered){overflow:hidden;display:block;box-sizing:border-box}code-input:not(.code-input_registered)::after{content:"Use codeInput.registerTemplate to set up.";display:block;position:absolute;bottom:var(--padding);left:var(--padding);width:calc(100% - 2 * var(--padding));border-top:1px solid grey;outline:var(--padding) solid #fff;background-color:#fff}code-input:not(.code-input_loaded) pre,code-input:not(.code-input_loaded) textarea{opacity:0}code-input .code-input_dialog-container{z-index:2;position:sticky;grid-row:1;grid-column:1;top:0;left:0;margin:0;padding:0;width:100%;height:0;text-align:inherit}[dir=rtl] code-input .code-input_dialog-container,code-input[dir=rtl] .code-input_dialog-container{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions{top:0;left:0;display:block;position:absolute;background-color:#000;color:#fff;padding:2px;padding-left:10px;margin:0;text-wrap:balance;overflow:hidden;text-overflow:ellipsis;width:calc(100% - 12px);max-height:3em}code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-navigation-instructions{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions:empty,code-input.code-input_mouse-focused .code-input_dialog-container .code-input_keyboard-navigation-instructions,code-input:not(:has(textarea:focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions{display:none}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code{padding-top:calc(var(--padding) + 3em)!important}
package/code-input.min.js CHANGED
@@ -1 +1 @@
1
- var codeInput={observedAttributes:["value","placeholder","language","lang","template"],textareaSyncAttributes:["value","min","max","type","pattern","autocomplete","autocorrect","autofocus","cols","dirname","disabled","form","maxlength","minlength","name","placeholder","readonly","required","rows","spellcheck","wrap"],textareaSyncEvents:["change","selectionchange","invalid","input"],usedTemplates:{},defaultTemplate:void 0,templateNotYetRegisteredQueue:{},registerTemplate:function(a,b){if(!("string"==typeof a||a instanceof String))throw TypeError(`code-input: Name of template "${a}" must be a string.`);if(!("function"==typeof b.highlight||b.highlight instanceof Function))throw TypeError(`code-input: Template for "${a}" invalid, because the highlight function provided is not a function; it is "${b.highlight}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.includeCodeInputInHighlightFunc||b.includeCodeInputInHighlightFunc instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the includeCodeInputInHighlightFunc value provided is not a true or false; it is "${b.includeCodeInputInHighlightFunc}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.preElementStyled||b.preElementStyled instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the preElementStyled value provided is not a true or false; it is "${b.preElementStyled}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.isCode||b.isCode instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the isCode value provided is not a true or false; it is "${b.isCode}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!Array.isArray(b.plugins))throw TypeError(`code-input: Template for "${a}" invalid, because the plugin array provided is not an array; it is "${b.plugins}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(b.plugins.forEach((c,d)=>{if(!(c instanceof codeInput.Plugin))throw TypeError(`code-input: Template for "${a}" invalid, because the plugin provided at index ${d} is not valid; it is "${b.plugins[d]}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`)}),codeInput.usedTemplates[a]=b,a in codeInput.templateNotYetRegisteredQueue){for(let c in codeInput.templateNotYetRegisteredQueue[a])elem=codeInput.templateNotYetRegisteredQueue[a][c],elem.template=b,codeInput.runOnceWindowLoaded(function(a){a.connectedCallback()}.bind(null,elem),elem);console.log(`code-input: template: Added existing elements with template ${a}`)}if(null==codeInput.defaultTemplate){if(codeInput.defaultTemplate=a,void 0 in codeInput.templateNotYetRegisteredQueue)for(let a in codeInput.templateNotYetRegisteredQueue[void 0])elem=codeInput.templateNotYetRegisteredQueue[void 0][a],elem.template=b,codeInput.runOnceWindowLoaded(function(a){a.connectedCallback()}.bind(null,elem),elem);console.log(`code-input: template: Set template ${a} as default`)}console.log(`code-input: template: Created template ${a}`)},Template:class{constructor(a=function(){},b=!0,c=!0,d=!1,e=[]){this.highlight=a,this.preElementStyled=b,this.isCode=c,this.includeCodeInputInHighlightFunc=d,this.plugins=e}highlight=function(){};preElementStyled=!0;isCode=!0;includeCodeInputInHighlightFunc=!1;plugins=[]},templates:{prism(a,b=[]){return new codeInput.Template(a.highlightElement,!0,!0,!1,b)},hljs(a,b=[]){return new codeInput.Template(function(b){b.removeAttribute("data-highlighted"),a.highlightElement(b)},!1,!0,!1,b)},characterLimit(a){return{highlight:function(a,b,c=[]){let d=+b.getAttribute("data-character-limit"),e=b.escapeHtml(b.value.slice(0,d)),f=b.escapeHtml(b.value.slice(d));a.innerHTML=`${e}<mark class="overflow">${f}</mark>`,0<f.length&&(a.innerHTML+=` <mark class="overflow-msg">${b.getAttribute("data-overflow-msg")||"(Character limit reached)"}</mark>`)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,plugins:a}},rainbowText(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return{highlight:function(a,b){let c=[],d=b.value.split(b.template.delimiter);for(let e=0;e<d.length;e++)c.push(`<span style="color: ${b.template.rainbowColors[e%b.template.rainbowColors.length]}">${b.escapeHtml(d[e])}</span>`);a.innerHTML=c.join(b.template.delimiter)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,rainbowColors:a,delimiter:b,plugins:c}},character_limit(){return this.characterLimit([])},rainbow_text(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return this.rainbowText(a,b,c)},custom(a=function(){},b=!0,c=!0,d=!1,e=[]){return{highlight:a,includeCodeInputInHighlightFunc:d,preElementStyled:b,isCode:c,plugins:e}}},plugins:new Proxy({},{get(a,b){if(a[b]==null)throw ReferenceError(`code-input: Plugin '${b}' is not defined. Please ensure you import the necessary files from the plugins folder in the WebCoder49/code-input repository, in the <head> of your HTML, before the plugin is instatiated.`);return a[b]}}),Plugin:class{constructor(a){console.log("code-input: plugin: Created plugin"),a.forEach(a=>{codeInput.observedAttributes.push(a)})}beforeHighlight(){}afterHighlight(){}beforeElementsAdded(){}afterElementsAdded(){}attributeChanged(){}},CodeInput:class extends HTMLElement{constructor(){super()}textareaElement=null;preElement=null;codeElement=null;dialogContainerElement=null;static formAssociated=!0;boundEventCallbacks={};pluginEvt(a,b){for(let c in this.template.plugins){let d=this.template.plugins[c];a in d&&(b===void 0?d[a](this):d[a](this,...b))}}needsHighlight=!1;scheduleHighlight(){this.needsHighlight=!0}animateFrame(){this.template.preElementStyled?(this.style.backgroundColor=getComputedStyle(this.preElement).backgroundColor,this.textareaElement.style.height=getComputedStyle(this.preElement).height,this.textareaElement.style.width=getComputedStyle(this.preElement).width):(this.style.backgroundColor=getComputedStyle(this.codeElement).backgroundColor,this.textareaElement.style.height=getComputedStyle(this.codeElement).height,this.textareaElement.style.width=getComputedStyle(this.codeElement).width),this.needsHighlight&&(this.update(),this.needsHighlight=!1),window.requestAnimationFrame(this.animateFrame.bind(this))}update(){let a=this.codeElement,b=this.value;("\n"==b[b.length-1]||0==b.length)&&(b+=" "),a.innerHTML=this.escapeHtml(b),this.pluginEvt("beforeHighlight"),this.template.includeCodeInputInHighlightFunc?this.template.highlight(a,this):this.template.highlight(a),this.pluginEvt("afterHighlight")}escapeHtml(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;")}unescapeHtml(a){return a.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")}getTemplate(){let a;return a=null==this.getAttribute("template")?codeInput.defaultTemplate:this.getAttribute("template"),a in codeInput.usedTemplates?codeInput.usedTemplates[a]:(a in codeInput.templateNotYetRegisteredQueue||(codeInput.templateNotYetRegisteredQueue[a]=[]),void codeInput.templateNotYetRegisteredQueue[a].push(this))}setup(){if(null!=this.textareaElement)return;this.classList.add("code-input_registered"),this.template.preElementStyled&&this.classList.add("code-input_pre-element-styled"),this.pluginEvt("beforeElementsAdded");let a=this.getAttribute("language")||this.getAttribute("lang"),b=this.getAttribute("placeholder")||this.getAttribute("lang")||"",c=this.unescapeHtml(this.innerHTML)||this.getAttribute("value")||"";this.initialValue=c;let d=document.createElement("textarea");d.placeholder=b,""!=c&&(d.value=c),d.innerHTML=this.innerHTML,d.setAttribute("spellcheck","false"),this.innerHTML="";for(let a,b=0;b<this.attributes.length;b++)a=this.attributes[b].name,(codeInput.textareaSyncAttributes.includes(a)||"aria-"==a.substring(0,5))&&d.setAttribute(a,this.getAttribute(a));d.addEventListener("input",()=>{this.value=this.textareaElement.value}),this.textareaElement=d,this.append(d);let e=document.createElement("code"),f=document.createElement("pre");f.setAttribute("aria-hidden","true"),this.preElement=f,this.codeElement=e,f.append(e),this.append(f),this.template.isCode&&a!=null&&""!=a&&e.classList.add("language-"+a.toLowerCase());let g=document.createElement("div");g.classList.add("code-input_dialog-container"),this.append(g),this.dialogContainerElement=g,this.pluginEvt("afterElementsAdded"),this.dispatchEvent(new CustomEvent("code-input_load")),this.value=c,this.animateFrame()}escape_html(a){return this.escapeHtml(a)}get_template(){return this.getTemplate()}connectedCallback(){this.template=this.getTemplate(),this.template!=null&&(this.classList.add("code-input_registered"),codeInput.runOnceWindowLoaded(()=>{this.setup(),this.classList.add("code-input_loaded")},this)),this.mutationObserver=new MutationObserver(this.mutationObserverCallback.bind(this)),this.mutationObserver.observe(this,{attributes:!0,attributeOldValue:!0})}mutationObserverCallback(a){for(const b of a)if("attributes"===b.type){for(let a=0;a<codeInput.observedAttributes.length;a++)if(b.attributeName==codeInput.observedAttributes[a])return this.attributeChangedCallback(b.attributeName,b.oldValue,super.getAttribute(b.attributeName));if("aria-"==b.attributeName.substring(0,5))return this.attributeChangedCallback(b.attributeName,b.oldValue,super.getAttribute(b.attributeName))}}disconnectedCallback(){this.mutationObserver.disconnect()}attributeChangedCallback(a,b,c){if(this.isConnected)switch(this.pluginEvt("attributeChanged",[a,b,c]),a){case"value":this.value=c;break;case"template":this.template=codeInput.usedTemplates[c||codeInput.defaultTemplate],this.template.preElementStyled?this.classList.add("code-input_pre-element-styled"):this.classList.remove("code-input_pre-element-styled"),this.needsHighlight=!0;break;case"lang":case"language":let d=this.codeElement,e=this.textareaElement;if(null!=c&&(c=c.toLowerCase(),d.classList.contains(`language-${c}`)))break;b=b.toLowerCase(),d.classList.remove("language-"+b),d.parentElement.classList.remove("language-"+b),d.classList.remove("language-none"),d.parentElement.classList.remove("language-none"),null!=c&&""!=c&&d.classList.add("language-"+c),e.placeholder==b&&(e.placeholder=c),this.needsHighlight=!0;break;default:codeInput.textareaSyncAttributes.includes(a)||"aria-"==a.substring(0,5)?null==c||null==c?this.textareaElement.removeAttribute(a):this.textareaElement.setAttribute(a,c):codeInput.textareaSyncAttributes.regexp.forEach(b=>{a.match(b)&&(null==c?this.textareaElement.removeAttribute(a):this.textareaElement.setAttribute(a,c))})}}addEventListener(a,b,c=void 0){let d=b.bind(this);this.boundEventCallbacks[b]=d,codeInput.textareaSyncEvents.includes(a)?c===void 0?null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.addEventListener(a,d)}):this.textareaElement.addEventListener(a,d):null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.addEventListener(a,d,c)}):this.textareaElement.addEventListener(a,d,c):c===void 0?super.addEventListener(a,d):super.addEventListener(a,d,c)}removeEventListener(a,b,c=void 0){let d=this.boundEventCallbacks[b];codeInput.textareaSyncEvents.includes(a)?c===void 0?null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.removeEventListener(a,d)}):this.textareaElement.removeEventListener(a,d):null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.removeEventListener(a,d,c)}):this.textareaElement.removeEventListener(a,d,c):c===void 0?super.removeEventListener(a,d):super.removeEventListener(a,d,c)}get value(){return this.textareaElement.value}set value(a){return(null===a||void 0===a)&&(a=""),this.textareaElement.value=a,this.needsHighlight=!0,a}get placeholder(){return this.getAttribute("placeholder")}set placeholder(a){return this.setAttribute("placeholder",a)}get validity(){return this.textareaElement.validity}get validationMessage(){return this.textareaElement.validationMessage}setCustomValidity(a){return this.textareaElement.setCustomValidity(a)}checkValidity(){return this.textareaElement.checkValidity()}reportValidity(){return this.textareaElement.reportValidity()}pluginData={};formResetCallback(){this.value=this.initialValue}},runOnceWindowLoaded(a){codeInput.windowLoaded?a():window.addEventListener("load",a)},windowLoaded:!1};window.addEventListener("load",function(){codeInput.windowLoaded=!0}),customElements.define("code-input",codeInput.CodeInput);
1
+ var codeInput={observedAttributes:["value","placeholder","language","lang","template"],textareaSyncAttributes:["value","min","max","type","pattern","autocomplete","autocorrect","autofocus","cols","dirname","disabled","form","maxlength","minlength","name","placeholder","readonly","required","rows","spellcheck","wrap"],textareaSyncEvents:["change","selectionchange","invalid","input"],usedTemplates:{},defaultTemplate:void 0,templateNotYetRegisteredQueue:{},registerTemplate:function(a,b){if(!("string"==typeof a||a instanceof String))throw TypeError(`code-input: Name of template "${a}" must be a string.`);if(!("function"==typeof b.highlight||b.highlight instanceof Function))throw TypeError(`code-input: Template for "${a}" invalid, because the highlight function provided is not a function; it is "${b.highlight}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.includeCodeInputInHighlightFunc||b.includeCodeInputInHighlightFunc instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the includeCodeInputInHighlightFunc value provided is not a true or false; it is "${b.includeCodeInputInHighlightFunc}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.preElementStyled||b.preElementStyled instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the preElementStyled value provided is not a true or false; it is "${b.preElementStyled}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!("boolean"==typeof b.isCode||b.isCode instanceof Boolean))throw TypeError(`code-input: Template for "${a}" invalid, because the isCode value provided is not a true or false; it is "${b.isCode}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(!Array.isArray(b.plugins))throw TypeError(`code-input: Template for "${a}" invalid, because the plugin array provided is not an array; it is "${b.plugins}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);if(b.plugins.forEach((c,d)=>{if(!(c instanceof codeInput.Plugin))throw TypeError(`code-input: Template for "${a}" invalid, because the plugin provided at index ${d} is not valid; it is "${b.plugins[d]}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`)}),codeInput.usedTemplates[a]=b,a in codeInput.templateNotYetRegisteredQueue){for(let c in codeInput.templateNotYetRegisteredQueue[a])elem=codeInput.templateNotYetRegisteredQueue[a][c],elem.template=b,codeInput.runOnceWindowLoaded(function(a){a.connectedCallback()}.bind(null,elem),elem);console.log(`code-input: template: Added existing elements with template ${a}`)}if(null==codeInput.defaultTemplate){if(codeInput.defaultTemplate=a,void 0 in codeInput.templateNotYetRegisteredQueue)for(let a in codeInput.templateNotYetRegisteredQueue[void 0])elem=codeInput.templateNotYetRegisteredQueue[void 0][a],elem.template=b,codeInput.runOnceWindowLoaded(function(a){a.connectedCallback()}.bind(null,elem),elem);console.log(`code-input: template: Set template ${a} as default`)}console.log(`code-input: template: Created template ${a}`)},Template:class{constructor(a=function(){},b=!0,c=!0,d=!1,e=[]){this.highlight=a,this.preElementStyled=b,this.isCode=c,this.includeCodeInputInHighlightFunc=d,this.plugins=e}highlight=function(){};preElementStyled=!0;isCode=!0;includeCodeInputInHighlightFunc=!1;plugins=[]},templates:{prism(a,b=[]){return new codeInput.Template(a.highlightElement,!0,!0,!1,b)},hljs(a,b=[]){return new codeInput.Template(function(b){b.removeAttribute("data-highlighted"),a.highlightElement(b)},!1,!0,!1,b)},characterLimit(a){return{highlight:function(a,b,c=[]){let d=+b.getAttribute("data-character-limit"),e=b.escapeHtml(b.value.slice(0,d)),f=b.escapeHtml(b.value.slice(d));a.innerHTML=`${e}<mark class="overflow">${f}</mark>`,0<f.length&&(a.innerHTML+=` <mark class="overflow-msg">${b.getAttribute("data-overflow-msg")||"(Character limit reached)"}</mark>`)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,plugins:a}},rainbowText(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return{highlight:function(a,b){let c=[],d=b.value.split(b.template.delimiter);for(let e=0;e<d.length;e++)c.push(`<span style="color: ${b.template.rainbowColors[e%b.template.rainbowColors.length]}">${b.escapeHtml(d[e])}</span>`);a.innerHTML=c.join(b.template.delimiter)},includeCodeInputInHighlightFunc:!0,preElementStyled:!0,isCode:!1,rainbowColors:a,delimiter:b,plugins:c}},character_limit(){return this.characterLimit([])},rainbow_text(a=["red","orangered","orange","goldenrod","gold","green","darkgreen","navy","blue","magenta"],b="",c=[]){return this.rainbowText(a,b,c)},custom(a=function(){},b=!0,c=!0,d=!1,e=[]){return{highlight:a,includeCodeInputInHighlightFunc:d,preElementStyled:b,isCode:c,plugins:e}}},plugins:new Proxy({},{get(a,b){if(a[b]==null)throw ReferenceError(`code-input: Plugin '${b}' is not defined. Please ensure you import the necessary files from the plugins folder in the WebCoder49/code-input repository, in the <head> of your HTML, before the plugin is instatiated.`);return a[b]}}),Plugin:class{constructor(a){console.log("code-input: plugin: Created plugin"),a.forEach(a=>{codeInput.observedAttributes.push(a)})}addTranslations(a,b){for(const c in b)a[c]=b[c]}beforeHighlight(){}afterHighlight(){}beforeElementsAdded(){}afterElementsAdded(){}attributeChanged(){}},CodeInput:class extends HTMLElement{constructor(){super()}textareaElement=null;preElement=null;codeElement=null;dialogContainerElement=null;static formAssociated=!0;boundEventCallbacks={};pluginEvt(a,b){for(let c in this.template.plugins){let d=this.template.plugins[c];a in d&&(b===void 0?d[a](this):d[a](this,...b))}}needsHighlight=!1;handleEventsFromTextarea=!0;originalAriaDescription;scheduleHighlight(){this.needsHighlight=!0}animateFrame(){this.needsHighlight&&(this.update(),this.needsHighlight=!1),window.requestAnimationFrame(this.animateFrame.bind(this))}update(){let a=this.codeElement,b=this.value;b+="\n",a.innerHTML=this.escapeHtml(b),this.pluginEvt("beforeHighlight"),this.template.includeCodeInputInHighlightFunc?this.template.highlight(a,this):this.template.highlight(a),this.syncSize(),this.textareaElement===document.activeElement&&(this.handleEventsFromTextarea=!1,this.textareaElement.blur(),this.textareaElement.focus(),this.handleEventsFromTextarea=!0),this.pluginEvt("afterHighlight")}syncSize(){this.template.preElementStyled?(this.style.backgroundColor=getComputedStyle(this.preElement).backgroundColor,this.textareaElement.style.height=getComputedStyle(this.preElement).height,this.textareaElement.style.width=getComputedStyle(this.preElement).width):(this.style.backgroundColor=getComputedStyle(this.codeElement).backgroundColor,this.textareaElement.style.height=getComputedStyle(this.codeElement).height,this.textareaElement.style.width=getComputedStyle(this.codeElement).width)}setKeyboardNavInstructions(a,b){this.dialogContainerElement.querySelector(".code-input_keyboard-navigation-instructions").innerText=a,b?this.textareaElement.setAttribute("aria-description",this.originalAriaDescription+". "+a):this.textareaElement.setAttribute("aria-description",a)}escapeHtml(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;")}unescapeHtml(a){return a.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")}getTemplate(){let a;return a=null==this.getAttribute("template")?codeInput.defaultTemplate:this.getAttribute("template"),a in codeInput.usedTemplates?codeInput.usedTemplates[a]:(a in codeInput.templateNotYetRegisteredQueue||(codeInput.templateNotYetRegisteredQueue[a]=[]),void codeInput.templateNotYetRegisteredQueue[a].push(this))}setup(){if(null!=this.textareaElement)return;this.classList.add("code-input_registered"),this.template.preElementStyled&&this.classList.add("code-input_pre-element-styled"),this.pluginEvt("beforeElementsAdded");let a=this.getAttribute("language")||this.getAttribute("lang"),b=this.getAttribute("placeholder")||this.getAttribute("language")||this.getAttribute("lang")||"",c=this.unescapeHtml(this.innerHTML)||this.getAttribute("value")||"";this.initialValue=c;let d=document.createElement("textarea");d.placeholder=b,""!=c&&(d.value=c),d.innerHTML=this.innerHTML,d.setAttribute("spellcheck","false"),d.setAttribute("tabindex",this.getAttribute("tabindex")||0),this.setAttribute("tabindex",-1),this.originalAriaDescription=this.getAttribute("aria-description")||"Code input field",this.addEventListener("mousedown",()=>{this.classList.add("code-input_mouse-focused")}),d.addEventListener("blur",()=>{this.handleEventsFromTextarea&&this.classList.remove("code-input_mouse-focused")}),this.innerHTML="";for(let a,b=0;b<this.attributes.length;b++)a=this.attributes[b].name,(codeInput.textareaSyncAttributes.includes(a)||"aria-"==a.substring(0,5))&&d.setAttribute(a,this.getAttribute(a));d.addEventListener("input",()=>{this.value=this.textareaElement.value}),this.textareaElement=d,this.append(d);let e=document.createElement("code"),f=document.createElement("pre");f.setAttribute("aria-hidden","true"),f.setAttribute("tabindex","-1"),f.setAttribute("inert",!0),this.preElement=f,this.codeElement=e,f.append(e),this.append(f),this.template.isCode&&a!=null&&""!=a&&e.classList.add("language-"+a.toLowerCase());let g=document.createElement("div");g.classList.add("code-input_dialog-container"),this.append(g),this.dialogContainerElement=g;let h=document.createElement("div");h.classList.add("code-input_keyboard-navigation-instructions"),g.append(h),this.pluginEvt("afterElementsAdded"),this.dispatchEvent(new CustomEvent("code-input_load")),this.value=c,this.animateFrame();const i=new ResizeObserver(()=>{this.syncSize()});i.observe(this)}escape_html(a){return this.escapeHtml(a)}get_template(){return this.getTemplate()}connectedCallback(){this.template=this.getTemplate(),this.template!=null&&(this.classList.add("code-input_registered"),codeInput.runOnceWindowLoaded(()=>{this.setup(),this.classList.add("code-input_loaded")},this)),this.mutationObserver=new MutationObserver(this.mutationObserverCallback.bind(this)),this.mutationObserver.observe(this,{attributes:!0,attributeOldValue:!0})}mutationObserverCallback(a){for(const b of a)if("attributes"===b.type){for(let a=0;a<codeInput.observedAttributes.length;a++)if(b.attributeName==codeInput.observedAttributes[a])return this.attributeChangedCallback(b.attributeName,b.oldValue,super.getAttribute(b.attributeName));if("aria-"==b.attributeName.substring(0,5))return this.attributeChangedCallback(b.attributeName,b.oldValue,super.getAttribute(b.attributeName))}}disconnectedCallback(){this.mutationObserver.disconnect()}attributeChangedCallback(a,b,c){if(this.isConnected)switch(this.pluginEvt("attributeChanged",[a,b,c]),a){case"value":this.value=c;break;case"template":this.template=codeInput.usedTemplates[c||codeInput.defaultTemplate],this.template.preElementStyled?this.classList.add("code-input_pre-element-styled"):this.classList.remove("code-input_pre-element-styled"),this.scheduleHighlight();break;case"lang":case"language":let d=this.codeElement,e=this.textareaElement;if(null!=c&&(c=c.toLowerCase(),d.classList.contains(`language-${c}`)))break;null!==b&&(b=b.toLowerCase(),d.classList.remove("language-"+b),d.parentElement.classList.remove("language-"+b)),d.classList.remove("language-none"),d.parentElement.classList.remove("language-none"),null!=c&&""!=c&&d.classList.add("language-"+c),e.placeholder==b&&(e.placeholder=c),this.scheduleHighlight();break;default:codeInput.textareaSyncAttributes.includes(a)||"aria-"==a.substring(0,5)?null==c||null==c?this.textareaElement.removeAttribute(a):this.textareaElement.setAttribute(a,c):codeInput.textareaSyncAttributes.regexp.forEach(b=>{a.match(b)&&(null==c?this.textareaElement.removeAttribute(a):this.textareaElement.setAttribute(a,c))})}}addEventListener(a,b,c=void 0){let d=function(a){"function"==typeof b?b(a):b&&b.handleEvent&&b.handleEvent(a)}.bind(this);if(this.boundEventCallbacks[b]=d,codeInput.textareaSyncEvents.includes(a)){let e=function(a){this.handleEventsFromTextarea&&d(a)}.bind(this);this.boundEventCallbacks[b]=e,void 0===c?null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.addEventListener(a,d)}):this.textareaElement.addEventListener(a,e):null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.addEventListener(a,d,c)}):this.textareaElement.addEventListener(a,e,c)}else void 0===c?super.addEventListener(a,d):super.addEventListener(a,d,c)}removeEventListener(a,b,c=void 0){let d=this.boundEventCallbacks[b];codeInput.textareaSyncEvents.includes(a)?c===void 0?null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.removeEventListener(a,d)}):this.textareaElement.removeEventListener(a,d):null==this.textareaElement?this.addEventListener("code-input_load",()=>{this.textareaElement.removeEventListener(a,d,c)}):this.textareaElement.removeEventListener(a,d,c):c===void 0?super.removeEventListener(a,d):super.removeEventListener(a,d,c)}get value(){return this.textareaElement.value}set value(a){return(null===a||void 0===a)&&(a=""),this.textareaElement.value=a,this.scheduleHighlight(),a}get placeholder(){return this.getAttribute("placeholder")}set placeholder(a){return this.setAttribute("placeholder",a)}get validity(){return this.textareaElement.validity}get validationMessage(){return this.textareaElement.validationMessage}setCustomValidity(a){return this.textareaElement.setCustomValidity(a)}checkValidity(){return this.textareaElement.checkValidity()}reportValidity(){return this.textareaElement.reportValidity()}pluginData={};formResetCallback(){this.value=this.initialValue}},runOnceWindowLoaded(a){"complete"==document.readyState?a():window.addEventListener("load",a)}};customElements.define("code-input",codeInput.CodeInput);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcoder49/code-input",
3
- "version": "2.2.1",
3
+ "version": "2.5.1",
4
4
  "description": "Fully customisable, editable syntax-highlighted textareas.",
5
5
  "browser": "code-input.js",
6
6
  "scripts": {
package/plugins/README.md CHANGED
@@ -65,6 +65,13 @@ Files: [special-chars.js](./special-chars.js) / [special-chars.css](./special-ch
65
65
 
66
66
  [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/jOeYJbm)
67
67
 
68
+ ### Select Token Callbacks
69
+ Make tokens in the `<pre><code>` element that are included within the selected text of the `<code-input>` gain a CSS class while selected, or trigger JavaScript callbacks.
70
+
71
+ Files: select-token-callbacks.js
72
+
73
+ [🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/WNVZXxM)
74
+
68
75
  ## Using Plugins
69
76
  Plugins allow you to add extra features to a template, like [automatic indentation](./indent.js) or [support for highlight.js's language autodetection](./autodetect.js). To use them, just:
70
77
  - Import the plugins' JS/CSS files (there may only be one of these; import all of the files that exist) after you have imported `code-input` and before registering the template.
@@ -19,13 +19,17 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
19
19
 
20
20
  /* Add keystroke events */
21
21
  afterElementsAdded(codeInput) {
22
- codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) });
23
- codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); });
22
+ codeInput.pluginData.autoCloseBrackets = { automatedKeypresses: false};
23
+ codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event); });
24
+ codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkClosingBracket(codeInput, event); });
25
+ codeInput.textareaElement.addEventListener('input', (event) => { this.checkOpeningBracket(codeInput, event); });
24
26
  }
25
27
 
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) {
28
+ /* Deal with the ability to "retype" a closing bracket where one has already
29
+ been placed. Runs before input so newly typing a closing bracket can be
30
+ prevented.*/
31
+ checkClosingBracket(codeInput, event) {
32
+ if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return;
29
33
  if(event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) {
30
34
  // Check if a closing bracket is typed
31
35
  for(let openingBracket in this.bracketPairs) {
@@ -37,11 +41,22 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
37
41
  break;
38
42
  }
39
43
  }
40
- } else if(event.data in this.bracketPairs) {
44
+ }
45
+ }
46
+
47
+ /* Deal with the automatic creation of closing bracket when opening brackets are typed. Runs after input for consistency between browsers. */
48
+ checkOpeningBracket(codeInput, event) {
49
+ if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return;
50
+ if(event.data in this.bracketPairs) {
41
51
  // Opening bracket typed; Create bracket pair
42
52
  let closingBracket = this.bracketPairs[event.data];
43
53
  // Insert the closing bracket
54
+ // automatedKeypresses property to prevent keypresses being captured
55
+ // by this plugin during automated input as some browsers
56
+ // (e.g. GNOME Web) do.
57
+ codeInput.pluginData.autoCloseBrackets.automatedKeypresses = true;
44
58
  document.execCommand("insertText", false, closingBracket);
59
+ codeInput.pluginData.autoCloseBrackets.automatedKeypresses = false;
45
60
  // Move caret before the inserted closing bracket
46
61
  codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1;
47
62
  }
@@ -49,6 +64,7 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
49
64
 
50
65
  /* Deal with cases where a backspace deleting an opening bracket deletes the closing bracket straight after it as well */
51
66
  checkBackspace(codeInput, event) {
67
+ if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return;
52
68
  if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) {
53
69
  let closingBracket = this.bracketPairs[codeInput.textareaElement.value[codeInput.textareaElement.selectionStart-1]];
54
70
  if(closingBracket != undefined && codeInput.textareaElement.value[codeInput.textareaElement.selectionStart] == closingBracket) {
@@ -58,4 +74,4 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
58
74
  }
59
75
  }
60
76
  }
61
- }
77
+ }
@@ -1 +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)}}};
1
+ codeInput.plugins.AutoCloseBrackets=class extends codeInput.Plugin{bracketPairs=[];bracketsOpenedStack=[];constructor(a={"(":")","[":"]","{":"}",'"':"\""}){super([]),this.bracketPairs=a}afterElementsAdded(a){a.pluginData.autoCloseBrackets={automatedKeypresses:!1},a.textareaElement.addEventListener("keydown",b=>{this.checkBackspace(a,b)}),a.textareaElement.addEventListener("beforeinput",b=>{this.checkClosingBracket(a,b)}),a.textareaElement.addEventListener("input",b=>{this.checkOpeningBracket(a,b)})}checkClosingBracket(a,b){if(!a.pluginData.autoCloseBrackets.automatedKeypresses&&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}}}checkOpeningBracket(a,b){if(!a.pluginData.autoCloseBrackets.automatedKeypresses&&b.data in this.bracketPairs){let c=this.bracketPairs[b.data];a.pluginData.autoCloseBrackets.automatedKeypresses=!0,document.execCommand("insertText",!1,c),a.pluginData.autoCloseBrackets.automatedKeypresses=!1,a.textareaElement.selectionStart=a.textareaElement.selectionEnd-=1}}checkBackspace(a,b){if(!a.pluginData.autoCloseBrackets.automatedKeypresses&&"Backspace"==b.key&&a.textareaElement.selectionStart==a.textareaElement.selectionEnd){let b=this.bracketPairs[a.textareaElement.value[a.textareaElement.selectionStart-1]];null!=b&&a.textareaElement.value[a.textareaElement.selectionStart]==b&&(a.textareaElement.selectionEnd=a.textareaElement.selectionStart+1,a.textareaElement.selectionStart-=1)}}};
@@ -5,7 +5,7 @@
5
5
  codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
6
6
  /**
7
7
  * Pass in a function to create a plugin that displays the popup that takes in (popup element, textarea, textarea.selectionEnd).
8
- * @param {function} updatePopupCallback a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
8
+ * @param {(popupElement: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number) => void} 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
@@ -30,7 +30,9 @@ codeInput.plugins.Autocomplete = class extends codeInput.Plugin {
30
30
  codeInput.appendChild(popupElem);
31
31
 
32
32
  let testPosPre = document.createElement("pre");
33
- testPosPre.setAttribute("aria-hidden", "true"); // Hide for screen readers
33
+ popupElem.setAttribute("inert", true); // Invisible to keyboard navigation
34
+ popupElem.setAttribute("tabindex", -1); // Invisible to keyboard navigation
35
+ testPosPre.setAttribute("aria-hidden", true); // Hide for screen readers
34
36
  if(codeInput.template.preElementStyled) {
35
37
  testPosPre.classList.add("code-input_autocomplete_testpos");
36
38
  codeInput.appendChild(testPosPre); // Styled like first pre, but first pre found to update
@@ -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");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(){}};
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(b.setAttribute("inert",!0),b.setAttribute("tabindex",-1),c.setAttribute("aria-hidden",!0),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(){}};