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 758b88d..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 * * **** @@ -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/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 * * **** 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. 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 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))); } 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..96bfdd4 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -105,8 +105,75 @@ 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 +var popupClicked = false; // Global variable for Autocomplete plugin +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(), @@ -114,7 +181,7 @@ 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"; } @@ -133,7 +200,7 @@ 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"; } @@ -145,7 +212,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 +219,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 +247,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 +264,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 @@ -439,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);