From 2c6f35433e4eb86b80cd7e7064c9d7695b05ec3f Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Fri, 20 Jun 2025 17:51:27 +0100 Subject: [PATCH 01/38] Remove janky blur/focus - This was a "fix" at the wrong level (by me), and caused code-input's aim of acting like a textarea to be violated. - TODO: See why this was added in the first place (I did it by my own will, but it's unclear why now) before merging --- code-input.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/code-input.js b/code-input.js index 7bace3b..bba9362 100644 --- a/code-input.js +++ b/code-input.js @@ -482,7 +482,6 @@ var codeInput = { * to syntax-highlight it. */ needsHighlight = false; // Just inputted - handleEventsFromTextarea = true; // Turn to false when unusual internal events are called on the textarea originalAriaDescription; /** @@ -523,14 +522,6 @@ var codeInput = { this.syncSize(); - // If editing here, scroll to the caret by focusing, though this shouldn't count as a focus event - if(this.textareaElement === document.activeElement) { - this.handleEventsFromTextarea = false; - this.textareaElement.blur(); - this.textareaElement.focus(); - this.handleEventsFromTextarea = true; - } - this.pluginEvt("afterHighlight"); } @@ -646,9 +637,7 @@ var codeInput = { this.classList.add("code-input_mouse-focused"); }); textarea.addEventListener("blur", () => { - if(this.handleEventsFromTextarea) { - this.classList.remove("code-input_mouse-focused"); - } + this.classList.remove("code-input_mouse-focused"); }); this.innerHTML = ""; // Clear Content @@ -874,22 +863,20 @@ var codeInput = { this.boundEventCallbacks[listener] = boundCallback; if (codeInput.textareaSyncEvents.includes(type)) { - // Synchronise with textarea, only when handleEventsFromTextarea is true - // This callback is modified to only run when the handleEventsFromTextarea is set. - let conditionalBoundCallback = function(evt) { if(this.handleEventsFromTextarea) boundCallback(evt); }.bind(this); - this.boundEventCallbacks[listener] = conditionalBoundCallback; + // Synchronise with textarea + this.boundEventCallbacks[listener] = boundCallback; if (options === undefined) { if(this.textareaElement == null) { this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback); }); } else { - this.textareaElement.addEventListener(type, conditionalBoundCallback); + this.textareaElement.addEventListener(type, boundCallback); } } else { if(this.textareaElement == null) { this.addEventListener("code-input_load", () => { this.textareaElement.addEventListener(type, boundCallback, options); }); } else { - this.textareaElement.addEventListener(type, conditionalBoundCallback, options); + this.textareaElement.addEventListener(type, boundCallback, options); } } } else { @@ -949,10 +936,12 @@ var codeInput = { if (val === null || val === undefined) { val = ""; } + // Save in editable textarea element this.textareaElement.value = val; // Trigger highlight this.scheduleHighlight(); + return val; } From 11c3227cb344733c29453efaef69f1861a935a2b Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Fri, 27 Jun 2025 17:46:54 +0100 Subject: [PATCH 02/38] Fix infinite recursion (bug unfixed - see #131) --- plugins/auto-close-brackets.js | 12 ++++++++++-- plugins/indent.js | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/auto-close-brackets.js b/plugins/auto-close-brackets.js index a30a813..77ce6de 100644 --- a/plugins/auto-close-brackets.js +++ b/plugins/auto-close-brackets.js @@ -19,13 +19,15 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { /* Add keystroke events */ afterElementsAdded(codeInput) { - codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) }); + codeInput.pluginData.autoCloseBrackets = { automatedKeypresses: false}; + codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event); }); codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); }); } /* Deal with the automatic creation of closing bracket when opening brackets are typed, and the ability to "retype" a closing bracket where one has already been placed. */ checkBrackets(codeInput, event) { + if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return; if(event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) { // Check if a closing bracket is typed for(let openingBracket in this.bracketPairs) { @@ -41,7 +43,12 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { // Opening bracket typed; Create bracket pair let closingBracket = this.bracketPairs[event.data]; // Insert the closing bracket + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.autoCloseBrackets.automatedKeypresses = true; document.execCommand("insertText", false, closingBracket); + codeInput.pluginData.autoCloseBrackets.automatedKeypresses = false; // Move caret before the inserted closing bracket codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1; } @@ -49,6 +56,7 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { /* Deal with cases where a backspace deleting an opening bracket deletes the closing bracket straight after it as well */ checkBackspace(codeInput, event) { + if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return; if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) { let closingBracket = this.bracketPairs[codeInput.textareaElement.value[codeInput.textareaElement.selectionStart-1]]; if(closingBracket != undefined && codeInput.textareaElement.value[codeInput.textareaElement.selectionStart] == closingBracket) { @@ -58,4 +66,4 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { } } } -} \ No newline at end of file +} diff --git a/plugins/indent.js b/plugins/indent.js index 7ddc2fe..5031e9a 100644 --- a/plugins/indent.js +++ b/plugins/indent.js @@ -81,11 +81,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { let indentationWidthPx = testIndentationWidthSpan.offsetWidth; codeInput.removeChild(testIndentationWidthPre); - codeInput.pluginData.indent = {indentationWidthPx: indentationWidthPx}; + codeInput.pluginData.indent = {automatedKeypresses: false, indentationWidthPx: indentationWidthPx}; } /* Deal with the Tab key causing indentation, and Tab+Selection indenting / Shift+Tab+Selection unindenting lines, and the mechanism through which Tab can be used to switch focus instead (accessibility). */ checkTab(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(!this.tabIndentationEnabled) return; if(this.escTabToChangeFocus) { // Accessibility - allow Tab for keyboard navigation when Esc pressed right before it. @@ -116,7 +117,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { if(!event.shiftKey && inputElement.selectionStart == inputElement.selectionEnd) { // Just place a tab/spaces here. + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.automatedKeypresses = true; document.execCommand("insertText", false, this.indentation); + codeInput.pluginData.indent.automatedKeypresses = false; } else { let lines = inputElement.value.split("\n"); @@ -147,7 +153,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { // Add tab at start inputElement.selectionStart = letterI; inputElement.selectionEnd = letterI; + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.f = true; document.execCommand("insertText", false, this.indentation); + codeInput.pluginData.indent.automatedKeypresses = false; // Change selection if(selectionStartI > letterI) { // Indented outside selection @@ -191,6 +202,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { /* Deal with new lines retaining indentation */ checkEnter(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(event.key != "Enter") { return; } @@ -270,11 +282,16 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { // save the current cursor position let selectionStartI = inputElement.selectionStart; + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.automatedKeypresses = true; if(bracketThreeLinesTriggered) { document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line numberIndents += 1; // Reflects the new indent } document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation + codeInput.pluginData.indent.automatedKeypresses = false; // move cursor to new position inputElement.selectionStart = selectionStartI + numberIndents*this.indentationNumChars + 1; // count the indent level and the newline character @@ -294,6 +311,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { /* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */ checkBackspace(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(event.key != "Backspace" || this.indentationNumChars == 1) { return; // Normal backspace when indentation of 1 } From d53c19bf11d33c12204524ebad447e6be78a7117 Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Wed, 9 Jul 2025 18:40:36 +0100 Subject: [PATCH 03/38] Use classes for templates; keep but deprecate use of function template creators --- code-input.d.ts | 34 ++++++++++++++----- code-input.js | 88 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/code-input.d.ts b/code-input.d.ts index 285caa4..284b2c2 100644 --- a/code-input.d.ts +++ b/code-input.d.ts @@ -345,17 +345,35 @@ export class Template { */ export namespace templates { /** - * Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/) - * @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter. - * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` - * @returns template object + * A template that uses Prism.js syntax highlighting (https://prismjs.com/). + */ + class Prism extends Template { + /** + * Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/) + * @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter. + * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` + * @returns template object + */ + constructor(prism: Object, plugins?: Plugin[]) + } + /** + * @deprecated Please use `new codeInput.templates.Prism(...)` */ function prism(prism: Object, plugins?: Plugin[]): Template /** - * Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/) - * @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter. - * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` - * @returns template object + * A template that uses highlight.js syntax highlighting (https://highlightjs.org/). + */ + class Hljs extends Template { + /** + * Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/) + * @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter. + * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` + * @returns template object + */ + constructor(hljs: Object, plugins?: Plugin[]) + } + /** + * @deprecated Please use `new codeInput.templates.Hljs(...)` */ function hljs(hljs: Object, plugins?: Plugin[]): Template /** diff --git a/code-input.js b/code-input.js index e724927..a9fc914 100644 --- a/code-input.js +++ b/code-input.js @@ -222,38 +222,19 @@ var codeInput = { * For adding small pieces of functionality, please see `codeInput.plugins`. */ templates: { + // (Source code for class templates after var codeInput = ... so they can extend the codeInput.Template class) /** - * Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/) - * @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter. - * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` - * @returns {codeInput.Template} template object + * @deprecated Please use `new codeInput.templates.Prism(...)` */ prism(prism, plugins = []) { // Dependency: Prism.js (https://prismjs.com/) - return new codeInput.Template( - prism.highlightElement, // highlight - true, // preElementStyled - true, // isCode - false, // includeCodeInputInHighlightFunc - plugins - ); + return new codeInput.templates.Prism(prism, plugins); }, + /** - * Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/) - * @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter. - * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` - * @returns {codeInput.Template} template object + * @deprecated Please use `new codeInput.templates.Hljs(...)` */ hljs(hljs, plugins = []) { // Dependency: Highlight.js (https://highlightjs.org/) - return new codeInput.Template( - function(codeElement) { - codeElement.removeAttribute("data-highlighted"); - hljs.highlightElement(codeElement); - }, // highlight - false, // preElementStyled - true, // isCode - false, // includeCodeInputInHighlightFunc - plugins - ); + return new codeInput.templates.Hljs(hljs, plugins); }, /** @@ -318,7 +299,7 @@ var codeInput = { }, /** - * @deprecated Please use `new codeInput.Template()` + * @deprecated Please use `new codeInput.Template(...)` */ custom(highlight = function () { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = []) { return { @@ -1058,4 +1039,59 @@ var codeInput = { } } +{ + // Templates are defined here after the codeInput variable is set, because they reference it by extending codeInput.Template. + + // ESM-SUPPORT-START-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation! + /** + * A template that uses Prism.js syntax highlighting (https://prismjs.com/). + */ + class Prism extends codeInput.Template { // Dependency: Prism.js (https://prismjs.com/) + /** + * Constructor to create a template that uses Prism.js syntax highlighting (https://prismjs.com/) + * @param {Object} prism Import Prism.js, then after that import pass the `Prism` object as this parameter. + * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` + * @returns {codeInput.Template} template object + */ + constructor(prism, plugins = []) { + super( + prism.highlightElement, // highlight + true, // preElementStyled + true, // isCode + false, // includeCodeInputInHighlightFunc + plugins + ); + } + }; + // ESM-SUPPORT-END-TEMPLATE-prism Do not (re)move this - it's needed for ESM generation! + codeInput.templates.Prism = Prism; + + // ESM-SUPPORT-START-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation! + /** + * A template that uses highlight.js syntax highlighting (https://highlightjs.org/). + */ + class Hljs extends codeInput.Template { // Dependency: Highlight.js (https://highlightjs.org/) + /** + * Constructor to create a template that uses highlight.js syntax highlighting (https://highlightjs.org/) + * @param {Object} hljs Import highlight.js, then after that import pass the `hljs` object as this parameter. + * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.plugins` + * @returns {codeInput.Template} template object + */ + constructor(hljs, plugins = []) { + super( + function(codeElement) { + codeElement.removeAttribute("data-highlighted"); + hljs.highlightElement(codeElement); + }, // highlight + false, // preElementStyled + true, // isCode + false, // includeCodeInputInHighlightFunc + plugins + ); + } + }; + // ESM-SUPPORT-END-TEMPLATE-hljs Do not (re)move this - it's needed for ESM generation! + codeInput.templates.Hljs = Hljs; +} + customElements.define("code-input", codeInput.CodeInput); From fe408d3cb488a8712d4a602101857587d12af38a Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Fri, 11 Jul 2025 13:45:29 +0100 Subject: [PATCH 04/38] Use classes for templates in README and tests --- README.md | 6 +++--- tests/tester.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f336e9a..2607e46 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,12 @@ The next step is to set up a `template` to link `code-input` to your syntax-high - *Highlight.js:* ```js - codeInput.registerTemplate("syntax-highlighted", codeInput.templates.hljs(hljs, [] /* Array of plugins (see below) */)); + codeInput.registerTemplate("syntax-highlighted", new codeInput.templates.Hljs(hljs, [] /* Array of plugins (see below) */)); ``` - *Prism.js:* ```js - codeInput.registerTemplate("syntax-highlighted", codeInput.templates.prism(Prism, [] /* Array of plugins (see below) */)); + codeInput.registerTemplate("syntax-highlighted", new codeInput.templates.Prism(Prism, [] /* Array of plugins (see below) */)); ``` - *Custom:* @@ -106,7 +106,7 @@ The next step is to set up a `template` to link `code-input` to your syntax-high + From 57b4b1651b741c6a0428be5af184661516c22e16 Mon Sep 17 00:00:00 2001 From: WebCoder49 Date: Mon, 28 Jul 2025 17:45:38 +0000 Subject: [PATCH 36/38] Auto Minified JS and CSS files --- code-input.min.css | 2 +- plugins/prism-line-numbers.min.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code-input.min.css b/code-input.min.css index 3f99ec1..612ea66 100644 --- a/code-input.min.css +++ b/code-input.min.css @@ -1 +1 @@ -code-input{display:block;overflow-y:auto;overflow-x:auto;position:relative;top:0;left:0;margin:8px;--padding:16px;height:250px;font-size:inherit;font-family:monospace;line-height:1.5;tab-size:2;caret-color:#a9a9a9;white-space:pre;padding:0!important;display:grid;grid-template-columns:100%;grid-template-rows:100%}code-input:not(.code-input_loaded){padding:var(--padding,16px)!important}code-input textarea,code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{margin:0!important;padding:var(--padding,16px)!important;border:0;min-width:calc(100% - var(--padding) * 2);min-height:calc(100% - var(--padding) * 2);box-sizing:content-box;overflow:hidden;resize:none;grid-row:1;grid-column:1;display:block}code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{height:max-content;width:max-content}code-input.code-input_pre-element-styled pre code,code-input:not(.code-input_pre-element-styled) pre{margin:0!important;padding:0!important;width:100%;height:100%}code-input pre,code-input pre *,code-input textarea{font-size:inherit!important;font-family:inherit!important;line-height:inherit!important;tab-size:inherit!important;text-align:inherit!important}code-input textarea[dir=auto]+pre{unicode-bidi:plaintext}code-input textarea[dir=ltr]+pre{direction:ltr}code-input textarea[dir=rtl]+pre{direction:rtl}code-input pre,code-input textarea{grid-column:1;grid-row:1}code-input textarea{z-index:1}code-input pre{z-index:0}code-input textarea{color:transparent;background:0 0;caret-color:inherit!important}code-input textarea::placeholder{color:#d3d3d3}code-input pre,code-input textarea{white-space:inherit;word-spacing:normal;word-break:normal;word-wrap:normal}code-input textarea{resize:none;outline:0!important}code-input:has(textarea:focus):not(.code-input_mouse-focused){outline:2px solid #000}code-input:not(.code-input_registered){overflow:hidden;display:block;box-sizing:border-box}code-input:not(.code-input_registered)::after{content:"Use codeInput.registerTemplate to set up.";display:block;position:absolute;bottom:var(--padding);left:var(--padding);width:calc(100% - 2 * var(--padding));border-top:1px solid grey;outline:var(--padding) solid #fff;background-color:#fff}code-input:not(.code-input_loaded) pre,code-input:not(.code-input_loaded) textarea{opacity:0}code-input .code-input_dialog-container{z-index:2;position:sticky;grid-row:1;grid-column:1;top:0;left:0;margin:0;padding:0;width:100%;height:0;text-align:inherit}[dir=rtl] code-input .code-input_dialog-container,code-input[dir=rtl] .code-input_dialog-container{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions{top:0;left:0;display:block;position:absolute;background-color:#000;color:#fff;padding:2px;padding-left:10px;margin:0;text-wrap:balance;overflow:hidden;text-overflow:ellipsis;width:calc(100% - 12px);max-height:3em}code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-navigation-instructions{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions:empty,code-input.code-input_mouse-focused .code-input_dialog-container .code-input_keyboard-navigation-instructions,code-input:not(:has(textarea:focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions{display:none}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code{padding-top:calc(var(--padding) + 3em)!important} \ No newline at end of file +code-input{display:block;overflow-y:auto;overflow-x:auto;position:relative;top:0;left:0;margin:8px;--padding:16px;height:250px;font-size:inherit;font-family:monospace;line-height:1.5;tab-size:2;caret-color:#a9a9a9;white-space:pre;padding:0!important;display:grid;grid-template-columns:100%;grid-template-rows:100%}code-input:not(.code-input_loaded){padding:var(--padding,16px)!important}code-input textarea,code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{margin:0!important;padding:var(--padding,16px)!important;border:0;min-width:calc(100% - var(--padding,16px) * 2);min-height:calc(100% - var(--padding,16px) * 2);box-sizing:content-box;overflow:hidden;resize:none;grid-row:1;grid-column:1;display:block}code-input.code-input_pre-element-styled pre,code-input:not(.code-input_pre-element-styled) pre code{height:max-content;width:max-content}code-input.code-input_pre-element-styled pre code,code-input:not(.code-input_pre-element-styled) pre{margin:0!important;padding:0!important}code-input pre,code-input pre *,code-input textarea{font-size:inherit!important;font-family:inherit!important;line-height:inherit!important;tab-size:inherit!important;text-align:inherit!important}code-input textarea[dir=auto]+pre{unicode-bidi:plaintext}code-input textarea[dir=ltr]+pre{direction:ltr}code-input textarea[dir=rtl]+pre{direction:rtl}code-input pre,code-input textarea{grid-column:1;grid-row:1}code-input textarea{z-index:1}code-input pre{z-index:0}code-input textarea{color:transparent;background:0 0;caret-color:inherit!important}code-input textarea::placeholder{color:#d3d3d3}code-input pre,code-input textarea{white-space:inherit;word-spacing:normal;word-break:normal;word-wrap:normal}code-input textarea{resize:none;outline:0!important}code-input:has(textarea:focus):not(.code-input_mouse-focused){outline:2px solid #000}code-input:not(.code-input_registered){overflow:hidden;display:block;box-sizing:border-box}code-input:not(.code-input_registered)::after{content:"Use codeInput.registerTemplate to set up.";display:block;position:absolute;bottom:var(--padding,16px);left:var(--padding,16px);width:calc(100% - 2 * var(--padding,16px));border-top:1px solid grey;outline:var(--padding,16px) solid #fff;background-color:#fff}code-input:not(.code-input_loaded) pre,code-input:not(.code-input_loaded) textarea{opacity:0}code-input .code-input_dialog-container{z-index:2;position:sticky;grid-row:1;grid-column:1;top:0;left:0;margin:0;padding:0;height:0;width:100%;text-align:inherit}code-input.code-input_pre-element-styled .code-input_dialog-container{width:calc(100% + 2 * var(--padding,16px) - 2px)}[dir=rtl] code-input .code-input_dialog-container,code-input[dir=rtl] .code-input_dialog-container{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions{top:0;left:0;display:block;position:absolute;background-color:#000;color:#fff;padding:2px;padding-left:10px;margin:0;text-wrap:balance;overflow:hidden;text-overflow:ellipsis;width:calc(100% - 12px);max-height:3em}code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-navigation-instructions{left:unset;right:0}code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions:empty,code-input.code-input_mouse-focused .code-input_dialog-container .code-input_keyboard-navigation-instructions,code-input:not(:has(textarea:focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions{display:none}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code{padding-top:calc(var(--padding,16px) + 3em)!important}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre,code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code{min-height:calc(100% - var(--padding,16px) * 2 - 3em)} \ No newline at end of file diff --git a/plugins/prism-line-numbers.min.css b/plugins/prism-line-numbers.min.css index 015e1e4..57dcccd 100644 --- a/plugins/prism-line-numbers.min.css +++ b/plugins/prism-line-numbers.min.css @@ -1 +1 @@ -.line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-left:max(3.8em,var(--padding,16px))!important}.line-numbers code-input,code-input.line-numbers{grid-template-columns:calc(100% - max(0em,calc(3.8em - var(--padding,16px))))}.line-numbers code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions,code-input.line-numbers .code-input_dialog-container .code-input_keyboard-navigation-instructions{width:calc(100% + max(3.8em,var(--padding,16px)))!important}code-input pre[class*=language-].line-numbers>code{position:static}code-input .line-numbers .line-numbers-rows{left:0;top:var(--padding)}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) .line-numbers .line-numbers-rows{top:calc(var(--padding) + 3em)} \ No newline at end of file +.line-numbers code-input textarea,.line-numbers code-input.code-input_pre-element-styled pre,code-input.line-numbers textarea,code-input.line-numbers.code-input_pre-element-styled pre{padding-left:max(3.8em,var(--padding,16px))!important}.line-numbers code-input,code-input.line-numbers{grid-template-columns:calc(100% - max(0em,calc(3.8em - var(--padding,16px))))}code-input pre[class*=language-].line-numbers>code{position:static}code-input .line-numbers .line-numbers-rows{left:0;top:var(--padding)}code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) .line-numbers .line-numbers-rows{top:calc(var(--padding) + 3em)} \ No newline at end of file From 96a0f806c2dbed96726f4d012c0e5ccfff8b8822 Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Mon, 28 Jul 2025 21:04:22 +0100 Subject: [PATCH 37/38] Add no-JavaScript fallback (Fixes #134) --- README.md | 6 +++--- code-input.css | 51 +++++++++++++++++++++++++++++++++++------------- code-input.js | 28 +++++++++++++++++++++----- tests/hljs.html | 6 +++--- tests/i18n.html | 16 +++++++-------- tests/prism.html | 4 ++-- 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 2607e46..7dcfbbb 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,13 @@ The next step is to set up a `template` to link `code-input` to your syntax-high To see a full list of plugins and their functions, please see [plugins/README.md](./plugins/README.md). ### 4. Using the component -Now that you have registered a template, you can use the custom `` element in HTML. If you have more than one template registered, you need to add the template name as the `template` attribute. With the element, using the `language` attribute will add a `language-{value}` class to the `pre code` block. You can now use HTML attributes and events, as well as CSS styles, to make your element as simple or interactive as you like, as if it were a `textarea` element! +Now that you have registered a template, you can use the custom `` element in HTML. I recommend it surrounds a fallback ` ``` *or* ```HTML - < href='https://github.com/WebCoder49/code-input'>code-input</a> + ``` > ⚠️ At the moment, you need to set the `--padding` property rather than `padding` for a `code-input` element's CSS. All other properties should work as normal. diff --git a/code-input.css b/code-input.css index c3ac631..01505a6 100644 --- a/code-input.css +++ b/code-input.css @@ -12,6 +12,9 @@ code-input { top: 0; left: 0; + color: black; + background-color: white; + /* Normal inline styles */ margin: 8px; --padding: 16px; @@ -90,7 +93,7 @@ code-input textarea, code-input pre { /* Move the textarea in front of the result */ -code-input textarea { +code-input textarea:not([code-input-fallback]) { z-index: 1; } code-input pre { @@ -99,7 +102,7 @@ code-input pre { /* Make textarea almost completely transparent, except for caret and placeholder */ -code-input textarea { +code-input textarea:not([code-input-fallback]) { color: transparent; background: transparent; caret-color: inherit!important; /* Or choose your favourite color */ @@ -122,7 +125,7 @@ code-input textarea { outline: none!important; } code-input:has(textarea:focus):not(.code-input_mouse-focused) { - outline: 2px solid black; + outline: 2px solid currentColor; } /* Before registering give a hint about how to register. */ @@ -134,19 +137,30 @@ code-input:not(.code-input_registered) { code-input:not(.code-input_registered)::after { /* Display message to register */ - content: "Use codeInput.registerTemplate to set up."; + content: "No-JavaScript fallback. For highlighting and plugins: as a user use a newer browser/enable JavaScript support; as a developer use codeInput.registerTemplate."; display: block; position: absolute; - bottom: var(--padding, 16px); + bottom: 0; left: var(--padding, 16px); width: calc(100% - 2 * var(--padding, 16px)); + overflow-x: auto; - border-top: 1px solid grey; - outline: var(--padding, 16px) solid white; - background-color: white; + border-top: 1px solid currentColor; + outline-top: 0; + background-color: inherit; + color: inherit; + + margin: 0; + padding: 0; + height: 2em; +} + +code-input:not(.code-input_registered) textarea { + /* Don't overlap with message */ + min-height: calc(100% - var(--padding, 16px) * 2 - 2em); } -code-input:not(.code-input_loaded) pre, code-input:not(.code-input_loaded) textarea { +code-input:not(.code-input_loaded) pre, code-input:not(.code-input_loaded) textarea:not([code-input-fallback]) { opacity: 0; } @@ -203,7 +217,7 @@ code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-n right: 0; } -code-input:not(:has(textarea:focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions, +code-input:not(:has(textarea:not([code-input-fallback]):focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions, code-input.code-input_mouse-focused .code-input_dialog-container .code-input_keyboard-navigation-instructions, code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions:empty { /* When not keyboard-focused / no instructions don't show instructions */ @@ -211,11 +225,20 @@ code-input .code-input_dialog-container .code-input_keyboard-navigation-instruct } /* Things with padding when instructions are present */ -code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea, -code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code, -code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre { +code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused) textarea, +code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code, +code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre { padding-top: calc(var(--padding, 16px) + 3em)!important; } -code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused) textarea, code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code, code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre { +code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused) textarea, code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused):not(.code-input_pre-element-styled) pre code, code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre { min-height: calc(100% - var(--padding, 16px) * 2 - 3em); } + +/* No JavaScript fallback - styles to override all previous */ + +code-input textarea[code-input-fallback] { + overflow: auto; + background-color: inherit; + color: inherit; + height: max-content; +} diff --git a/code-input.js b/code-input.js index 82e92e3..4b391c8 100644 --- a/code-input.js +++ b/code-input.js @@ -605,16 +605,34 @@ var codeInput = { this.pluginEvt("beforeElementsAdded"); + const fallbackTextarea = this.querySelector("textarea[code-input-fallback]"); + let value; + if(fallbackTextarea) { + // Fallback textarea exists + // Sync attributes; existing code-input attributes take priority + let textareaAttributeNames = fallbackTextarea.getAttributeNames(); + for(let i = 0; i < textareaAttributeNames.length; i++) { + const attr = textareaAttributeNames[i]; + if(!this.hasAttribute(attr)) { + this.setAttribute(attr, fallbackTextarea.getAttribute(attr)); + } + } + // Sync value + value = fallbackTextarea.value; + } else { + value = this.unescapeHtml(this.innerHTML); + } + value = value || this.getAttribute("value") || ""; + // First-time attribute sync - let lang = this.getAttribute("language") || this.getAttribute("lang"); - let placeholder = this.getAttribute("placeholder") || this.getAttribute("language") || this.getAttribute("lang") || ""; - let value = this.unescapeHtml(this.innerHTML) || this.getAttribute("value") || ""; - // Value attribute deprecated, but included for compatibility + const lang = this.getAttribute("language") || this.getAttribute("lang"); + const placeholder = this.getAttribute("placeholder") || lang || ""; + this.initialValue = value; // For form reset // Create textarea - let textarea = document.createElement("textarea"); + const textarea = document.createElement("textarea"); textarea.placeholder = placeholder; if(value != "") { textarea.value = value; diff --git a/tests/hljs.html b/tests/hljs.html index 1aeb729..ffa1cbc 100644 --- a/tests/hljs.html +++ b/tests/hljs.html @@ -42,9 +42,9 @@

Test for Prism.js

Test Results (Click to Open)
- console.log("Hello, World!"); + @@ -52,4 +52,4 @@

Test for Prism.js

beginTest(true); - \ No newline at end of file + diff --git a/tests/i18n.html b/tests/i18n.html index 8f18f80..ed6321d 100644 --- a/tests/i18n.html +++ b/tests/i18n.html @@ -48,14 +48,14 @@ - - - - - - - - + + + + + + + +