From d35e605e5154f3f0e036367bd9de9ce6cda77a42 Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Tue, 30 Jun 2026 23:12:46 +0100 Subject: [PATCH 1/6] Ensure fallback textarea doesn't overlap with unregistered notice (Fixes #224) --- code-input.css | 4 +++- plugins/autogrow.css | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/code-input.css b/code-input.css index 758b88d..07140e6 100644 --- a/code-input.css +++ b/code-input.css @@ -284,5 +284,7 @@ code-input textarea[data-code-input-fallback] { color: var(--code-input_highlight-text-color, inherit); /* Don't overlap with message */ - min-height: calc(100% - var(--padding-top, 16px) - 2em - var(--padding-bottom, 16px)); + min-height: calc(100% - var(--padding-top, 16px) - max(2em, var(--padding-bottom, 16px))); + height: calc(100% - var(--padding-top, 16px) - max(2em, var(--padding-bottom, 16px))); + padding-bottom: max(2em, var(--padding-bottom, 16px))!important; } diff --git a/plugins/autogrow.css b/plugins/autogrow.css index d775f5f..9595dc5 100644 --- a/plugins/autogrow.css +++ b/plugins/autogrow.css @@ -71,7 +71,7 @@ code-input.code-input_autogrow_height:has(textarea[data-code-input-fallback]) { --code-input_autogrow_internal-min-height: calc(1lh + 2em + var(--padding-top, 16px) + var(--padding-bottom, 16px)); /* So minimum height possible while containing highlighted code */ height: var(--code-input_autogrow_true-min-height); } -code-input textarea[data-code-input-fallback] { - height: calc(var(--code-input_autogrow_true-min-height) - var(--padding-top, 16px) - var(--padding-bottom, 16px) - 2em)!important; /* So minimum height possible while containing highlighted code */ - min-height: calc(100% - var(--padding-top, 16px) - var(--padding-bottom, 16px) - 2em); +code-input.code-input_autogrow_height textarea[data-code-input-fallback] { + height: calc(var(--code-input_autogrow_true-min-height) - var(--padding-top, 16px) - max(2em, var(--padding-bottom, 16px)))!important; + min-height: calc(var(--code-input_autogrow_true-min-height) - var(--padding-top, 16px) - max(2em, var(--padding-bottom, 16px))); } From 90e78f4f5469d2feecdd35a8ce2fc343b69da76f Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Tue, 30 Jun 2026 23:27:43 +0100 Subject: [PATCH 2/6] Add more tests for fallback textarea and load (Fixes #216) (#99) --- tests/prism.html | 2 +- tests/tester.js | 97 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/tests/prism.html b/tests/prism.html index 787216c..591604b 100644 --- a/tests/prism.html +++ b/tests/prism.html @@ -41,7 +41,7 @@

Test for highlight.js

This page carries out automated tests for the code-input library to check that both the core components and the plugins work in some ways. It doesn't fully cover every scenario so you should test any code you change by hand, but it's good for quickly checking a wide range of functionality works.

Test Results (Click to Open)
-
+ diff --git a/tests/tester.js b/tests/tester.js index 679703b..05cec13 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -105,8 +105,74 @@ function waitAsync(milliseconds) { /* --- Running the tests --- */ /* Start the test, for Prism.js if isHLJS is false, or for highlight.js if isHLJS is true. */ -function beginTest(isHLJS) { +var loadEventFired = false; // Global variable so can check the load event is fired in startLoad function +async function beginTest(isHLJS) { let codeInputElem = document.querySelector("code-input"); + codeInputElem.addEventListener("code-input_load", () => { + loadEventFired = true; + testAssertion("Load", "code-input_load Event Fired Late Enough", codeInputElem.querySelector("textarea:not([data-code-input-fallback])") != null, "code-input_load event fired before non-fallback textarea element appeared"); + }); + const fallbackTextarea = codeInputElem.querySelector("textarea[data-code-input-fallback]"); + + + startLoad(codeInputElem, isHLJS); + codeInputElem.style.height = "calc(1lh + 2em)"; // 2em for the "No highlighting." message + codeInputElem.style.setProperty("--padding", "0px"); + await waitAsync(50); // Wait for display to update + // Select the "A" in the fallback textarea's last line + fallbackTextarea.selectionStart = 50; + fallbackTextarea.selectionEnd = 51; + fallbackTextarea.focus(); + fallbackTextarea.scrollTo(0, codeInputElem.clientHeight * 3); // At least 3 lines + + await waitAsync(50); // Wait for scroll to occur + testAssertion("FallbackTextarea", "Scrolls Correctly", confirm("Is the phrase 'A third', with 'A' highlighted, visible? "), "user-judged"); + codeInputElem.style.removeProperty("--padding"); + codeInputElem.style.removeProperty("height"); + + // Select the "log" in the fallback textarea's initial line + fallbackTextarea.selectionStart = 8; + fallbackTextarea.selectionEnd = 11; + fallbackTextarea.focus(); + + codeInputElem.style.setProperty("--padding", "100px"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea", "Displayed Correctly With More Padding", confirm("Is the highlighted 'log' properly aligned after the 'console.', which is then properly aligned inside the visible (non-highlighted) textarea? Also, there should be a lot of padding."), "user-judged"); + codeInputElem.style.setProperty("--padding", "0px"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea", "Displayed Correctly With No Padding", confirm("Does the element have zero padding, but otherwise the display remains correct?"), "user-judged"); + codeInputElem.style.removeProperty("--padding"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea", "Displayed Correctly By Default", confirm("Is the highlighted 'log' properly aligned after the 'console.', which is then properly aligned inside the visible (non-highlighted) textarea?"), "user-judged"); + + + codeInputElem.classList.add("code-input_autogrow_height"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea-Autogrow", "Displayed Correctly With Default Autogrow Height", confirm("Is the element not very tall (but tall enough to properly show some code), but otherwise the display remains correct?"), "user-judged"); + + codeInputElem.style.setProperty("--code-input_autogrow_min-height", "200px"); + await waitAsync(50); // Wait for display to update + assertEqual("FallbackTextarea-Autogrow", "--code-input_autogrow_min-height Sets Height", codeInputElem.clientHeight, 200); + codeInputElem.style.removeProperty("--code-input_autogrow_min-height"); + codeInputElem.classList.remove("code-input_autogrow_height"); + + codeInputElem.classList.add("code-input_autogrow_width"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea-Autogrow", "Displayed Correctly With Default Autogrow Width", confirm("Is the element a sensible but narrow width, and otherwise the display remains correct?"), "user-judged"); + + codeInputElem.style.setProperty("--code-input_autogrow_min-width", "200px"); + await waitAsync(50); // Wait for display to update + assertEqual("FallbackTextarea-Autogrow", "--code-input_autogrow_min-height Sets Height", codeInputElem.clientWidth, 200); + codeInputElem.style.removeProperty("--code-input_autogrow_min-width"); + codeInputElem.classList.remove("code-input_autogrow_width"); + + + if(!isHLJS) { + codeInputElem.classList.add("line-numbers"); + await waitAsync(50); // Wait for display to update + testAssertion("FallbackTextarea-PrismLineNumbers", "Displayed Correctly With line-numbers Class", confirm("Is there more padding to the left now, but otherwise the display remains correct?"), "user-judged"); + } + if(isHLJS) { codeInput.registerTemplate("code-editor", new codeInput.templates.Hljs(hljs, [ new codeInput.plugins.AutoCloseBrackets(), @@ -145,7 +211,6 @@ function beginTest(isHLJS) { new codeInput.plugins.SpecialChars(true), ])); } - startLoad(codeInputElem, isHLJS); } /* Start loading the tests, using the codeInput load time as one of the tests. */ @@ -153,11 +218,14 @@ function startLoad(codeInputElem, isHLJS) { let textarea; let timeToLoad = 0; let interval = window.setInterval(() => { - textarea = codeInputElem.querySelector("textarea"); - if(textarea != null) window.clearInterval(interval); - timeToLoad += 10; - testData("TimeTaken", "Textarea Appears", timeToLoad+"ms (nearest 10)"); - startTests(textarea, isHLJS); + textarea = codeInputElem.querySelector("textarea:not([data-code-input-fallback])"); + if(textarea != null) { + testAssertion("Load", "code-input_load Event Fired Early Enough", loadEventFired, "non-fallback textarea element appeared before code-input_load event"); + window.clearInterval(interval); + timeToLoad += 10; + testData("Load", "Time Taken for Textarea to Appear", timeToLoad+"ms (nearest 10)"); + beginTestsAfterLoad(textarea, isHLJS); + } }, 10); } @@ -178,8 +246,16 @@ function allowInputEvents(inputElement, codeInputElement=undefined) { } /* Start the tests using the textarea inside the code-input element and whether highlight.js is being used (as the Autodetect plugin only works with highlight.js, for example) */ -async function startTests(textarea, isHLJS) { +async function beginTestsAfterLoad(textarea, isHLJS) { textarea.focus(); + if(!isHLJS) { + await waitAsync(200); // Wait for display to update + testAssertion("FallbackTextarea-PrismLineNumbers", "Alignment between Fallback and Loaded Texareas", confirm("Now with the highlighting, is all the code in the same horizontal position (don't mind the vertical offset by keyboard navigation instructions) as with the last question?"), "user-judged"); + + } else { + await waitAsync(200); // Wait for display to update + testAssertion("FallbackTextarea+PrismLineNumbers", "Alignment between Fallback and Loaded Texareas", confirm("Now with the highlighting, is all the code in the same horizontal position (don't mind the vertical offset by keyboard navigation instructions) as with the last question?"), "user-judged"); + } codeInputElement = textarea.parentElement; allowInputEvents(textarea, codeInputElement); @@ -187,7 +263,10 @@ async function startTests(textarea, isHLJS) { /*--- Tests for core functionality ---*/ // Textarea's initial value should be correct. - assertEqual("Core", "Initial Textarea Value", textarea.value, `console.log("Hello, World!"); + assertEqual("FallbackTextarea", "Textarea selectionStart Once Loaded same as Fallback Textarea's", textarea.selectionStart, 8); + assertEqual("FallbackTextarea", "Textarea selectionEnd Once Loaded same as Fallback Textarea's", textarea.selectionEnd, 11); + + assertEqual("FallbackTextarea", "Textarea Value Once Loaded same as Fallback Textarea's", textarea.value, `console.log("Hello, World!"); // A second line // A third line with tags`); // Code element's displayed value, ignoring appearance with HTML tags, should be the initial value but HTML-escaped From 26c1ffd91e1ba92739663bdcb64b0383a3550a8e Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Tue, 30 Jun 2026 23:41:45 +0100 Subject: [PATCH 3/6] Fix typo in docs --- docs/modules-and-frameworks/custom/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules-and-frameworks/custom/_index.md b/docs/modules-and-frameworks/custom/_index.md index 91275cd..c97b312 100644 --- a/docs/modules-and-frameworks/custom/_index.md +++ b/docs/modules-and-frameworks/custom/_index.md @@ -1,5 +1,5 @@ +++ -title = 'Using code-input.js with custom highlighting algorihms in projects which use modules or frameworks' +title = 'Using code-input.js with custom highlighting algorithms in projects which use modules or frameworks' +++ # Using code-input.js with custom highlighting algorithms in projects which use modules or frameworks From 28e1f9933e5df6569632d523b7ba2252e67daff8 Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Tue, 30 Jun 2026 23:49:45 +0100 Subject: [PATCH 4/6] Update copyright year --- LICENSE | 2 +- code-input.css | 2 +- code-input.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index a0a9e97..19fa604 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2025 Oliver Geer and contributors +Copyright (c) 2021-2026 Oliver Geer and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/code-input.css b/code-input.css index 07140e6..b794a63 100644 --- a/code-input.css +++ b/code-input.css @@ -5,7 +5,7 @@ * * License of whole library for bundlers: * - * Copyright 2021-2025 Oliver Geer and contributors + * Copyright 2021-2026 Oliver Geer and contributors * @license MIT * * **** diff --git a/code-input.js b/code-input.js index a728df2..5eb6ff0 100644 --- a/code-input.js +++ b/code-input.js @@ -5,7 +5,7 @@ * * License of whole library for bundlers: * - * Copyright 2021-2025 Oliver Geer and contributors + * Copyright 2021-2026 Oliver Geer and contributors * @license MIT * * **** From d27af35c3d9b96ee56ee4d4c62d6799d35f7151d Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Wed, 1 Jul 2026 15:34:27 +0100 Subject: [PATCH 5/6] Add test Autocomplete: Popup Clickable (Closes #174) --- tests/tester.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/tester.js b/tests/tester.js index 05cec13..96bfdd4 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -106,6 +106,7 @@ function waitAsync(milliseconds) { /* Start the test, for Prism.js if isHLJS is false, or for highlight.js if isHLJS is true. */ var loadEventFired = false; // Global variable so can check the load event is fired in startLoad function +var popupClicked = false; // Global variable for Autocomplete plugin async function beginTest(isHLJS) { let codeInputElem = document.querySelector("code-input"); codeInputElem.addEventListener("code-input_load", () => { @@ -180,7 +181,7 @@ async function beginTest(isHLJS) { if(selectionStart == selectionEnd && textarea.value.substring(selectionEnd-5, selectionEnd) == "popup") { // Show popup popupElem.style.display = "block"; - popupElem.innerHTML = "Here's your popup!"; + popupElem.innerHTML = ''; } else { popupElem.style.display = "none"; } @@ -199,7 +200,7 @@ async function beginTest(isHLJS) { if(selectionStart == selectionEnd && textarea.value.substring(selectionEnd-5, selectionEnd) == "popup") { // Show popup popupElem.style.display = "block"; - popupElem.innerHTML = "Here's your popup!"; + popupElem.innerHTML = ''; } else { popupElem.style.display = "none"; } @@ -518,12 +519,31 @@ console.log("I've got another line!", 2 < 3, "should be true."); await waitAsync(50); // Wait for popup to be rendered testAssertion("Autocomplete", "Popup Shows on arrow key", confirm("Does the autocomplete popup display correctly? (OK=Yes)"), "user-judged"); - backspace(textarea); - await waitAsync(50); // Wait for popup disappearance to be rendered + backspace(textarea); + await waitAsync(50); // Wait for popup disappearance to be rendered testAssertion("Autocomplete", "Popup Disappears on backspace", confirm("Has the popup disappeared? (OK=Yes)"), "user-judged"); - move(textarea, 1); + + addText(textarea, "p"); + await waitAsync(50); + + const beforeClickSelectionStart = textarea.selectionStart; + const beforeClickSelectionEnd = textarea.selectionEnd; + alert("Dismiss this alert, then click the popup in the next 3 seconds."); + // To speed up test, wait until 3s have passed or a click has fired, whichever is earlier. + let timePassed = 0; + while(timePassed < 3000 && !popupClicked) { + await waitAsync(10); + timePassed += 10; + } + testAssertion("Autocomplete", "Popup Clickable", popupClicked, "The onclick event of the popup element didn't fire"); + // In case clicking the popup moved the caret inside the textarea: + textarea.selectionStart = beforeClickSelectionStart; + textarea.selectionEnd = beforeClickSelectionEnd; + textarea.focus(); + + backspace(textarea); backspace(textarea); backspace(textarea); backspace(textarea); From 9c94cea615df5ddc8b27b2a495aeb1540e088d9c Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Wed, 1 Jul 2026 15:54:28 +0100 Subject: [PATCH 6/6] Document a case of bold text misalignment (#130) --- docs/interface/css/_index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/interface/css/_index.md b/docs/interface/css/_index.md index 7fde3a5..f349842 100644 --- a/docs/interface/css/_index.md +++ b/docs/interface/css/_index.md @@ -9,6 +9,7 @@ title = 'Styling `code-input` elements with CSS' `code-input` elements can be styled like `textarea` elements in most cases; however, there are some exceptions: * The CSS variable `--padding` should be used rather than the property `padding` (e.g. `...`), or `--padding-left`, `--padding-right`, `--padding-top` and `--padding-bottom` instead of the CSS properties of the same names. For technical reasons, the value must have a unit (i.e. `0px`, not `0`). To avoid overcomplicating the code, this padding is always *included in* any width/heights of `code-input` elements, so if you want to style `textarea`s and `code-input` elements with best consistency set `box-sizing: border-box` on them. * Background colours set on `code-input` elements will not work with highlighters that set background colours themselves - use `(code-input's selector) pre[class*="language-"]` for Prism.js or `.hljs` for highlight.js to target the highlighted element with higher specificity than the highlighter's theme. You may also set the `background-color` of the code-input element for its appearance when its template is unregistered / there is no JavaScript. +* To minimise the chance of [this bug](https://github.com/WebCoder49/code-input/issues/130): if you import a monospace font family from an online source (for example, Google Fonts or Bunny Fonts) to style `code-input` elements, and use a syntax highlighting theme that includes **bold text**, import both the standard (400) and bold (700) weights of the font. * The caret and placeholder colour by default follow and give good contrast with the highlighted theme. Setting a CSS rule for `color` and/or `caret-color` properties on the code-input element will override this behaviour. * For technical reasons, `code-input:focus` won't match anything. Use `code-input:has(textarea:focus)` instead. * For now, elements on top of `code-input` elements should have a CSS `z-index` at least 3 greater than the `code-input` element.