@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.
- package/CONTRIBUTING.md +11 -1
- package/README.md +26 -11
- package/code-input.css +126 -29
- package/code-input.d.ts +153 -11
- package/code-input.js +218 -193
- package/code-input.min.css +1 -1
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +28 -6
- package/plugins/auto-close-brackets.js +61 -0
- package/plugins/auto-close-brackets.min.js +1 -0
- package/plugins/autocomplete.js +21 -12
- package/plugins/autocomplete.min.js +1 -1
- package/plugins/autodetect.js +4 -4
- package/plugins/autodetect.min.js +1 -1
- package/plugins/find-and-replace.css +145 -0
- package/plugins/find-and-replace.js +746 -0
- package/plugins/find-and-replace.min.css +1 -0
- package/plugins/find-and-replace.min.js +1 -0
- package/plugins/go-to-line.css +77 -0
- package/plugins/go-to-line.js +175 -0
- package/plugins/go-to-line.min.css +1 -0
- package/plugins/go-to-line.min.js +1 -0
- package/plugins/indent.js +166 -15
- package/plugins/indent.min.js +1 -1
- package/plugins/prism-line-numbers.css +10 -9
- package/plugins/prism-line-numbers.min.css +1 -1
- package/plugins/select-token-callbacks.js +289 -0
- package/plugins/select-token-callbacks.min.js +1 -0
- package/plugins/special-chars.css +1 -5
- package/plugins/special-chars.js +65 -61
- package/plugins/special-chars.min.css +2 -2
- package/plugins/special-chars.min.js +1 -1
- package/plugins/test.js +1 -2
- package/plugins/test.min.js +1 -1
- package/tests/hljs.html +55 -0
- package/tests/i18n.html +197 -0
- package/tests/prism-match-braces-compatibility.js +215 -0
- package/tests/prism-match-braces-compatibility.min.js +1 -0
- package/tests/prism.html +54 -0
- package/tests/tester.js +593 -0
- package/tests/tester.min.js +21 -0
- package/plugins/debounce-update.js +0 -40
- 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:
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
549
|
+
* Set the size of the textarea element to the size of the pre/code element.
|
|
531
550
|
*/
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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) => {
|
|
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
|
-
|
|
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.
|
|
710
|
+
this.pluginEvt("afterElementsAdded");
|
|
655
711
|
|
|
656
712
|
this.dispatchEvent(new CustomEvent("code-input_load"));
|
|
657
|
-
}
|
|
658
713
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
|
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
|
|
863
|
-
|
|
864
|
-
|
|
938
|
+
} else {
|
|
939
|
+
// Synchronise with code-input element
|
|
940
|
+
if (options === undefined) {
|
|
941
|
+
super.removeEventListener(type, boundCallback);
|
|
865
942
|
} else {
|
|
866
|
-
|
|
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
|
-
|
|
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
|
-
|
|
888
|
-
this.
|
|
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.
|
|
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(
|
|
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);
|