@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
package/code-input.js CHANGED
@@ -19,6 +19,7 @@ var codeInput = {
19
19
  observedAttributes: [
20
20
  "value",
21
21
  "placeholder",
22
+ "language",
22
23
  "lang",
23
24
  "template"
24
25
  ],
@@ -29,7 +30,6 @@ var codeInput = {
29
30
  * code-input element.
30
31
  */
31
32
  textareaSyncAttributes: [
32
- "aria-*",
33
33
  "value",
34
34
  // Form validation - https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation
35
35
  "min", "max",
@@ -103,7 +103,7 @@ var codeInput = {
103
103
  * @param {Object} template - a Template object instance - see `codeInput.templates`
104
104
  */
105
105
  registerTemplate: function (templateName, template) {
106
- if(!(typeof templateName == "string" || templateName instanceof String)) throw TypeError(`code-input: Template for "${templateName}" must be a string.`);
106
+ if(!(typeof templateName == "string" || templateName instanceof String)) throw TypeError(`code-input: Name of template "${templateName}" must be a string.`);
107
107
  if(!(typeof template.highlight == "function" || template.highlight instanceof Function)) throw TypeError(`code-input: Template for "${templateName}" invalid, because the highlight function provided is not a function; it is "${template.highlight}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
108
108
  if(!(typeof template.includeCodeInputInHighlightFunc == "boolean" || template.includeCodeInputInHighlightFunc instanceof Boolean)) throw TypeError(`code-input: Template for "${templateName}" invalid, because the includeCodeInputInHighlightFunc value provided is not a true or false; it is "${template.includeCodeInputInHighlightFunc}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
109
109
  if(!(typeof template.preElementStyled == "boolean" || template.preElementStyled instanceof Boolean)) throw TypeError(`code-input: Template for "${templateName}" invalid, because the preElementStyled value provided is not a true or false; it is "${template.preElementStyled}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
@@ -124,7 +124,7 @@ var codeInput = {
124
124
  elem = codeInput.templateNotYetRegisteredQueue[templateName][i];
125
125
  elem.template = template;
126
126
  codeInput.runOnceWindowLoaded((function(elem) { elem.connectedCallback(); }).bind(null, elem), elem);
127
- // Bind sets elem in parameter
127
+ // Bind sets elem as first parameter of function
128
128
  // So innerHTML can be read
129
129
  }
130
130
  console.log(`code-input: template: Added existing elements with template ${templateName}`);
@@ -138,7 +138,7 @@ var codeInput = {
138
138
  elem = codeInput.templateNotYetRegisteredQueue[undefined][i];
139
139
  elem.template = template;
140
140
  codeInput.runOnceWindowLoaded((function(elem) { elem.connectedCallback(); }).bind(null, elem), elem);
141
- // Bind sets elem in parameter
141
+ // Bind sets elem as first parameter of function
142
142
  // So innerHTML can be read
143
143
  }
144
144
  }
@@ -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
- * @returns template object
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;
@@ -179,7 +179,7 @@ var codeInput = {
179
179
  * `<code-input>` element parameter if `this.includeCodeInputInHighlightFunc` is
180
180
  * `true`.
181
181
  */
182
- highlight = function() {};
182
+ highlight = function(codeElement) {};
183
183
 
184
184
  /**
185
185
  * Is the <pre> element CSS-styled as well as the `<code>` element?
@@ -226,37 +226,38 @@ var codeInput = {
226
226
  * Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/)
227
227
  * @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter.
228
228
  * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
229
- * @returns template object
229
+ * @returns {codeInput.Template} template object
230
230
  */
231
231
  prism(prism, plugins = []) { // Dependency: Prism.js (https://prismjs.com/)
232
- return {
233
- includeCodeInputInHighlightFunc: false,
234
- highlight: prism.highlightElement,
235
- preElementStyled: true,
236
- isCode: true,
237
- plugins: plugins,
238
- };
232
+ return new codeInput.Template(
233
+ prism.highlightElement, // highlight
234
+ true, // preElementStyled
235
+ true, // isCode
236
+ false, // includeCodeInputInHighlightFunc
237
+ plugins
238
+ );
239
239
  },
240
240
  /**
241
241
  * Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/)
242
242
  * @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter.
243
243
  * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
244
- * @returns template object
244
+ * @returns {codeInput.Template} template object
245
245
  */
246
246
  hljs(hljs, plugins = []) { // Dependency: Highlight.js (https://highlightjs.org/)
247
- return {
248
- includeCodeInputInHighlightFunc: false,
249
- highlight: hljs.highlightElement,
250
- preElementStyled: false,
251
- isCode: true,
252
- plugins: plugins,
253
- };
247
+ return new codeInput.Template(
248
+ function(codeElement) {
249
+ codeElement.removeAttribute("data-highlighted");
250
+ hljs.highlightElement(codeElement);
251
+ }, // highlight
252
+ false, // preElementStyled
253
+ true, // isCode
254
+ false, // includeCodeInputInHighlightFunc
255
+ plugins
256
+ );
254
257
  },
255
258
 
256
259
  /**
257
- * Constructor to create a proof-of-concept template that gives a message if too many characters are typed.
258
- * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
259
- * @returns template object
260
+ * @deprecated Make your own version of this template if you need it - we think it isn't widely used so will remove it from the next version of code-input.
260
261
  */
261
262
  characterLimit(plugins) {
262
263
  return {
@@ -280,11 +281,7 @@ var codeInput = {
280
281
  },
281
282
 
282
283
  /**
283
- * Constructor to create a proof-of-concept template that shows text in a repeating series of colors.
284
- * @param {string[]} rainbowColors - An array of CSS colors, in the order each color will be shown
285
- * @param {string} delimiter - The character used to split up parts of text where each part is a different colour (e.g. "" = characters, " " = words)
286
- * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins`
287
- * @returns template object
284
+ * @deprecated Make your own version of this template if you need it - we think it isn't widely used so will remove it from the next version of code-input.
288
285
  */
289
286
  rainbowText(rainbowColors = ["red", "orangered", "orange", "goldenrod", "gold", "green", "darkgreen", "navy", "blue", "magenta"], delimiter = "", plugins = []) {
290
287
  return {
@@ -299,20 +296,22 @@ var codeInput = {
299
296
  includeCodeInputInHighlightFunc: true,
300
297
  preElementStyled: true,
301
298
  isCode: false,
299
+
302
300
  rainbowColors: rainbowColors,
303
301
  delimiter: delimiter,
302
+
304
303
  plugins: plugins,
305
304
  }
306
305
  },
307
306
 
308
307
  /**
309
- * @deprecated Please use `codeInput.characterLimit(plugins)`
308
+ * @deprecated Make your own version of this template if you need it - we think it isn't widely used so will remove it from the next version of code-input.
310
309
  */
311
310
  character_limit() {
312
311
  return this.characterLimit([]);
313
312
  },
314
313
  /**
315
- * @deprecated Please use `codeInput.rainbowText`
314
+ * @deprecated Make your own version of this template if you need it - we think it isn't widely used so will remove it from the next version of code-input.
316
315
  */
317
316
  rainbow_text(rainbowColors = ["red", "orangered", "orange", "goldenrod", "gold", "green", "darkgreen", "navy", "blue", "magenta"], delimiter = "", plugins = []) {
318
317
  return this.rainbowText(rainbowColors, delimiter, plugins);
@@ -374,17 +373,21 @@ var codeInput = {
374
373
  console.log("code-input: plugin: Created plugin");
375
374
 
376
375
  observedAttributes.forEach((attribute) => {
377
- // Move plugin attribute to codeInput observed attributes
378
- let regexFromWildcard = codeInput.wildcard2regex(attribute);
379
- if(regexFromWildcard == null) {
380
- // Not a wildcard
381
- codeInput.observedAttributes.push(attribute);
382
- } else {
383
- codeInput.observedAttributes.regexp.push(regexFromWildcard);
384
- }
376
+ codeInput.observedAttributes.push(attribute);
385
377
  });
386
378
  }
387
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
+
388
391
  /**
389
392
  * Runs before code is highlighted.
390
393
  * @param {codeInput.CodeInput} codeInput - The codeInput element
@@ -426,10 +429,6 @@ var codeInput = {
426
429
  constructor() {
427
430
  super(); // Element
428
431
  }
429
- /**
430
- * Store value internally
431
- */
432
- _value = '';
433
432
 
434
433
  /**
435
434
  * Exposed child textarea element for user to input code in
@@ -439,13 +438,19 @@ var codeInput = {
439
438
  * Exposed child pre element where syntax-highlighted code is outputted.
440
439
  * Contains this.codeElement as its only child.
441
440
  */
442
- preElement = null;
441
+ preElement = null
443
442
  /**
444
443
  * Exposed child pre element's child code element where syntax-highlighted code is outputted.
445
444
  * Has this.preElement as its parent.
446
445
  */
447
446
  codeElement = null;
448
447
 
448
+ /**
449
+ * Exposed non-scrolling element designed to contain dialog boxes etc. that shouldn't scroll
450
+ * with the code-input element.
451
+ */
452
+ dialogContainerElement = null;
453
+
449
454
  /**
450
455
  * Form-Associated Custom Element Callbacks
451
456
  * https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example
@@ -487,33 +492,37 @@ var codeInput = {
487
492
  * the result (pre code) element, then use the template object
488
493
  * to syntax-highlight it. */
489
494
 
490
- /** Update the text value to the result element, after the textarea contents have changed.
491
- * @param {string} value - The text value of the code-input element
492
- * @param {boolean} originalUpdate - Whether this update originates from the textarea's content; if so, run it first so custom updates override it.
495
+ needsHighlight = false; // Just inputted
496
+ handleEventsFromTextarea = true; // Turn to false when unusual internal events are called on the textarea
497
+ originalAriaDescription;
498
+
499
+ /**
500
+ * Highlight the code as soon as possible
493
501
  */
494
- update(value) {
495
- // Prevent this from running multiple times on the same input when "value" attribute is changed,
496
- // by not running when value is already equal to the input of this (implying update has already
497
- // been run). Thank you to peterprvy for this.
498
- if (this.ignoreValueUpdate) return;
499
-
500
- if(this.textareaElement == null) {
501
- this.addEventListener("code-input_load", () => { this.update(value) }); // Only run when fully loaded
502
- return;
503
- }
502
+ scheduleHighlight() {
503
+ this.needsHighlight = true;
504
+ }
504
505
 
505
- this.ignoreValueUpdate = true;
506
- this.value = value;
507
- this.ignoreValueUpdate = false;
508
- if (this.textareaElement.value != value) this.textareaElement.value = value;
506
+ /**
507
+ * Call an animation frame
508
+ */
509
+ animateFrame() {
510
+ // Synchronise the contents of the pre/code and textarea elements
511
+ if(this.needsHighlight) {
512
+ this.update();
513
+ this.needsHighlight = false;
514
+ }
509
515
 
516
+ window.requestAnimationFrame(this.animateFrame.bind(this));
517
+ }
510
518
 
519
+ /**
520
+ * Update the text value to the result element, after the textarea contents have changed.
521
+ */
522
+ update() {
511
523
  let resultElement = this.codeElement;
512
-
513
- // Handle final newlines
514
- if (value[value.length - 1] == "\n") {
515
- value += " ";
516
- }
524
+ let value = this.value;
525
+ value += "\n"; // Placeholder for next line
517
526
 
518
527
  // Update code
519
528
  resultElement.innerHTML = this.escapeHtml(value);
@@ -523,18 +532,47 @@ var codeInput = {
523
532
  if (this.template.includeCodeInputInHighlightFunc) this.template.highlight(resultElement, this);
524
533
  else this.template.highlight(resultElement);
525
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
+
526
545
  this.pluginEvt("afterHighlight");
527
546
  }
528
547
 
529
548
  /**
530
- * Synchronise the scrolling of the textarea to the result element.
549
+ * Set the size of the textarea element to the size of the pre/code element.
531
550
  */
532
- syncScroll() {
533
- let inputElement = this.textareaElement;
534
- let resultElement = this.template.preElementStyled ? this.preElement : this.codeElement;
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
+ }
535
563
 
536
- resultElement.scrollTop = inputElement.scrollTop;
537
- resultElement.scrollLeft = inputElement.scrollLeft;
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
+ }
538
576
  }
539
577
 
540
578
  /**
@@ -592,8 +630,8 @@ var codeInput = {
592
630
  this.pluginEvt("beforeElementsAdded");
593
631
 
594
632
  // First-time attribute sync
595
- let lang = this.getAttribute("lang");
596
- let placeholder = this.getAttribute("placeholder") || this.getAttribute("lang") || "";
633
+ let lang = this.getAttribute("language") || 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,26 +645,34 @@ 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
 
613
667
  // Synchronise attributes to textarea
614
- codeInput.textareaSyncAttributes.forEach((attribute) => {
615
- if (this.hasAttribute(attribute)) {
668
+ for(let i = 0; i < this.attributes.length; i++) {
669
+ let attribute = this.attributes[i].name;
670
+ if (codeInput.textareaSyncAttributes.includes(attribute) || attribute.substring(0, 5) == "aria-") {
616
671
  textarea.setAttribute(attribute, this.getAttribute(attribute));
617
672
  }
618
- });
619
- codeInput.textareaSyncAttributes.regexp.forEach((reg) =>
620
- {
621
- for(const attr of this.attributes) {
622
- if (attr.nodeName.match(reg)) {
623
- textarea.setAttribute(attr.nodeName, attr.nodeValue);
624
- }
625
- }
626
- });
673
+ }
627
674
 
628
- textarea.addEventListener('input', (evt) => { textarea.parentElement.update(textarea.value); textarea.parentElement.sync_scroll(); });
629
- textarea.addEventListener('scroll', (evt) => textarea.parentElement.sync_scroll());
675
+ textarea.addEventListener('input', (evt) => { this.value = this.textareaElement.value; });
630
676
 
631
677
  // Save element internally
632
678
  this.textareaElement = textarea;
@@ -636,6 +682,8 @@ var codeInput = {
636
682
  let code = document.createElement("code");
637
683
  let pre = document.createElement("pre");
638
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
639
687
 
640
688
  // Save elements internally
641
689
  this.preElement = pre;
@@ -645,22 +693,32 @@ var codeInput = {
645
693
 
646
694
  if (this.template.isCode) {
647
695
  if (lang != undefined && lang != "") {
648
- code.classList.add("language-" + lang);
696
+ code.classList.add("language-" + lang.toLowerCase());
649
697
  }
650
698
  }
651
699
 
652
- this.pluginEvt("afterElementsAdded");
700
+ // dialogContainerElement used to store non-scrolling dialog boxes, etc.
701
+ let dialogContainerElement = document.createElement("div");
702
+ dialogContainerElement.classList.add("code-input_dialog-container");
703
+ this.append(dialogContainerElement);
704
+ this.dialogContainerElement = dialogContainerElement;
705
+
706
+ let keyboardNavigationInstructions = document.createElement("div");
707
+ keyboardNavigationInstructions.classList.add("code-input_keyboard-navigation-instructions");
708
+ dialogContainerElement.append(keyboardNavigationInstructions);
653
709
 
654
- this.update(value);
710
+ this.pluginEvt("afterElementsAdded");
655
711
 
656
712
  this.dispatchEvent(new CustomEvent("code-input_load"));
657
- }
658
713
 
659
- /**
660
- * @deprecated Please use `codeInput.CodeInput.syncScroll`
661
- */
662
- sync_scroll() {
663
- this.syncScroll();
714
+ this.value = value;
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);
664
722
  }
665
723
 
666
724
  /**
@@ -671,7 +729,7 @@ var codeInput = {
671
729
  }
672
730
 
673
731
  /**
674
- * @deprecated Please use `codeInput.CodeInput.escapeHtml`
732
+ * @deprecated Please use `codeInput.CodeInput.getTemplate`
675
733
  */
676
734
  get_template() {
677
735
  return this.getTemplate();
@@ -715,13 +773,8 @@ var codeInput = {
715
773
  return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
716
774
  }
717
775
  }
718
-
719
- /* Check wildcard attributes */
720
- for(let i = 0; i < codeInput.observedAttributes.regexp.length; i++) {
721
- const reg = codeInput.observedAttributes.regexp[i];
722
- if (mutation.attributeName.match(reg)) {
723
- return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
724
- }
776
+ if (mutation.attributeName.substring(0, 5) == "aria-") {
777
+ return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
725
778
  }
726
779
  }
727
780
  }
@@ -745,20 +798,17 @@ var codeInput = {
745
798
  case "value":
746
799
  this.value = newValue;
747
800
  break;
748
- case "placeholder":
749
- this.textareaElement.placeholder = newValue;
750
- break;
751
801
  case "template":
752
802
  this.template = codeInput.usedTemplates[newValue || codeInput.defaultTemplate];
753
803
  if (this.template.preElementStyled) this.classList.add("code-input_pre-element-styled");
754
804
  else this.classList.remove("code-input_pre-element-styled");
755
805
  // Syntax Highlight
756
- this.update(this.value);
806
+ this.scheduleHighlight();
757
807
 
758
808
  break;
759
809
 
760
810
  case "lang":
761
-
811
+ case "language":
762
812
  let code = this.codeElement;
763
813
  let mainTextarea = this.textareaElement;
764
814
 
@@ -769,13 +819,15 @@ var codeInput = {
769
819
  if (code.classList.contains(`language-${newValue}`)) break; // Already updated
770
820
  }
771
821
 
822
+ if(oldValue !== null) {
823
+ // Case insensitive
824
+ oldValue = oldValue.toLowerCase();
772
825
 
773
- // Case insensitive
774
- oldValue = oldValue.toLowerCase();
775
-
776
- // Remove old language class and add new
777
- code.classList.remove("language-" + oldValue); // From codeElement
778
- 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
779
831
  code.classList.remove("language-none"); // Prism
780
832
  code.parentElement.classList.remove("language-none"); // Prism
781
833
 
@@ -785,11 +837,11 @@ var codeInput = {
785
837
 
786
838
  if (mainTextarea.placeholder == oldValue) mainTextarea.placeholder = newValue;
787
839
 
788
- this.update(this.value);
840
+ this.scheduleHighlight();
789
841
 
790
842
  break;
791
843
  default:
792
- if (codeInput.textareaSyncAttributes.includes(name)) {
844
+ if (codeInput.textareaSyncAttributes.includes(name) || name.substring(0, 5) == "aria-") {
793
845
  if(newValue == null || newValue == undefined) {
794
846
  this.textareaElement.removeAttribute(name);
795
847
  } else {
@@ -822,24 +874,37 @@ var codeInput = {
822
874
  * @override
823
875
  */
824
876
  addEventListener(type, listener, options = undefined) {
825
- 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);
826
885
  this.boundEventCallbacks[listener] = boundCallback;
827
886
 
828
887
  if (codeInput.textareaSyncEvents.includes(type)) {
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
+
829
893
  if (options === undefined) {
830
894
  if(this.textareaElement == null) {
831
895
  this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback); });
832
896
  } else {
833
- this.textareaElement.addEventListener(type, boundCallback);
897
+ this.textareaElement.addEventListener(type, conditionalBoundCallback);
834
898
  }
835
899
  } else {
836
900
  if(this.textareaElement == null) {
837
901
  this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback, options); });
838
902
  } else {
839
- this.textareaElement.addEventListener(type, boundCallback, options);
903
+ this.textareaElement.addEventListener(type, conditionalBoundCallback, options);
840
904
  }
841
905
  }
842
906
  } else {
907
+ // Synchronise with code-input element
843
908
  if (options === undefined) {
844
909
  super.addEventListener(type, boundCallback);
845
910
  } else {
@@ -851,22 +916,32 @@ var codeInput = {
851
916
  /**
852
917
  * @override
853
918
  */
854
- removeEventListener(type, listener, options = null) {
919
+ removeEventListener(type, listener, options = undefined) {
920
+ // Save a copy of the callback where `this` refers to the code-input element
855
921
  let boundCallback = this.boundEventCallbacks[listener];
856
- if (type == "change") {
857
- if (options === null) {
858
- this.textareaElement.removeEventListener("change", boundCallback);
922
+
923
+ if (codeInput.textareaSyncEvents.includes(type)) {
924
+ // Synchronise with textarea
925
+ if (options === undefined) {
926
+ if(this.textareaElement == null) {
927
+ this.addEventListener("code-input_load", () => { this.textareaElement.removeEventListener(type, boundCallback); });
928
+ } else {
929
+ this.textareaElement.removeEventListener(type, boundCallback);
930
+ }
859
931
  } else {
860
- this.textareaElement.removeEventListener("change", boundCallback, options);
932
+ if(this.textareaElement == null) {
933
+ this.addEventListener("code-input_load", () => { this.textareaElement.removeEventListener(type, boundCallback, options); });
934
+ } else {
935
+ this.textareaElement.removeEventListener(type, boundCallback, options);
936
+ }
861
937
  }
862
- } else if (type == "selectionchange") {
863
- if (options === null) {
864
- this.textareaElement.removeEventListener("selectionchange", boundCallback);
938
+ } else {
939
+ // Synchronise with code-input element
940
+ if (options === undefined) {
941
+ super.removeEventListener(type, boundCallback);
865
942
  } else {
866
- this.textareaElement.removeEventListener("selectionchange", boundCallback, options);
943
+ super.removeEventListener(type, boundCallback, options);
867
944
  }
868
- } else {
869
- super.removeEventListener(type, listener, options);
870
945
  }
871
946
  }
872
947
 
@@ -874,7 +949,8 @@ var codeInput = {
874
949
  * Get the text contents of the code-input element.
875
950
  */
876
951
  get value() {
877
- return this._value;
952
+ // Get from editable textarea element
953
+ return this.textareaElement.value;
878
954
  }
879
955
  /**
880
956
  * Set the text contents of the code-input element.
@@ -884,8 +960,10 @@ var codeInput = {
884
960
  if (val === null || val === undefined) {
885
961
  val = "";
886
962
  }
887
- this._value = val;
888
- this.update(val);
963
+ // Save in editable textarea element
964
+ this.textareaElement.value = val;
965
+ // Trigger highlight
966
+ this.scheduleHighlight();
889
967
  return val;
890
968
  }
891
969
 
@@ -963,74 +1041,21 @@ var codeInput = {
963
1041
  * Update value on form reset
964
1042
  */
965
1043
  formResetCallback() {
966
- this.update(this.initialValue);
967
- };
968
- },
969
-
970
- arrayWildcards2regex(list) {
971
- for(let i = 0; i < list.length; i++) {
972
- const name = list[i];
973
- if (name.indexOf("*") < 0)
974
- continue;
975
-
976
- list.regexp.push(new RegExp("^" +
977
- name.replace(/[/\-\\^$+?.()|[\]{}]/g, '\\$&')
978
- .replace("*", ".*")
979
- + "$", "i"));
980
- list.splice(i--, 1);
1044
+ this.value = this.initialValue;
981
1045
  };
982
1046
  },
983
1047
 
984
- wildcard2regex(wildcard) {
985
- if (wildcard.indexOf("*") < 0)
986
- return null;
987
-
988
- return new RegExp("^" +
989
- wildcard.replace(/[/\-\\^$+?.()|[\]{}]/g, '\\$&')
990
- .replace("*", ".*")
991
- + "$", "i");
992
- },
993
-
994
1048
  /**
995
1049
  * To ensure the DOM is ready, run this callback after the window
996
1050
  * has loaded (or now if it has already loaded)
997
1051
  */
998
1052
  runOnceWindowLoaded(callback, codeInputElem) {
999
- if(codeInput.windowLoaded) {
1053
+ if(document.readyState == "complete") {
1000
1054
  callback(); // Fully loaded
1001
1055
  } else {
1002
1056
  window.addEventListener("load", callback);
1003
1057
  }
1004
- },
1005
- windowLoaded: false
1006
- }
1007
- window.addEventListener("load", function() {
1008
- codeInput.windowLoaded = true;
1009
- });
1010
-
1011
-
1012
- /**
1013
- * convert wildcards into regex
1014
- */
1015
-
1016
- {
1017
- Object.defineProperty(codeInput.textareaSyncAttributes, 'regexp', {
1018
- value: [],
1019
- writable: false,
1020
- enumerable: false,
1021
- configurable: false
1022
- });
1023
- codeInput.observedAttributes = codeInput.observedAttributes.concat(codeInput.textareaSyncAttributes);
1024
-
1025
- Object.defineProperty(codeInput.observedAttributes, 'regexp', {
1026
- value: [],
1027
- writable: false,
1028
- enumerable: false,
1029
- configurable: false
1030
- });
1031
-
1032
- codeInput.arrayWildcards2regex(codeInput.textareaSyncAttributes);
1033
- codeInput.arrayWildcards2regex(codeInput.observedAttributes);
1058
+ }
1034
1059
  }
1035
1060
 
1036
1061
  customElements.define("code-input", codeInput.CodeInput);