From 2c6f35433e4eb86b80cd7e7064c9d7695b05ec3f Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Fri, 20 Jun 2025 17:51:27 +0100 Subject: [PATCH 1/7] 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 457b0a9fea6053203f5019ffa2af0347dd87944d Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Wed, 16 Jul 2025 11:22:31 +0100 Subject: [PATCH 2/7] Make textarea rather than code-input element scroll --- code-input.css | 23 ++++++++------- code-input.js | 76 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/code-input.css b/code-input.css index a5fd92a..b738087 100644 --- a/code-input.css +++ b/code-input.css @@ -6,8 +6,7 @@ code-input { /* Allow other elements to be inside */ display: block; - overflow-y: auto; - overflow-x: auto; + overflow: hidden; position: relative; top: 0; left: 0; @@ -38,21 +37,16 @@ code-input textarea, code-input:not(.code-input_pre-element-styled) pre code, co margin: 0px!important; padding: var(--padding, 16px)!important; border: 0; - min-width: calc(100% - var(--padding) * 2); - min-height: calc(100% - var(--padding) * 2); + width: calc(100% - var(--padding) * 2); + height: calc(100% - var(--padding) * 2); box-sizing: content-box; /* Make height, width work consistently no matter the box-sizing of ancestors; dialogs can be styled as wanted so are excluded. */ - overflow: hidden; + overflow: auto; resize: none; grid-row: 1; grid-column: 1; display: block; } -code-input:not(.code-input_pre-element-styled) pre code, code-input.code-input_pre-element-styled pre { - height: max-content; - width: max-content; -} - code-input:not(.code-input_pre-element-styled) pre, code-input.code-input_pre-element-styled pre code { /* Remove all margin and padding from others */ margin: 0px!important; @@ -104,7 +98,7 @@ code-input pre { code-input textarea { color: transparent; background: transparent; - caret-color: inherit!important; /* Or choose your favourite color */ + caret-color: inherit!important; } code-input textarea::placeholder { color: lightgrey; @@ -187,7 +181,11 @@ code-input .code-input_dialog-container .code-input_keyboard-navigation-instruct position: absolute; background-color: black; color: white; + padding: 2px; + line-height: 2em; + box-sizing: content-box; /* Make height, width work consistently no matter the box-sizing of ancestors; dialogs can be styled as wanted so are excluded. */ + padding-left: 10px; margin: 0; text-wrap: balance; @@ -212,5 +210,6 @@ code-input .code-input_dialog-container .code-input_keyboard-navigation-instruct 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 { - padding-top: calc(var(--padding) + 3em)!important; + padding-top: calc(var(--padding) + 2em + 4px)!important; + height: calc(100% - var(--padding) * 2 - 2em - 4px)!important; } diff --git a/code-input.js b/code-input.js index 783f265..e9f1444 100644 --- a/code-input.js +++ b/code-input.js @@ -6,7 +6,6 @@ * **** */ - var codeInput = { /** * A list of attributes that will trigger the @@ -64,7 +63,8 @@ var codeInput = { "change", "selectionchange", "invalid", - "input" + "input", + "scroll" ], /* ------------------------------------ @@ -512,27 +512,9 @@ var codeInput = { if (this.template.includeCodeInputInHighlightFunc) this.template.highlight(resultElement, this); else this.template.highlight(resultElement); - this.syncSize(); - this.pluginEvt("afterHighlight"); } - /** - * Set the size of the textarea element to the size of the pre/code element. - */ - syncSize() { - // Synchronise the size of the pre/code and textarea elements - if(this.template.preElementStyled) { - this.style.backgroundColor = getComputedStyle(this.preElement).backgroundColor; - this.textareaElement.style.height = getComputedStyle(this.preElement).height; - this.textareaElement.style.width = getComputedStyle(this.preElement).width; - } else { - this.style.backgroundColor = getComputedStyle(this.codeElement).backgroundColor; - this.textareaElement.style.height = getComputedStyle(this.codeElement).height; - this.textareaElement.style.width = getComputedStyle(this.codeElement).width; - } - } - /** * 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. * @param {string} instructions The instructions to display only if keyboard navigation is being used. If it's blank, no instructions will be shown. @@ -684,22 +666,64 @@ var codeInput = { this.value = value; this.animateFrame(); - const resizeObserver = new ResizeObserver((elements) => { - // The only element that could be resized is this code-input element. - this.syncSize(); + // Scrolling + this.textareaElement.addEventListener("scroll", () => { + this.syncScrollFromTextarea(); }); - resizeObserver.observe(this); + if(this.template.preElementStyled) { + this.preElement.addEventListener("scroll", () => { + this.syncScrollFromHighlighted(this.preElement) + }); + } else { + this.codeElement.addEventListener("scroll", () => { + this.syncScrollFromHighlighted(this.codeElement) + }); + } + // TODO: Make scrollTop etc. directly accessible from code-input. + } + + /** + * Synchronise the scroll position of the textarea element to that of the pre/code element. + * @param {HTMLElement} highlightedElement The pre/code element that has been scrolled (the styled one). + */ + syncScrollFromHighlighted(highlightedElement) { + this.textareaElement.scrollTo(highlightedElement.scrollLeft, highlightedElement.scrollTop); + } + + /** + * Synchronise the scroll position of the pre/code element to that of the textarea element. + */ + syncScrollFromTextarea() { + if(this.template.preElementStyled) { + this.preElement.scrollTo(this.textareaElement.scrollLeft, this.textareaElement.scrollTop); + } else { + this.codeElement.scrollTo(this.textareaElement.scrollLeft, this.textareaElement.scrollTop); + } + } + + /** + * @deprecated This is internal code used by code-input and it may change/be removed to improve the library. This function with its old name remains, though, because that wasn't made as clear in the past. + */ + syncScroll() { + this.syncScrollFromTextarea(); + } + + /** + * @deprecated This is internal code used by code-input and it may change/be removed to improve the library. This function with its old name remains, though, because that wasn't made as clear in the past, and will remain until the next major version of code-inp. + */ + sync_scroll() { + this.syncScroll(); } /** - * @deprecated Please use `codeInput.CodeInput.escapeHtml` + * @deprecated This is internal code used by code-input and it may change/be removed to improve the library. This function with its old name remains, though, because that wasn't made clear in the past. */ escape_html(text) { return this.escapeHtml(text); } /** - * @deprecated Please use `codeInput.CodeInput.getTemplate` + * @deprecated This is internal code used by code-input and it may change/be removed to improve the library. This function with its old name remains, though, because that wasn't made clear in the past. */ get_template() { return this.getTemplate(); From 2fd5d4844012cd3d50348ce399d4782f2f898a4f Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Thu, 17 Jul 2025 13:39:01 +0100 Subject: [PATCH 3/7] Return textarea's scroll attributes and methods from code-input element --- code-input.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/code-input.js b/code-input.js index e9f1444..c4f836f 100644 --- a/code-input.js +++ b/code-input.js @@ -614,6 +614,11 @@ var codeInput = { this.classList.remove("code-input_mouse-focused"); }); + // @deprecated Right now it's better to use the textarea element directly once it's loaded. + textarea.addEventListener("scroll", (evt) => { + this.dispatchEvent("scroll", evt); + }); + this.innerHTML = ""; // Clear Content // Synchronise attributes to textarea @@ -1037,6 +1042,33 @@ var codeInput = { formResetCallback() { this.value = this.initialValue; }; + + /* Pass scrolling to textarea, for backwards compatibility with when the code-input element scrolled. + @deprecated Right now it's better to use the textarea element directly once it's loaded. */ + get scrollTop() { return this.textareaElement.scrollTop; }; + set scrollTop(val) { this.textareaElement.scrollTop = val; }; + get scrollLeft() { return this.textareaElement.scrollLeft; }; + set scrollLeft(val) { this.textareaElement.scrollLeft = val; }; + get scrollTopMax() { return this.textareaElement.scrollTopMax; }; + set scrollTopMax(val) { this.textareaElement.scrollTopMax = val; }; + get scrollLeftMax() { return this.textareaElement.scrollLeftMax; }; + set scrollLeftMax(val) { this.textareaElement.scrollLeftMax = val; }; + get scrollHeight() { return this.textareaElement.scrollHeight; }; + set scrollHeight(val) { this.textareaElement.scrollHeight = val; }; + get scrollWidth() { return this.textareaElement.scrollWidth; }; + set scrollWidth(val) { this.textareaElement.scrollWidth = val; }; + scroll(firstArg, secondArg=undefined) { + if(secondArg === undefined) this.textareaElement.scroll(firstArg); + else this.textareaElement.scroll(firstArg, secondArg); + }; + scrollTo(firstArg, secondArg=undefined) { + if(secondArg === undefined) this.textareaElement.scrollTo(firstArg); + else this.textareaElement.scrollTo(firstArg, secondArg); + }; + scrollBy(firstArg, secondArg=undefined) { + if(secondArg === undefined) this.textareaElement.scrollBy(firstArg); + else this.textareaElement.scrollBy(firstArg, secondArg); + }; }, /** From bea73fa80fadb6e3527d56c1626134971600c60c Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Thu, 17 Jul 2025 15:36:23 +0100 Subject: [PATCH 4/7] Add placeholder to last empty line (to ensure alignment) if template specifies it --- code-input.d.ts | 18 +++++------------- code-input.js | 29 ++++++++++++++++++++--------- tests/tester.js | 9 +++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/code-input.d.ts b/code-input.d.ts index 284b2c2..80879f5 100644 --- a/code-input.d.ts +++ b/code-input.d.ts @@ -295,11 +295,11 @@ export class Template { * @param {boolean} preElementStyled - is the `
` element CSS-styled as well as the `` element? If true, `
` element's scrolling is synchronised; if false, `` element's scrolling is synchronised.
    * @param {boolean} isCode - is this for writing code? If true, the code-input's lang HTML attribute can be used, and the `` element will be given the class name 'language-[lang attribute's value]'.
    * @param {false} includeCodeInputInHighlightFunc - Setting this to true passes the `` element as a second argument to the highlight function.
-   * @param {boolean} autoDisableDuplicateSearching - Leaving this as true uses code-input's default fix for preventing duplicate results in Ctrl+F searching from the input and result elements, and setting this to false indicates your highlighting function implements its own fix. The default fix works by moving text content from elements to CSS `::before` pseudo-elements after highlighting.
    * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.Plugin`
+   * @param {boolean} addPlaceholderToLastEmptyLine - Setting this to true adds a space character to the end of the `` element before highlighting when its last line is empty, ensuring that last line is displayed and aligns with the editing.
    * @returns template object
    */
-  constructor(highlight?: (codeElement: HTMLElement) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: false, autoDisableDuplicateSearching?: boolean, plugins?: Plugin[])
+  constructor(highlight?: (codeElement: HTMLElement) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: false, plugins?: Plugin[], addPlaceholderToLastEmptyLine?: boolean)
   /**
    * **When `includeCodeInputInHighlightFunc` is `true`, `highlight` takes two parameters: the `
` element, and the `` element.**
    * 
@@ -309,25 +309,17 @@ export class Template {
    * @param {boolean} preElementStyled - is the `
` element CSS-styled as well as the `` element? If true, `
` element's scrolling is synchronised; if false, `` element's scrolling is synchronised.
    * @param {boolean} isCode - is this for writing code? If true, the code-input's lang HTML attribute can be used, and the `` element will be given the class name 'language-[lang attribute's value]'.
    * @param {true} includeCodeInputInHighlightFunc - Setting this to true passes the `` element as a second argument to the highlight function.
-   * @param {boolean} autoDisableDuplicateSearching - Leaving this as true uses code-input's default fix for preventing duplicate results in Ctrl+F searching from the input and result elements, and setting this to false indicates your highlighting function implements its own fix. The default fix works by moving text content from elements to CSS `::before` pseudo-elements after highlighting.
    * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.Plugin`
+   * @param {boolean} addPlaceholderToLastEmptyLine - Setting this to true adds a space character to the end of the `` element before highlighting when its last line is empty, ensuring that last line is displayed and aligns with the editing.
    * @returns template object
    */
-  constructor(highlight?: (codeElement: HTMLElement, codeInput: CodeInput) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: true, autoDisableDuplicateSearching?: boolean, plugins?: Plugin[])
+  constructor(highlight?: (codeElement: HTMLElement, codeInput: CodeInput) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: true, plugins?: Plugin[], addPlaceholderToLastEmptyLine?: boolean)
   highlight: Function
   preElementStyled: boolean
   isCode: boolean
   includeCodeInputInHighlightFunc: boolean
-  autoDisableDuplicateSearching: boolean
   plugins: Plugin[]
-  /**
-   * @deprecated Please give a value for the `autoDisableDuplicateSearching` parameter.
-   */
-  constructor(highlight?: (code: HTMLElement) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: false, plugins?: Plugin[])
-  /**
-   * @deprecated Please give a value for the `autoDisableDuplicateSearching` parameter.
-   */
-  constructor(highlight?: (code: HTMLElement, codeInput: CodeInput) => void, preElementStyled?: boolean, isCode?: boolean, includeCodeInputInHighlightFunc?: true, plugins?: Plugin[])
+  addPlaceholderToLastEmptyLine: boolean
 }
 
 /** 
diff --git a/code-input.js b/code-input.js
index c4f836f..982b142 100644
--- a/code-input.js
+++ b/code-input.js
@@ -163,14 +163,17 @@ var codeInput = {
          * @param {boolean} isCode - is this for writing code? If true, the code-input's lang HTML attribute can be used, and the `` element will be given the class name 'language-[lang attribute's value]'.
          * @param {boolean} includeCodeInputInHighlightFunc - Setting this to true passes the `` element as a second argument to the highlight function.
          * @param {codeInput.Plugin[]} plugins - An array of plugin objects to add extra features - see `codeInput.Plugin`
+         * @param {boolean} addPlaceholderToLastEmptyLine - Setting this to true adds a space character to the end of the `` element before highlighting when its last line is empty, ensuring that last line is displayed and aligns with the editing.
          * @returns {codeInput.Template} template object
          */
-        constructor(highlight = function (codeElement) { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = []) {
+        constructor(highlight = function (codeElement) { }, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = [], addPlaceholderToLastEmptyLine = false) {
+
             this.highlight = highlight;
             this.preElementStyled = preElementStyled;
             this.isCode = isCode;
             this.includeCodeInputInHighlightFunc = includeCodeInputInHighlightFunc;
             this.plugins = plugins;
+            this.addPlaceholderToLastEmptyLine = addPlaceholderToLastEmptyLine;
         }
 
         /**
@@ -207,6 +210,13 @@ var codeInput = {
          * see `codeInput.Plugin`.
          */
         plugins = [];
+
+        /**
+         * Setting this to true adds a space character to the end of the
+         * `` element before highlighting when its last line is empty, ensuring that last
+         * line is displayed and aligns with the editing.
+         */
+        addPlaceholderToLastEmptyLine = false;
     },
 
     /**
@@ -258,6 +268,7 @@ var codeInput = {
                 preElementStyled: true,
                 isCode: false,
                 plugins: plugins,
+                addPlaceholderToLastEmptyLine: false,
             }
         },
 
@@ -282,6 +293,7 @@ var codeInput = {
                 delimiter: delimiter,
 
                 plugins: plugins,
+                addPlaceholderToLastEmptyLine: false,
             }
         },
 
@@ -502,7 +514,9 @@ var codeInput = {
         update() {
             let resultElement = this.codeElement;
             let value = this.value;
-            value += "\n"; // Placeholder for next line
+            if(this.template.addPlaceholderToLastEmptyLine && (value[value.length-1] == "\n" || value.length == 0)) { // If last line empty
+                value += " "; // Add a placeholder space character to the final line
+            }
 
             // Update code
             resultElement.innerHTML = this.escapeHtml(value);
@@ -614,11 +628,6 @@ var codeInput = {
                 this.classList.remove("code-input_mouse-focused");
             });
 
-            // @deprecated Right now it's better to use the textarea element directly once it's loaded.
-            textarea.addEventListener("scroll", (evt) => {
-                this.dispatchEvent("scroll", evt);
-            });
-
             this.innerHTML = ""; // Clear Content
 
             // Synchronise attributes to textarea
@@ -1104,7 +1113,8 @@ var codeInput = {
                 true, // preElementStyled
                 true, // isCode
                 false, // includeCodeInputInHighlightFunc
-                plugins
+                plugins,
+                true // addPlaceholderToLastEmptyLine
             );
         }
     };
@@ -1131,7 +1141,8 @@ var codeInput = {
                 false, // preElementStyled
                 true, // isCode
                 false, // includeCodeInputInHighlightFunc
-                plugins
+                plugins,
+                true // addPlaceholderToLastEmptyLine
             );
         }
     };
diff --git a/tests/tester.js b/tests/tester.js
index 4a34264..0668f06 100644
--- a/tests/tester.js
+++ b/tests/tester.js
@@ -194,8 +194,7 @@ async function startTests(textarea, isHLJS) {
     let renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
     assertEqual("Core", "Initial Rendered Value", renderedValue, `console.log("Hello, World!");
 // A second line
-// A third line with <html> tags
-`); // Extra newline so line numbers visible if enabled
+// A third line with <html> tags`);
 
 
     // Update code-input value with JavaScript, new value and num events should be correct.
@@ -214,8 +213,7 @@ console.log("I've got another line!", 2 < 3, "should be true.");`);
     assertEqual("Core", "JS-updated Rendered Value", renderedValue, `console.log("Hello, World!");
 // A second line
 // A third line with <html> tags
-console.log("I've got another line!", 2 < 3, "should be true.");
-`); // Extra newline so line numbers visible if enabled
+console.log("I've got another line!", 2 < 3, "should be true.");`);
 
     // Event Listener Tests
     // Function type listeners
@@ -323,8 +321,7 @@ console.log("I've got another line!", 2 < 3, "should be true.");
     renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
     assertEqual("Core", "Form Reset resets Rendered Value", renderedValue, `console.log("Hello, World!");
 // A second line
-// A third line with <html> tags
-`); // Extra newline so line numbers visible if enabled.
+// A third line with <html> tags`);
 
     /*--- Tests for plugins ---*/
     // AutoCloseBrackets

From 9659416a610fd84a5a538ae96352c57c3122d8cf Mon Sep 17 00:00:00 2001
From: Oliver Geer 
Date: Thu, 17 Jul 2025 17:21:42 +0100
Subject: [PATCH 5/7] Make keyboard navigation instructions correct size with
 new scrolling

---
 code-input.css                 | 9 +++++----
 plugins/prism-line-numbers.css | 5 -----
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/code-input.css b/code-input.css
index b738087..d6b69cb 100644
--- a/code-input.css
+++ b/code-input.css
@@ -152,12 +152,8 @@ Sticks to the top of the code-input element */
 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;
@@ -167,6 +163,10 @@ code-input .code-input_dialog-container {
   /* Dialog boxes' text is based on text-direction */
   text-align: inherit;
 }
+code-input.code-input_pre-element-styled .code-input_dialog-container {
+  width: calc(100% + 2 * var(--padding));
+}
+
 [dir=rtl] code-input .code-input_dialog-container, code-input[dir=rtl] .code-input_dialog-container {
   left: unset;
   right: 0;
@@ -192,6 +192,7 @@ code-input .code-input_dialog-container .code-input_keyboard-navigation-instruct
   overflow: hidden;
   text-overflow: ellipsis;
   width: calc(100% - 12px);
+  box-sizing: content-box; /* Make height, width work consistently no matter the box-sizing of ancestors. */
   max-height: 3em;
 }
 code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-navigation-instructions {
diff --git a/plugins/prism-line-numbers.css b/plugins/prism-line-numbers.css
index 036d373..668539f 100644
--- a/plugins/prism-line-numbers.css
+++ b/plugins/prism-line-numbers.css
@@ -14,8 +14,3 @@ code-input.line-numbers textarea, code-input.line-numbers.code-input_pre-element
 code-input.line-numbers, .line-numbers code-input {
   grid-template-columns: calc(100% - max(0em, calc(3.8em - var(--padding, 16px))));
 }
-
-/* Make keyboard navigation still fill width */
-code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions {
-  width: calc(100% + max(3.8em, var(--padding, 16px)))!important;
-}

From a142a7bf7f3bcd357fec3de82305270116be6446 Mon Sep 17 00:00:00 2001
From: Oliver Geer 
Date: Thu, 17 Jul 2025 19:14:36 +0100
Subject: [PATCH 6/7] Enable wrapping to be enabled/disabled by CSS properties
 of code-input

---
 code-input.css | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/code-input.css b/code-input.css
index d6b69cb..7ffce43 100644
--- a/code-input.css
+++ b/code-input.css
@@ -21,6 +21,10 @@ code-input {
   tab-size: 2;
   caret-color: darkgrey;
   white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+
   padding: 0!important; /* Use --padding to set the code-input element's padding */
   display: grid;
   grid-template-columns: 100%;
@@ -62,6 +66,11 @@ code-input textarea, code-input pre, code-input pre * {
   line-height: inherit!important;
   tab-size: inherit!important;
   text-align: inherit!important;
+
+  white-space: inherit!important;
+  word-spacing: inherit!important;
+  word-break: inherit!important;
+  word-wrap: inherit!important;
 }
 
 /* Make changing the text direction propogate */
@@ -78,7 +87,7 @@ code-input textarea[dir=rtl] + pre {
 }
 
 
-code-input textarea, code-input pre {
+code-input textarea, code-input pre, code-input pre code {
   /* In the same place */
   grid-column: 1;
   grid-row: 1;
@@ -104,14 +113,6 @@ code-input textarea::placeholder {
   color: lightgrey;
 }
 
-/* Can be scrolled */
-code-input textarea, code-input pre {
-  white-space: inherit;
-  word-spacing: normal;
-  word-break: normal;
-  word-wrap: normal;
-}
-
 /* No resize on textarea; transfer outline on focus to code-input element */
 code-input textarea {
   resize: none;

From 2e5cf0a61ba069b4fa699792b1da87228309da91 Mon Sep 17 00:00:00 2001
From: Oliver Geer 
Date: Fri, 18 Jul 2025 09:50:15 +0100
Subject: [PATCH 7/7] Add some tests for CSS and scrolling; fix scroll handling
 so errors clearer

---
 code-input.css  |  2 +-
 code-input.js   | 27 ++++++++++++++++++---------
 tests/tester.js | 19 +++++++++++++++++++
 3 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/code-input.css b/code-input.css
index 7ffce43..fe29963 100644
--- a/code-input.css
+++ b/code-input.css
@@ -190,7 +190,7 @@ code-input .code-input_dialog-container .code-input_keyboard-navigation-instruct
   padding-left: 10px;
   margin: 0;
   text-wrap: balance;
-  overflow: hidden;
+  overflow: auto;
   text-overflow: ellipsis;
   width: calc(100% - 12px);
   box-sizing: content-box; /* Make height, width work consistently no matter the box-sizing of ancestors. */
diff --git a/code-input.js b/code-input.js
index 982b142..d8ab49d 100644
--- a/code-input.js
+++ b/code-input.js
@@ -1066,17 +1066,26 @@ var codeInput = {
         set scrollHeight(val) { this.textareaElement.scrollHeight = val; };
         get scrollWidth() { return this.textareaElement.scrollWidth; };
         set scrollWidth(val) { this.textareaElement.scrollWidth = val; };
-        scroll(firstArg, secondArg=undefined) {
-            if(secondArg === undefined) this.textareaElement.scroll(firstArg);
-            else this.textareaElement.scroll(firstArg, secondArg);
+        scroll(options) {
+            this.textareaElement.scroll(options);
+            this.textareaElement.scroll(x, y);
         };
-        scrollTo(firstArg, secondArg=undefined) {
-            if(secondArg === undefined) this.textareaElement.scrollTo(firstArg);
-            else this.textareaElement.scrollTo(firstArg, secondArg);
+        scroll(x, y) {
+            this.textareaElement.scroll(x, y);
         };
-        scrollBy(firstArg, secondArg=undefined) {
-            if(secondArg === undefined) this.textareaElement.scrollBy(firstArg);
-            else this.textareaElement.scrollBy(firstArg, secondArg);
+        scrollTo(options) {
+            this.textareaElement.scroll(options);
+            this.textareaElement.scroll(x, y);
+        };
+        scrollTo(x, y) {
+            this.textareaElement.scroll(x, y);
+        };
+        scrollBy(options) {
+            this.textareaElement.scroll(options);
+            this.textareaElement.scroll(x, y);
+        };
+        scrollBy(x, y) {
+            this.textareaElement.scroll(x, y);
         };
     },
 
diff --git a/tests/tester.js b/tests/tester.js
index 0668f06..abf1cd0 100644
--- a/tests/tester.js
+++ b/tests/tester.js
@@ -323,6 +323,25 @@ console.log("I've got another line!", 2 < 3, "should be true.");`);
 // A second line
 // A third line with <html> tags`);
 
+    codeInputElement.setAttribute("style", "width: 10em; height: 30em; white-space: pre-wrap; word-break: normal;");
+    textarea.selectionStart -= 1;
+    await waitAsync(50); // Wait for CSS to visibly change
+    testAssertion("Core", "CSS pre-wrap normal", confirm("Is the code wrapped, and the selected character aligned? (OK=Yes)"), "user-judged");
+    codeInputElement.setAttribute("style", "width: 10em; height: 30em; white-space: pre-wrap; word-break: break-word;");
+    await waitAsync(50); // Wait for CSS to visibly change
+    testAssertion("Core", "CSS pre-wrap break-word", confirm("Is the code wrapped, and the selected character aligned? (OK=Yes)"), "user-judged");
+    codeInputElement.setAttribute("style", "width: 10em; height: 30em; white-space: pre-wrap; word-break: break-all;");
+    await waitAsync(50); // Wait for CSS to visibly change
+    testAssertion("Core", "CSS pre-wrap break-all", confirm("Is the code wrapped, and the selected character aligned? (OK=Yes)"), "user-judged");
+    codeInputElement.setAttribute("style", "font-size: 50px; height: 10em;");
+    await waitAsync(50); // Wait for CSS to visibly change
+    testAssertion("Core", "CSS font-size", confirm("Is the selected character aligned? (OK=Yes)"), "user-judged");
+    codeInputElement.setAttribute("style", "width: 10em; height: 10em; white-space: pre-wrap; word-break: break-all;");
+    codeInputElement.scrollTo(0, (codeInputElement.offsetHeight - codeInputElement.clientHeight) + codeInputElement.scrollHeight); // Scroll to bottom - offset-client is margin+border+padding
+    await waitAsync(50); // Wait for CSS to visibly change
+    testAssertion("Core", "Scroll aligned", confirm("Is the selected character aligned? (OK=Yes)"), "user-judged");
+    codeInputElement.removeAttribute("style");
+
     /*--- Tests for plugins ---*/
     // AutoCloseBrackets
     testAddingText("AutoCloseBrackets", textarea, function(textarea) {