diff --git a/code-input.css b/code-input.css index a5fd92a..fe29963 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; @@ -22,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%; @@ -38,21 +41,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; @@ -68,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 */ @@ -84,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,20 +107,12 @@ 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; } -/* 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; @@ -158,12 +153,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; @@ -173,6 +164,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; @@ -187,13 +182,18 @@ 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; - 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. */ max-height: 3em; } code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-navigation-instructions { @@ -212,5 +212,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.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 a9fc914..d8ab49d 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"
     ],
 
     /* ------------------------------------
@@ -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,
             }
         },
 
@@ -474,7 +486,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;
 
         /**
@@ -503,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);
@@ -513,35 +526,9 @@ var codeInput = {
             if (this.template.includeCodeInputInHighlightFunc) this.template.highlight(resultElement, this);
             else this.template.highlight(resultElement);
 
-            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");
         }
 
-        /**
-         * 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.
@@ -638,9 +625,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
@@ -695,22 +680,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);
         }
 
         /**
-         * @deprecated Please use `codeInput.CodeInput.escapeHtml`
+         * 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 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();
@@ -866,22 +893,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 {
@@ -941,10 +966,12 @@ var codeInput = {
             if (val === null || val === undefined) {
                 val = "";
             }
+
             // Save in editable textarea element
             this.textareaElement.value = val;
             // Trigger highlight
             this.scheduleHighlight();
+
             return val;
         }
 
@@ -1024,6 +1051,42 @@ 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(options) {
+            this.textareaElement.scroll(options);
+            this.textareaElement.scroll(x, y);
+        };
+        scroll(x, y) {
+            this.textareaElement.scroll(x, y);
+        };
+        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);
+        };
     },
 
     /** 
@@ -1059,7 +1122,8 @@ var codeInput = {
                 true, // preElementStyled
                 true, // isCode
                 false, // includeCodeInputInHighlightFunc
-                plugins
+                plugins,
+                true // addPlaceholderToLastEmptyLine
             );
         }
     };
@@ -1086,7 +1150,8 @@ var codeInput = {
                 false, // preElementStyled
                 true, // isCode
                 false, // includeCodeInputInHighlightFunc
-                plugins
+                plugins,
+                true // addPlaceholderToLastEmptyLine
             );
         }
     };
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;
-}
diff --git a/tests/tester.js b/tests/tester.js
index 4a34264..abf1cd0 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,26 @@ 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`);
+
+    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