@webcoder49/code-input 2.5.1 → 2.6.2

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 (93) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +9 -126
  3. package/code-input.css +70 -33
  4. package/code-input.d.ts +135 -59
  5. package/code-input.js +201 -110
  6. package/code-input.min.css +1 -1
  7. package/code-input.min.js +12 -1
  8. package/docs/LICENSE +3 -0
  9. package/docs/LICENSE.CC-BY-SA-4.0 +116 -0
  10. package/docs/LICENSE.CC0-1.0 +30 -0
  11. package/docs/README.md +5 -0
  12. package/docs/_index.md +308 -0
  13. package/docs/i18n/_index.md +52 -0
  14. package/docs/interface/_index.md +3 -0
  15. package/docs/interface/css/_index.md +12 -0
  16. package/docs/interface/forms/_index.md +17 -0
  17. package/docs/interface/js/_index.md +11 -0
  18. package/docs/modules-and-frameworks/_index.md +3 -0
  19. package/docs/modules-and-frameworks/custom/_index.md +9 -0
  20. package/docs/modules-and-frameworks/hljs/_index.md +13 -0
  21. package/docs/modules-and-frameworks/hljs/esm/_index.md +71 -0
  22. package/docs/modules-and-frameworks/hljs/nuxt/_index.md +250 -0
  23. package/docs/modules-and-frameworks/hljs/nuxt/nuxt-demo-screenshot.png +0 -0
  24. package/docs/modules-and-frameworks/hljs/vue/_index.md +233 -0
  25. package/docs/modules-and-frameworks/hljs/vue/vue-demo-screenshot.png +0 -0
  26. package/docs/modules-and-frameworks/prism/_index.md +14 -0
  27. package/docs/plugins/_index.md +676 -0
  28. package/docs/plugins/new/_index.md +52 -0
  29. package/docs/theory/_index.md +9 -0
  30. package/esm/README.md +23 -0
  31. package/esm/code-input.d.mts +154 -0
  32. package/esm/code-input.mjs +997 -0
  33. package/esm/plugins/auto-close-brackets.d.mts +15 -0
  34. package/esm/plugins/auto-close-brackets.mjs +84 -0
  35. package/esm/plugins/autocomplete.d.mts +14 -0
  36. package/esm/plugins/autocomplete.mjs +93 -0
  37. package/esm/plugins/autodetect.d.mts +11 -0
  38. package/esm/plugins/autodetect.mjs +35 -0
  39. package/esm/plugins/find-and-replace.d.mts +43 -0
  40. package/esm/plugins/find-and-replace.mjs +777 -0
  41. package/esm/plugins/go-to-line.d.mts +29 -0
  42. package/esm/plugins/go-to-line.mjs +217 -0
  43. package/esm/plugins/indent.d.mts +22 -0
  44. package/esm/plugins/indent.mjs +359 -0
  45. package/esm/plugins/select-token-callbacks.d.mts +51 -0
  46. package/esm/plugins/select-token-callbacks.mjs +296 -0
  47. package/esm/plugins/special-chars.d.mts +25 -0
  48. package/esm/plugins/special-chars.mjs +207 -0
  49. package/esm/plugins/test.d.mts +16 -0
  50. package/esm/plugins/test.mjs +56 -0
  51. package/esm/templates/hljs.d.mts +16 -0
  52. package/esm/templates/hljs.mjs +28 -0
  53. package/esm/templates/prism.d.mts +16 -0
  54. package/esm/templates/prism.mjs +25 -0
  55. package/package.json +83 -7
  56. package/plugins/README.md +2 -0
  57. package/plugins/auto-close-brackets.js +2 -0
  58. package/plugins/auto-close-brackets.min.js +1 -1
  59. package/plugins/autocomplete.js +6 -6
  60. package/plugins/autocomplete.min.js +1 -1
  61. package/plugins/autodetect.js +4 -2
  62. package/plugins/autodetect.min.js +1 -1
  63. package/plugins/find-and-replace.css +0 -4
  64. package/plugins/find-and-replace.js +28 -8
  65. package/plugins/find-and-replace.min.css +1 -1
  66. package/plugins/find-and-replace.min.js +1 -1
  67. package/plugins/go-to-line.css +10 -5
  68. package/plugins/go-to-line.js +39 -6
  69. package/plugins/go-to-line.min.css +1 -1
  70. package/plugins/go-to-line.min.js +1 -1
  71. package/plugins/indent.js +4 -2
  72. package/plugins/indent.min.js +1 -1
  73. package/plugins/prism-line-numbers.css +14 -5
  74. package/plugins/prism-line-numbers.min.css +1 -1
  75. package/plugins/select-token-callbacks.js +3 -1
  76. package/plugins/select-token-callbacks.min.js +1 -1
  77. package/plugins/special-chars.css +13 -1
  78. package/plugins/special-chars.js +14 -4
  79. package/plugins/special-chars.min.css +1 -1
  80. package/plugins/special-chars.min.js +1 -1
  81. package/plugins/test.js +22 -7
  82. package/plugins/test.min.js +1 -1
  83. package/.github/workflows/minify.yml +0 -22
  84. package/.github/workflows/npm-publish.yml +0 -21
  85. package/CODE_OF_CONDUCT.md +0 -130
  86. package/CONTRIBUTING.md +0 -35
  87. package/tests/hljs.html +0 -55
  88. package/tests/i18n.html +0 -197
  89. package/tests/prism-match-braces-compatibility.js +0 -215
  90. package/tests/prism-match-braces-compatibility.min.js +0 -1
  91. package/tests/prism.html +0 -54
  92. package/tests/tester.js +0 -600
  93. package/tests/tester.min.js +0 -21
@@ -0,0 +1,997 @@
1
+ // NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!
2
+
3
+ /**
4
+ * **code-input** is a library which lets you create custom HTML `<code-input>`
5
+ * elements that act like `<textarea>` elements but support syntax-highlighted
6
+ * code, implemented using any typical syntax highlighting library.
7
+ *
8
+ * License of whole library for bundlers:
9
+ *
10
+ * Copyright 2021-2025 Oliver Geer and contributors
11
+ * @license MIT
12
+ *
13
+ * **<https://code-input-js.org>**
14
+ */
15
+ "use strict";
16
+
17
+
18
+ var codeInput = {
19
+ /**
20
+ * A list of attributes that will trigger the
21
+ * `codeInput.CodeInput.attributeChangedCallback`
22
+ * when modified in a code-input element. This
23
+ * does not include events, which are handled in
24
+ * `codeInput.CodeInput.addEventListener` and
25
+ * `codeInput.CodeInput.removeEventListener`.
26
+ */
27
+ observedAttributes: [
28
+ "value",
29
+ "placeholder",
30
+ "language",
31
+ "lang",
32
+ "template"
33
+ ],
34
+
35
+ /**
36
+ * A list of attributes that will be moved to
37
+ * the textarea after they are applied on the
38
+ * code-input element.
39
+ */
40
+ textareaSyncAttributes: [
41
+ "value",
42
+ // Form validation - https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation
43
+ "min", "max",
44
+ "type",
45
+ "pattern",
46
+
47
+ // Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea
48
+ "autocomplete",
49
+ "autocorrect",
50
+ "autofocus",
51
+ "cols",
52
+ "dirname",
53
+ "disabled",
54
+ "form",
55
+ "maxlength",
56
+ "minlength",
57
+ "name",
58
+ "placeholder",
59
+ "readonly",
60
+ "required",
61
+ "rows",
62
+ "spellcheck",
63
+ "wrap"
64
+ ],
65
+
66
+ /**
67
+ * A list of events whose listeners will be moved to
68
+ * the textarea after they are added to the
69
+ * code-input element.
70
+ */
71
+ textareaSyncEvents: [
72
+ "change",
73
+ "selectionchange",
74
+ "invalid",
75
+ "input",
76
+ "focus",
77
+ "blur",
78
+ "focusin",
79
+ "focusout"
80
+ ],
81
+
82
+ /* ------------------------------------
83
+ * ------------Templates---------------
84
+ * ------------------------------------ */
85
+
86
+ /**
87
+ * The templates currently available for any code-input elements
88
+ * to use. Registered using `codeInput.registerTemplate`.
89
+ * Key - Template Name
90
+ * Value - A Template Object
91
+ * @type {Object}
92
+ */
93
+ usedTemplates: {
94
+ },
95
+ /**
96
+ * The name of the default template that a code-input element that
97
+ * does not specify the template attribute uses.
98
+ * @type {string}
99
+ */
100
+ defaultTemplate: undefined,
101
+ /**
102
+ * A queue of elements waiting for a template to be registered,
103
+ * allowing elements to be created in HTML with a template before
104
+ * the template is registered in JS, for ease of use.
105
+ * Key - Template Name
106
+ * Value - An array of code-input elements
107
+ * @type {Object}
108
+ */
109
+ templateNotYetRegisteredQueue: {},
110
+
111
+ /**
112
+ * Register a template so code-input elements with a template attribute that equals the templateName will use the template.
113
+ * See `codeInput.templates` for constructors to create templates.
114
+ * @param {string} templateName - the name to register the template under
115
+ * @param {Object} template - a Template object instance - see `codeInput.templates`
116
+ */
117
+ registerTemplate: function (templateName, template) {
118
+ if(!(typeof templateName == "string" || templateName instanceof String)) throw TypeError(`code-input: Name of template "${templateName}" must be a string.`);
119
+ 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.`);
120
+ 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.`);
121
+ 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.`);
122
+ if(!(typeof template.isCode == "boolean" || template.isCode instanceof Boolean)) throw TypeError(`code-input: Template for "${templateName}" invalid, because the isCode value provided is not a true or false; it is "${template.isCode}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
123
+ if(!Array.isArray(template.plugins)) throw TypeError(`code-input: Template for "${templateName}" invalid, because the plugin array provided is not an array; it is "${template.plugins}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
124
+
125
+ template.plugins.forEach((plugin, i) => {
126
+ if(!(plugin instanceof codeInput.Plugin)) {
127
+ throw TypeError(`code-input: Template for "${templateName}" invalid, because the plugin provided at index ${i} is not valid; it is "${template.plugins[i]}". Please make sure you use one of the constructors in codeInput.templates, and that you provide the correct arguments.`);
128
+ }
129
+ });
130
+
131
+
132
+ codeInput.usedTemplates[templateName] = template;
133
+ // Add waiting code-input elements wanting this template from queue
134
+ if (templateName in codeInput.templateNotYetRegisteredQueue) {
135
+ for (let i in codeInput.templateNotYetRegisteredQueue[templateName]) {
136
+ const elem = codeInput.templateNotYetRegisteredQueue[templateName][i];
137
+ elem.templateObject = template;
138
+ elem.connectedCallback();
139
+ // Bind sets elem as first parameter of function
140
+ // So innerHTML can be read
141
+ }
142
+ }
143
+
144
+ if (codeInput.defaultTemplate == undefined) {
145
+ codeInput.defaultTemplate = templateName;
146
+ // Add elements with default template from queue
147
+ if (undefined in codeInput.templateNotYetRegisteredQueue) {
148
+ for (let i in codeInput.templateNotYetRegisteredQueue[undefined]) {
149
+ const elem = codeInput.templateNotYetRegisteredQueue[undefined][i];
150
+ elem.templateObject = template;
151
+ elem.connectedCallback();
152
+ // Bind sets elem as first parameter of function
153
+ // So innerHTML can be read
154
+ }
155
+ }
156
+ }
157
+ },
158
+
159
+ /**
160
+ * Please see `codeInput.templates.prism` or `codeInput.templates.hljs`.
161
+ * Templates are used in `<code-input>` elements and once registered with
162
+ * `codeInput.registerTemplate` will be in charge of the highlighting
163
+ * algorithm and settings for all code-inputs with a `template` attribute
164
+ * matching the registered name.
165
+ */
166
+ Template: class {
167
+ /**
168
+ * Constructor to create a custom template instance. Pass this into `codeInput.registerTemplate` to use it.
169
+ * I would strongly recommend using the built-in simpler template `codeInput.templates.prism` or `codeInput.templates.hljs`.
170
+ * @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
171
+ * @param {boolean} preElementStyled - is the `<pre>` element CSS-styled (if so set to true), or the `<code>` element (false)?
172
+ * @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]'.
173
+ * @param {boolean} includeCodeInputInHighlightFunc - Setting this to true passes the `<code-input>` element as a second argument to the highlight function.
174
+ * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.Plugin`
175
+ * @returns {codeInput.Template} template object
176
+ */
177
+ constructor(highlight = function (codeElement) { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = []) {
178
+ this.highlight = highlight;
179
+ this.preElementStyled = preElementStyled;
180
+ this.isCode = isCode;
181
+ this.includeCodeInputInHighlightFunc = includeCodeInputInHighlightFunc;
182
+ this.plugins = plugins;
183
+ }
184
+
185
+ /**
186
+ * A callback to highlight the code, that takes an HTML `<code>` element
187
+ * inside a `<pre>` element as a parameter, and an optional second
188
+ * `<code-input>` element parameter if `this.includeCodeInputInHighlightFunc` is
189
+ * `true`.
190
+ */
191
+ highlight = function(codeElement) {};
192
+
193
+ /**
194
+ * Is the <pre> element CSS-styled as well as the `<code>` element?
195
+ * If `true`, `<pre>` element's scrolling is synchronised; if false,
196
+ * <code> element's scrolling is synchronised.
197
+ */
198
+ preElementStyled = true;
199
+
200
+ /**
201
+ * Is this for writing code?
202
+ * If true, the code-input's lang HTML attribute can be used,
203
+ * and the `<code>` element will be given the class name
204
+ * 'language-[lang attribute's value]'.
205
+ */
206
+ isCode = true;
207
+
208
+ /**
209
+ * Setting this to true passes the `<code-input>` element as a
210
+ * second argument to the highlight function.
211
+ */
212
+ includeCodeInputInHighlightFunc = false;
213
+
214
+ /**
215
+ * An array of plugin objects to add extra features -
216
+ * see `codeInput.Plugin`.
217
+ */
218
+ plugins = [];
219
+ },
220
+
221
+
222
+ /* ------------------------------------
223
+ * ------------Plugins-----------------
224
+ * ------------------------------------ */
225
+
226
+ /**
227
+ * Before using any plugin in this namespace, please ensure you import its JavaScript
228
+ * files (in the plugins folder), or continue to get a more detailed error in the developer
229
+ * console.
230
+ *
231
+ * Where plugins are stored, after they are imported. The plugin
232
+ * file assigns them a space in this object.
233
+ * For adding completely new syntax-highlighting algorithms, please see `codeInput.templates`.
234
+ *
235
+ * Key - plugin name
236
+ *
237
+ * Value - plugin object
238
+ * @type {Object}
239
+ */
240
+ plugins: new Proxy({}, {
241
+ get(plugins, name) {
242
+ if(plugins[name] == undefined) {
243
+ throw ReferenceError(`code-input: Plugin '${name}' 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.`);
244
+ }
245
+ return plugins[name];
246
+ }
247
+ }),
248
+
249
+ /**
250
+ * Plugins are imported from the plugins folder. They will then
251
+ * provide custom extra functionality to code-input elements.
252
+ */
253
+ Plugin: class {
254
+ /**
255
+ * Create a Plugin
256
+ *
257
+ * @param {Array<string>} observedAttributes - The HTML attributes to watch for this plugin, and report any
258
+ * modifications to the `codeInput.Plugin.attributeChanged` method.
259
+ */
260
+ constructor(observedAttributes) {
261
+
262
+ observedAttributes.forEach((attribute) => {
263
+ codeInput.observedAttributes.push(attribute);
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Replace the values in destination with those from source where the keys match, in-place.
269
+ * @param {Object} destination Where to place the translated strings, already filled with the keys pointing to English strings.
270
+ * @param {Object} source The same keys, or some of them, mapped to translated strings. Keys not present here will retain the values they are maapped to in destination.
271
+ */
272
+ addTranslations(destination, source) {
273
+ for(const key in source) {
274
+ destination[key] = source[key];
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Runs before code is highlighted.
280
+ * @param {codeInput.CodeInput} codeInput - The codeInput element
281
+ */
282
+ beforeHighlight(codeInput) { }
283
+ /**
284
+ * Runs after code is highlighted.
285
+ * @param {codeInput.CodeInput} codeInput - The codeInput element
286
+ */
287
+ afterHighlight(codeInput) { }
288
+ /**
289
+ * Runs before elements are added into a code-input element.
290
+ * @param {codeInput.CodeInput} codeInput - The codeInput element
291
+ */
292
+ beforeElementsAdded(codeInput) { }
293
+ /**
294
+ * Runs after elements are added into a code-input element (useful for adding events to the textarea).
295
+ * @param {codeInput.CodeInput} codeInput - The codeInput element
296
+ */
297
+ afterElementsAdded(codeInput) { }
298
+ /**
299
+ * Runs when an attribute of a code-input element is changed (you must add the attribute name to `codeInput.Plugin.observedAttributes` first).
300
+ * @param {codeInput.CodeInput} codeInput - The codeInput element
301
+ * @param {string} name - The name of the attribute
302
+ * @param {string} oldValue - The value of the attribute before it was changed
303
+ * @param {string} newValue - The value of the attribute after it is changed
304
+ */
305
+ attributeChanged(codeInput, name, oldValue, newValue) { }
306
+ },
307
+
308
+ /* ------------------------------------
309
+ * -------------Main-------------------
310
+ * ------------------------------------ */
311
+
312
+ /**
313
+ * A `<code-input>` element, an instance of an `HTMLElement`, and the result
314
+ * of `document.createElement("code-input")`. Attributes are only set when
315
+ * the element's template has been registered, and before this are null.
316
+ */
317
+ CodeInput: class extends HTMLElement {
318
+ constructor() {
319
+ super(); // Element
320
+ }
321
+
322
+ /**
323
+ * When the code-input's template is registered, this contains its codeInput.Template object.
324
+ * Should be treated as read-only by external code.
325
+ */
326
+ templateObject = null;
327
+ /**
328
+ * Exposed child textarea element for user to input code in
329
+ */
330
+ textareaElement = null;
331
+ /**
332
+ * Exposed child pre element where syntax-highlighted code is outputted.
333
+ * Contains this.codeElement as its only child.
334
+ */
335
+ preElement = null
336
+ /**
337
+ * Exposed child pre element's child code element where syntax-highlighted code is outputted.
338
+ * Has this.preElement as its parent.
339
+ */
340
+ codeElement = null;
341
+
342
+ /**
343
+ * Exposed non-scrolling element designed to contain dialog boxes etc. from plugins,
344
+ * that shouldn't scroll with the code-input element.
345
+ */
346
+ dialogContainerElement = null;
347
+
348
+ /**
349
+ * Form-Associated Custom Element Callbacks
350
+ * https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example
351
+ */
352
+ static formAssociated = true;
353
+
354
+ /**
355
+ * When events are transferred to the textarea element, callbacks
356
+ * are bound to set the this variable to the code-input element
357
+ * rather than the textarea. This allows the callback to be converted
358
+ * to a bound one:
359
+ * Key - Callback not bound
360
+ * Value - Callback that is bound, with this equalling the code-input element in the callback
361
+ */
362
+ boundEventCallbacks = {};
363
+
364
+ /** Trigger this event in all plugins with a optional list of arguments
365
+ * @param {string} eventName - the name of the event to trigger
366
+ * @param {Array} args - the arguments to pass into the event callback in the template after the code-input element. Normally left empty
367
+ */
368
+ pluginEvt(eventName, args) {
369
+ for (let i in this.templateObject.plugins) {
370
+ let plugin = this.templateObject.plugins[i];
371
+ if (eventName in plugin) {
372
+ if (args === undefined) {
373
+ plugin[eventName](this);
374
+ } else {
375
+ plugin[eventName](this, ...args);
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ /* ------------------------------------
382
+ * ----------Main Functionality--------
383
+ * ------------------------------------
384
+ * The main function of a code-input element is to take
385
+ * code written in its textarea element, copy this code into
386
+ * the result (pre code) element, then use the template object
387
+ * to syntax-highlight it. */
388
+
389
+ needsHighlight = false; // Just inputted
390
+ originalAriaDescription;
391
+
392
+ /**
393
+ * Highlight the code as soon as possible
394
+ */
395
+ scheduleHighlight() {
396
+ this.needsHighlight = true;
397
+ }
398
+
399
+ /**
400
+ * Call an animation frame
401
+ */
402
+ animateFrame() {
403
+ // Synchronise the contents of the pre/code and textarea elements
404
+ if(this.needsHighlight) {
405
+ this.update();
406
+ this.needsHighlight = false;
407
+ }
408
+
409
+ window.requestAnimationFrame(this.animateFrame.bind(this));
410
+ }
411
+
412
+ /**
413
+ * Update the text value to the result element, after the textarea contents have changed.
414
+ */
415
+ update() {
416
+ let resultElement = this.codeElement;
417
+ let value = this.value;
418
+ value += "\n"; // Placeholder for next line
419
+
420
+ // Update code
421
+ resultElement.innerHTML = this.escapeHtml(value);
422
+ this.pluginEvt("beforeHighlight");
423
+
424
+ // Syntax Highlight
425
+ if (this.templateObject.includeCodeInputInHighlightFunc) this.templateObject.highlight(resultElement, this);
426
+ else this.templateObject.highlight(resultElement);
427
+
428
+ this.syncSize();
429
+
430
+ this.pluginEvt("afterHighlight");
431
+ }
432
+
433
+ /**
434
+ * Set the size of the textarea element to the size of the pre/code element.
435
+ */
436
+ syncSize() {
437
+ // Synchronise the size of the pre/code and textarea elements
438
+ if(this.templateObject.preElementStyled) {
439
+ this.style.backgroundColor = getComputedStyle(this.preElement).backgroundColor;
440
+ this.textareaElement.style.height = getComputedStyle(this.preElement).height;
441
+ this.textareaElement.style.width = getComputedStyle(this.preElement).width;
442
+ } else {
443
+ this.style.backgroundColor = getComputedStyle(this.codeElement).backgroundColor;
444
+ this.textareaElement.style.height = getComputedStyle(this.codeElement).height;
445
+ this.textareaElement.style.width = getComputedStyle(this.codeElement).width;
446
+ }
447
+ }
448
+
449
+ /**
450
+ * 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.
451
+ * @param {string} instructions The instructions to display only if keyboard navigation is being used. If it's blank, no instructions will be shown.
452
+ * @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.
453
+ */
454
+ setKeyboardNavInstructions(instructions, includeAriaDescriptionFirst) {
455
+ this.dialogContainerElement.querySelector(".code-input_keyboard-navigation-instructions").innerText = instructions;
456
+ if(includeAriaDescriptionFirst) {
457
+ this.textareaElement.setAttribute("aria-description", this.originalAriaDescription + ". " + instructions);
458
+ } else {
459
+ this.textareaElement.setAttribute("aria-description", instructions);
460
+ }
461
+ }
462
+
463
+ /**
464
+ * HTML-escape an arbitrary string.
465
+ * @param {string} text - The original, unescaped text
466
+ * @returns {string} - The new, HTML-escaped text
467
+ */
468
+ escapeHtml(text) {
469
+ return text.replace(new RegExp("&", "g"), "&amp;").replace(new RegExp("<", "g"), "&lt;"); /* Global RegExp */
470
+ }
471
+
472
+ /**
473
+ * HTML-unescape an arbitrary string.
474
+ * @param {string} text - The original, HTML-escaped text
475
+ * @returns {string} - The new, unescaped text
476
+ */
477
+ unescapeHtml(text) {
478
+ return text.replace(new RegExp("&amp;", "g"), "&").replace(new RegExp("&lt;", "g"), "<").replace(new RegExp("&gt;", "g"), ">"); /* Global RegExp */
479
+ }
480
+
481
+ /**
482
+ * Get the template object this code-input element is using.
483
+ * @returns {Object} - Template object
484
+ */
485
+ getTemplate() {
486
+ let templateName;
487
+ if (this.getAttribute("template") == undefined) {
488
+ // Default
489
+ templateName = codeInput.defaultTemplate;
490
+ } else {
491
+ templateName = this.getAttribute("template");
492
+ }
493
+ if (templateName in codeInput.usedTemplates) {
494
+ return codeInput.usedTemplates[templateName];
495
+ } else {
496
+ // Doesn't exist - add to queue
497
+ if (!(templateName in codeInput.templateNotYetRegisteredQueue)) {
498
+ codeInput.templateNotYetRegisteredQueue[templateName] = [];
499
+ }
500
+ codeInput.templateNotYetRegisteredQueue[templateName].push(this);
501
+ return undefined;
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Set up and initialise the textarea.
507
+ * This will be called once the template has been added.
508
+ */
509
+ setup() {
510
+ if(this.textareaElement != null) return; // Already set up
511
+
512
+ this.mutationObserver = new MutationObserver(this.mutationObserverCallback.bind(this));
513
+ this.mutationObserver.observe(this, {
514
+ attributes: true,
515
+ attributeOldValue: true
516
+ });
517
+
518
+ this.classList.add("code-input_registered"); // Remove register message
519
+ if (this.templateObject.preElementStyled) this.classList.add("code-input_pre-element-styled");
520
+
521
+ this.pluginEvt("beforeElementsAdded");
522
+
523
+ const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
524
+ let value;
525
+ if(fallbackTextarea) {
526
+ // Fallback textarea exists
527
+ // Sync attributes; existing code-input attributes take priority
528
+ let textareaAttributeNames = fallbackTextarea.getAttributeNames();
529
+ for(let i = 0; i < textareaAttributeNames.length; i++) {
530
+ const attr = textareaAttributeNames[i];
531
+ if(!this.hasAttribute(attr)) {
532
+ this.setAttribute(attr, fallbackTextarea.getAttribute(attr));
533
+ }
534
+ }
535
+ // Sync value
536
+ value = fallbackTextarea.value;
537
+ } else {
538
+ value = this.unescapeHtml(this.innerHTML);
539
+ }
540
+ value = value || this.getAttribute("value") || "";
541
+
542
+ // First-time attribute sync
543
+ const lang = this.getAttribute("language") || this.getAttribute("lang");
544
+ const placeholder = this.getAttribute("placeholder") || lang || "";
545
+
546
+
547
+ this.initialValue = value; // For form reset
548
+
549
+ // Create textarea
550
+ const textarea = document.createElement("textarea");
551
+ textarea.placeholder = placeholder;
552
+ if(value != "") {
553
+ textarea.value = value;
554
+ }
555
+ textarea.innerHTML = this.innerHTML;
556
+ textarea.setAttribute("spellcheck", "false");
557
+
558
+ // Disable focusing on the code-input element - only allow the textarea to be focusable
559
+ textarea.setAttribute("tabindex", this.getAttribute("tabindex") || 0);
560
+ this.setAttribute("tabindex", -1);
561
+ // Save aria-description so keyboard navigation guidance can be added.
562
+ this.originalAriaDescription = this.getAttribute("aria-description") || "Code input field";
563
+
564
+ // Accessibility - detect when mouse focus to remove focus outline + keyboard navigation guidance that could irritate users.
565
+ this.addEventListener("mousedown", () => {
566
+ this.classList.add("code-input_mouse-focused");
567
+ });
568
+ textarea.addEventListener("blur", () => {
569
+ this.classList.remove("code-input_mouse-focused");
570
+ });
571
+
572
+ this.innerHTML = ""; // Clear Content
573
+
574
+ // Synchronise attributes to textarea
575
+ for(let i = 0; i < this.attributes.length; i++) {
576
+ let attribute = this.attributes[i].name;
577
+ if (codeInput.textareaSyncAttributes.includes(attribute) || attribute.substring(0, 5) == "aria-") {
578
+ textarea.setAttribute(attribute, this.getAttribute(attribute));
579
+ }
580
+ }
581
+
582
+ textarea.addEventListener('input', (evt) => { this.value = this.textareaElement.value; });
583
+
584
+ // Save element internally
585
+ this.textareaElement = textarea;
586
+ this.append(textarea);
587
+
588
+ // Create result element
589
+ let code = document.createElement("code");
590
+ let pre = document.createElement("pre");
591
+ pre.setAttribute("aria-hidden", "true"); // Hide for screen readers
592
+ pre.setAttribute("tabindex", "-1"); // Hide for keyboard navigation
593
+ pre.setAttribute("inert", true); // Hide for keyboard navigation
594
+
595
+ // Save elements internally
596
+ this.preElement = pre;
597
+ this.codeElement = code;
598
+ pre.append(code);
599
+ this.append(pre);
600
+
601
+ if (this.templateObject.isCode) {
602
+ if (lang != undefined && lang != "") {
603
+ code.classList.add("language-" + lang.toLowerCase());
604
+ }
605
+ }
606
+
607
+ // dialogContainerElement used to store non-scrolling dialog boxes, etc.
608
+ let dialogContainerElement = document.createElement("div");
609
+ dialogContainerElement.classList.add("code-input_dialog-container");
610
+ this.append(dialogContainerElement);
611
+ this.dialogContainerElement = dialogContainerElement;
612
+
613
+ let keyboardNavigationInstructions = document.createElement("div");
614
+ keyboardNavigationInstructions.classList.add("code-input_keyboard-navigation-instructions");
615
+ dialogContainerElement.append(keyboardNavigationInstructions);
616
+
617
+ this.pluginEvt("afterElementsAdded");
618
+
619
+ this.dispatchEvent(new CustomEvent("code-input_load"));
620
+
621
+ this.value = value;
622
+ this.animateFrame();
623
+
624
+ const resizeObserver = new ResizeObserver((elements) => {
625
+ // The only element that could be resized is this code-input element.
626
+ this.syncSize();
627
+ });
628
+ resizeObserver.observe(this);
629
+ }
630
+
631
+ /**
632
+ * @deprecated This shouldn't have been accessed as part of the library's public interface (to enable more flexibility in backwards-compatible versions), but is still here just in case it was.
633
+ */
634
+ escape_html(text) {
635
+ return this.escapeHtml(text);
636
+ }
637
+
638
+ /**
639
+ * @deprecated This shouldn't have been accessed as part of the library's public interface (to enable more flexibility in backwards-compatible versions), but is still here just in case it was.
640
+ */
641
+ get_template() {
642
+ return this.getTemplate();
643
+ }
644
+
645
+ /**
646
+ * @deprecated Present for backwards compatibility; use CodeInput.templateObject.
647
+ */
648
+ get template() {
649
+ return this.templateObject;
650
+ }
651
+
652
+ /**
653
+ * @deprecated The Vue framework may try to set the template
654
+ * property to the value of the template attribute, a string.
655
+ * This should not happen. Intentional use of this should
656
+ * also not happen since templates are changed by changing
657
+ * the template attribute to the name of one registered.
658
+ */
659
+ set template(value) { }
660
+
661
+ /* ------------------------------------
662
+ * -----------Callbacks----------------
663
+ * ------------------------------------
664
+ * Implement the `HTMLElement` callbacks
665
+ * to trigger the main functionality properly. */
666
+
667
+ /**
668
+ * When the code-input element has been added to the document,
669
+ * find its template and set up the element.
670
+ */
671
+ connectedCallback() {
672
+ // Stored in templateObject because some frameworks will override
673
+ // template property with the string value of the attribute
674
+ this.templateObject = this.getTemplate();
675
+ if (this.templateObject != undefined) {
676
+ this.classList.add("code-input_registered");
677
+ this.setup();
678
+ this.classList.add("code-input_loaded");
679
+ }
680
+ }
681
+
682
+ mutationObserverCallback(mutationList, observer) {
683
+ for (const mutation of mutationList) {
684
+ if (mutation.type !== 'attributes')
685
+ continue;
686
+
687
+ /* Check regular attributes */
688
+ for(let i = 0; i < codeInput.observedAttributes.length; i++) {
689
+ if (mutation.attributeName == codeInput.observedAttributes[i]) {
690
+ return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
691
+ }
692
+ }
693
+ if (mutation.attributeName.substring(0, 5) == "aria-") {
694
+ return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName));
695
+ }
696
+ }
697
+ }
698
+
699
+ disconnectedCallback() {
700
+ this.mutationObserver.disconnect();
701
+ }
702
+
703
+ /**
704
+ * Triggered when an observed HTML attribute
705
+ * has been modified (called from `mutationObserverCallback`).
706
+ * @param {string} name - The name of the attribute
707
+ * @param {string} oldValue - The value of the attribute before it was changed
708
+ * @param {string} newValue - The value of the attribute after it is changed
709
+ */
710
+ attributeChangedCallback(name, oldValue, newValue) {
711
+ if (this.isConnected) {
712
+ this.pluginEvt("attributeChanged", [name, oldValue, newValue]);
713
+ switch (name) {
714
+
715
+ case "value":
716
+ this.value = newValue;
717
+ break;
718
+ case "template":
719
+ this.templateObject = codeInput.usedTemplates[newValue || codeInput.defaultTemplate];
720
+ if (this.templateObject.preElementStyled) this.classList.add("code-input_pre-element-styled");
721
+ else this.classList.remove("code-input_pre-element-styled");
722
+ // Syntax Highlight
723
+ this.scheduleHighlight();
724
+
725
+ break;
726
+
727
+ case "lang":
728
+ case "language":
729
+ let code = this.codeElement;
730
+ let mainTextarea = this.textareaElement;
731
+
732
+ // Check not already updated
733
+ if (newValue != null) {
734
+ newValue = newValue.toLowerCase();
735
+
736
+ if (code.classList.contains(`language-${newValue}`)) break; // Already updated
737
+ }
738
+
739
+ if(oldValue !== null) {
740
+ // Case insensitive
741
+ oldValue = oldValue.toLowerCase();
742
+
743
+ // Remove old language class and add new
744
+ code.classList.remove("language-" + oldValue); // From codeElement
745
+ code.parentElement.classList.remove("language-" + oldValue); // From preElement
746
+ }
747
+ // Add new language class
748
+ code.classList.remove("language-none"); // Prism
749
+ code.parentElement.classList.remove("language-none"); // Prism
750
+
751
+ if (newValue != undefined && newValue != "") {
752
+ code.classList.add("language-" + newValue);
753
+ }
754
+
755
+ if (mainTextarea.placeholder == oldValue) mainTextarea.placeholder = newValue;
756
+
757
+ this.scheduleHighlight();
758
+
759
+ break;
760
+ default:
761
+ if (codeInput.textareaSyncAttributes.includes(name) || name.substring(0, 5) == "aria-") {
762
+ if(newValue == null || newValue == undefined) {
763
+ this.textareaElement.removeAttribute(name);
764
+ } else {
765
+ this.textareaElement.setAttribute(name, newValue);
766
+ }
767
+ } else {
768
+ codeInput.textareaSyncAttributes.regexp.forEach((attribute) => {
769
+ if (name.match(attribute)) {
770
+ if(newValue == null) {
771
+ this.textareaElement.removeAttribute(name);
772
+ } else {
773
+ this.textareaElement.setAttribute(name, newValue);
774
+ }
775
+ }
776
+ });
777
+ }
778
+ break;
779
+ }
780
+ }
781
+
782
+ }
783
+
784
+ /* ------------------------------------
785
+ * -----------Overrides----------------
786
+ * ------------------------------------
787
+ * Override/Implement ordinary HTML textarea functionality so that the <code-input>
788
+ * element acts just like a <textarea>. */
789
+
790
+ /**
791
+ * @override
792
+ */
793
+ addEventListener(type, listener, options = undefined) {
794
+ // Save a copy of the callback where `this` refers to the code-input element.
795
+ let boundCallback = function (evt) {
796
+ if (typeof listener === 'function') {
797
+ listener(evt);
798
+ } else if (listener && listener.handleEvent) {
799
+ listener.handleEvent(evt);
800
+ }
801
+ }.bind(this);
802
+ this.boundEventCallbacks[listener] = boundCallback;
803
+
804
+ if (codeInput.textareaSyncEvents.includes(type)) {
805
+ // Synchronise with textarea
806
+ this.boundEventCallbacks[listener] = boundCallback;
807
+
808
+ if (options === undefined) {
809
+ if(this.textareaElement == null) {
810
+ this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback); });
811
+ } else {
812
+ this.textareaElement.addEventListener(type, boundCallback);
813
+ }
814
+ } else {
815
+ if(this.textareaElement == null) {
816
+ this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback, options); });
817
+ } else {
818
+ this.textareaElement.addEventListener(type, boundCallback, options);
819
+ }
820
+ }
821
+ } else {
822
+ // Synchronise with code-input element
823
+ if (options === undefined) {
824
+ super.addEventListener(type, boundCallback);
825
+ } else {
826
+ super.addEventListener(type, boundCallback, options);
827
+ }
828
+ }
829
+ }
830
+
831
+ /**
832
+ * @override
833
+ */
834
+ removeEventListener(type, listener, options = undefined) {
835
+ // Save a copy of the callback where `this` refers to the code-input element
836
+ let boundCallback = this.boundEventCallbacks[listener];
837
+
838
+ if (codeInput.textareaSyncEvents.includes(type)) {
839
+ // Synchronise with textarea
840
+ if (options === undefined) {
841
+ if(this.textareaElement == null) {
842
+ this.addEventListener("code-input_load", () => { this.textareaElement.removeEventListener(type, boundCallback); });
843
+ } else {
844
+ this.textareaElement.removeEventListener(type, boundCallback);
845
+ }
846
+ } else {
847
+ if(this.textareaElement == null) {
848
+ this.addEventListener("code-input_load", () => { this.textareaElement.removeEventListener(type, boundCallback, options); });
849
+ } else {
850
+ this.textareaElement.removeEventListener(type, boundCallback, options);
851
+ }
852
+ }
853
+ } else {
854
+ // Synchronise with code-input element
855
+ if (options === undefined) {
856
+ super.removeEventListener(type, boundCallback);
857
+ } else {
858
+ super.removeEventListener(type, boundCallback, options);
859
+ }
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Get the text contents of the code-input element.
865
+ */
866
+ get value() {
867
+ if(this.textareaElement) {
868
+ // Get from editable textarea element
869
+ return this.textareaElement.value;
870
+ } else {
871
+ // Unregistered
872
+ const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
873
+ if(fallbackTextarea) {
874
+ return fallbackTextarea.value;
875
+ } else {
876
+ return this.innerHTML;
877
+ }
878
+ }
879
+ }
880
+ /**
881
+ * Set the text contents of the code-input element.
882
+ * @param {string} val - New text contents
883
+ */
884
+ set value(val) {
885
+ if (val === null || val === undefined) {
886
+ val = "";
887
+ }
888
+ if(this.textareaElement) {
889
+ // Save in editable textarea element
890
+ this.textareaElement.value = val;
891
+ // Trigger highlight
892
+ this.scheduleHighlight();
893
+ } else {
894
+ // Unregistered
895
+ const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]");
896
+ if(fallbackTextarea) {
897
+ fallbackTextarea.value = val;
898
+ } else {
899
+ this.innerHTML = val;
900
+ }
901
+ }
902
+ }
903
+
904
+ // Synchronise blur and focus
905
+ focus(options={}) {
906
+ return this.textareaElement.focus(options);
907
+ }
908
+ blur(options={}) {
909
+ return this.textareaElement.blur(options);
910
+ }
911
+
912
+ /**
913
+ * Get the placeholder of the code-input element that appears
914
+ * when no code has been entered.
915
+ */
916
+ get placeholder() {
917
+ return this.getAttribute("placeholder");
918
+ }
919
+ /**
920
+ * Set the placeholder of the code-input element that appears
921
+ * when no code has been entered.
922
+ * @param {string} val - New placeholder
923
+ */
924
+ set placeholder(val) {
925
+ return this.setAttribute("placeholder", val);
926
+ }
927
+
928
+ /**
929
+ * Returns a ValidityState object that represents the validity states of an element.
930
+ *
931
+ * See `HTMLTextAreaElement.validity`
932
+ */
933
+ get validity() {
934
+ return this.textareaElement.validity;
935
+ }
936
+
937
+ /**
938
+ * Returns the error message that would be displayed if the user submits the form, or an empty string if no error message.
939
+ * It also triggers the standard error message, such as "this is a required field". The result is that the user sees validation
940
+ * messages without actually submitting.
941
+ *
942
+ * See `HTMLTextAreaElement.validationMessage`
943
+ */
944
+ get validationMessage() {
945
+ return this.textareaElement.validationMessage;
946
+ }
947
+
948
+ /**
949
+ * Sets a custom error message that is displayed when a form is submitted.
950
+ *
951
+ * See `HTMLTextAreaElement.setCustomValidity`
952
+ * @param error Sets a custom error message that is displayed when a form is submitted.
953
+ */
954
+ setCustomValidity(error) {
955
+ return this.textareaElement.setCustomValidity(error);
956
+ }
957
+
958
+ /**
959
+ * Returns whether a form will validate when it is submitted,
960
+ * without having to submit it.
961
+ *
962
+ * See `HTMLTextAreaElement.checkValidity`
963
+ */
964
+ checkValidity() {
965
+ return this.textareaElement.checkValidity();
966
+ }
967
+
968
+ /**
969
+ * See `HTMLTextAreaElement.reportValidity`
970
+ */
971
+ reportValidity() {
972
+ return this.textareaElement.reportValidity();
973
+ }
974
+
975
+ /**
976
+ * Allows plugins to store data in the scope of a single element.
977
+ * Key - name of the plugin, in camelCase
978
+ * Value - object of data to be stored; different plugins may use this differently.
979
+ */
980
+ pluginData = {};
981
+
982
+ /**
983
+ * Update value on form reset
984
+ */
985
+ formResetCallback() {
986
+ this.value = this.initialValue;
987
+ };
988
+ }
989
+ }
990
+
991
+
992
+ customElements.define("code-input", codeInput.CodeInput);
993
+ export const Plugin = codeInput.Plugin;
994
+ export const Template = codeInput.Template;
995
+ export const CodeInput = codeInput.CodeInput;
996
+ export const registerTemplate = codeInput.registerTemplate;
997
+ export default { Plugin, Template, CodeInput, registerTemplate };