Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions code-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ code-input {
top: 0;
left: 0;

color: black;
/* CSS variables rather than inline styles used for values synced from JavaScript
to keep low precedence and thus overridability
The variable names may change and are for internal use. */
/* --code-input_highlight-text-color: Set by JS to be base text color of pre code element */
/* --code-input_no-override-color: Set by JS for very short time to get whether color has been overriden */
color: var(--code-input_no-override-color, black);
/* --code-input_default-caret-color: Set by JS to be same as color property - currentColor won't work because it's lazily evaluated so gives transparent for the textarea */
caret-color: var(--code-input_default-caret-color, inherit);
background-color: white;

/* Normal inline styles */
Expand All @@ -36,7 +43,6 @@ code-input {
text-align: start;
line-height: 1.5; /* Inherited to child elements */
tab-size: 2;
caret-color: darkgrey;
white-space: pre;
padding: 0!important; /* Use --padding to set the code-input element's padding */
display: grid;
Expand Down Expand Up @@ -68,6 +74,12 @@ code-input textarea, code-input:not(.code-input_pre-element-styled) pre code, co
code-input:not(.code-input_pre-element-styled) pre code, code-input.code-input_pre-element-styled pre {
height: max-content;
width: max-content;


/* Allow colour change to reflect properly;
transition-behavior: allow-discrete could be used but this is better supported and
works with the color property. */
transition: color 0.001s;
}

code-input:not(.code-input_pre-element-styled) pre, code-input.code-input_pre-element-styled pre code {
Expand Down Expand Up @@ -118,12 +130,13 @@ code-input pre {
/* Make textarea almost completely transparent, except for caret and placeholder */

code-input textarea:not([data-code-input-fallback]) {
color: transparent;
background: transparent;
caret-color: inherit!important; /* Or choose your favourite color */
color: transparent;
caret-color: inherit;
}
code-input textarea::placeholder {
color: lightgrey;
code-input textarea:not([data-code-input-fallback]):placeholder-shown {
/* Show placeholder */
color: var(--code-input_highlight-text-color, inherit);
}

/* Can be scrolled */
Expand Down Expand Up @@ -163,6 +176,11 @@ code-input .code-input_dialog-container {

/* Dialog boxes' text is based on text-direction */
text-align: inherit;

/* Allow colour change to reflect properly;
* transition-behavior: allow-discrete could be used but this is * better supported and works with the color property. */
color: inherit;
transition: color 0.001s;
}

[dir=rtl] code-input .code-input_dialog-container, code-input[dir=rtl] .code-input_dialog-container {
Expand Down Expand Up @@ -252,11 +270,13 @@ code-input:not(.code-input_loaded) pre, code-input:not(.code-input_loaded) texta
code-input:has(textarea[data-code-input-fallback]) {
padding: 0!important; /* Padding now in the textarea */
box-sizing: content-box;

caret-color: revert; /* JS not setting the colour since no highlighting */
}
code-input textarea[data-code-input-fallback] {
overflow: auto;
background-color: inherit;
color: inherit;
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));
Expand Down
126 changes: 114 additions & 12 deletions code-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ var codeInput = {
}
},

stylesheetI: 0, // Increments to give different classes to each code-input element so they can have custom styles synchronised internally without affecting the inline style

/**
* Please see `codeInput.templates.prism` or `codeInput.templates.hljs`.
* Templates are used in `<code-input>` elements and once registered with
Expand Down Expand Up @@ -445,6 +447,16 @@ var codeInput = {
*/
dialogContainerElement = null;

/**
* Like style attribute, but with a specificity of 1
* element, 1 class. Present so styles can be set on only
* this element while giving other code freedom of use of
* the style attribute.
*
* For internal use only.
*/
internalStyle = null;

/**
* Form-Associated Custom Element Callbacks
* https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example
Expand Down Expand Up @@ -530,22 +542,75 @@ var codeInput = {
this.pluginEvt("afterHighlight");
}

getStyledHighlightingElement() {
if(this.templateObject.preElementStyled) {
return this.preElement;
} else {
return this.codeElement;
}
}

/**
* 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.templateObject.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;
this.textareaElement.style.height = getComputedStyle(this.getStyledHighlightingElement()).height;
this.textareaElement.style.width = getComputedStyle(this.getStyledHighlightingElement()).width;
}

/**
* If the color attribute has been defined on the
* code-input element by external code, return true.
* Otherwise, make the aspects the color affects
* (placeholder and caret colour) be the base colour
* of the highlighted text, for best contrast, and
* return false.
*/
isColorOverridenSyncIfNot() {
const oldTransition = this.style.transition;
this.style.transition = "unset";
window.requestAnimationFrame(() => {
this.internalStyle.setProperty("--code-input_no-override-color", "rgb(0, 0, 0)");
if(getComputedStyle(this).color == "rgb(0, 0, 0)") {
// May not be overriden
this.internalStyle.setProperty("--code-input_no-override-color", "rgb(255, 255, 255)");
if(getComputedStyle(this).color == "rgb(255, 255, 255)") {
// Definitely not overriden
this.internalStyle.removeProperty("--code-input_no-override-color");
this.style.transition = oldTransition;

const highlightedTextColor = getComputedStyle(this.getStyledHighlightingElement()).color;

this.internalStyle.setProperty("--code-input_highlight-text-color", highlightedTextColor);
this.internalStyle.setProperty("--code-input_default-caret-color", highlightedTextColor);
return false;
}
}
this.internalStyle.removeProperty("--code-input_no-override-color");
this.style.transition = oldTransition;
});

return true;
}

/**
* Update the aspects the color affects
* (placeholder and caret colour) to the correct
* colour: either that defined on the code-input
* element, or if none is defined externally the
* base colour of the highlighted text.
*/
syncColorCompletely() {
// color of code-input element
if(this.isColorOverridenSyncIfNot()) {
// color overriden
this.internalStyle.removeProperty("--code-input_highlight-text-color");
this.internalStyle.setProperty("--code-input_default-caret-color", getComputedStyle(this).color);
}
}


/**
* 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.
Expand Down Expand Up @@ -739,7 +804,6 @@ var codeInput = {
this.codeElement = code;
pre.append(code);
this.append(pre);

if (this.templateObject.isCode) {
if (lang != undefined && lang != "") {
code.classList.add("language-" + lang.toLowerCase());
Expand All @@ -765,7 +829,6 @@ var codeInput = {
// Update with fallback textarea's state so can keep editing
// if loaded slowly
if(fallbackSelectionStart !== undefined) {
console.log("sel", fallbackSelectionStart, fallbackSelectionEnd, fallbackSelectionDirection, "scr", fallbackScrollTop, fallbackScrollLeft, "foc", fallbackFocused);
textarea.setSelectionRange(fallbackSelectionStart, fallbackSelectionEnd, fallbackSelectionDirection);
textarea.scrollTo(fallbackScrollTop, fallbackScrollLeft);
}
Expand All @@ -779,7 +842,44 @@ var codeInput = {
// The only element that could be resized is this code-input element.
this.syncSize();
});
resizeObserver.observe(this.textareaElement);
resizeObserver.observe(this);


// Add internal style as non-externally-overridable alternative to style attribute for e.g. syncing color
this.classList.add("code-input_styles_" + codeInput.stylesheetI);
const stylesheet = document.createElement("style");
stylesheet.innerHTML = "code-input.code-input_styles_" + codeInput.stylesheetI + " {}";
this.appendChild(stylesheet);
this.internalStyle = stylesheet.sheet.cssRules[0].style;
codeInput.stylesheetI++;

// Synchronise colors
const preColorChangeCallback = (evt) => {
if(evt.propertyName == "color") {
this.isColorOverridenSyncIfNot();
}
};
this.preElement.addEventListener("transitionend", preColorChangeCallback);
this.preElement.addEventListener("-webkit-transitionend", preColorChangeCallback);
const thisColorChangeCallback = (evt) => {
if(evt.propertyName == "color") {
this.syncColorCompletely();
}
if(evt.target == this.dialogContainerElement) {
evt.stopPropagation();
// Prevent bubbling because code-input
// transitionend is separate
}
};
// Not on this element so CSS transition property does not override publicly-visible one
this.dialogContainerElement.addEventListener("transitionend", thisColorChangeCallback);
this.dialogContainerElement.addEventListener("-webkit-transitionend", thisColorChangeCallback);

// For when this code-input element has an externally-defined, different-duration transition
this.addEventListener("transitionend", thisColorChangeCallback);
this.addEventListener("-webkit-transitionend", thisColorChangeCallback);

this.syncColorCompletely();

this.classList.add("code-input_loaded");
}
Expand Down Expand Up @@ -914,7 +1014,9 @@ var codeInput = {
code.classList.add("language-" + newValue);
}

if (mainTextarea.placeholder == oldValue) mainTextarea.placeholder = newValue;
if (mainTextarea.placeholder == oldValue || oldValue == null && mainTextarea.placeholder == "") {
mainTextarea.placeholder = newValue;
}

this.scheduleHighlight();

Expand Down
3 changes: 3 additions & 0 deletions docs/interface/css/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ title = 'Styling `code-input` elements with CSS'
* The CSS variable `--padding` should be used rather than the property `padding` (e.g. `<code-input style="--padding: 10px;">...`), 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`).
* 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.
* For now, elements on top of `code-input` elements should have a CSS `z-index` at least 3 greater than the `code-input` element.
* The caret and placeholder colour by default follow and give good contrast with the highlighted theme. Setting a CSS rule (with a specificity higher than one element and one class, for good backwards compatibility) for `color` and/or `caret-color` properties on the code-input element will override this behaviour.

Please do **not** use `className` in JavaScript referring to code-input elements, because the code-input library needs to add its own classes to code-input elements for easier progressive enhancement. You can, however, use `classList` and `style` as much as you want - it will make your code cleaner anyway.
4 changes: 2 additions & 2 deletions tests/hljs.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>code-input Tester</title>

<!--Import Highlight.JS-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" id="theme-stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js"></script>
Expand Down Expand Up @@ -42,7 +42,7 @@ <h4><a href="prism.html">Test for Prism.js</a></h4>

<details id="collapse-results"><summary>Test Results (Click to Open)</summary><pre id="test-results"></pre></details>
<form method="GET" action="afterform.html" target="_blank">
<code-input><textarea data-code-input-fallback name="q">console.log("Hello, World!");
<code-input><textarea data-code-input-fallback name="q" placeholder="language auto-detected">console.log("Hello, World!");
// A second line
// A third line with &lt;html> tags</textarea></code-input>
<input type="submit" value="Test HTML Form"/>
Expand Down
2 changes: 1 addition & 1 deletion tests/prism.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>code-input Tester</title>
<!--Import Prism-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" id="theme-stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js" data-manual></script><!--Remove data-manual if also using Prism normally-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
Expand Down
28 changes: 28 additions & 0 deletions tests/tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,34 @@ console.log("I've got another line!", 2 &lt; 3, "should be true.");
// A third line with &lt;html&gt; tags
`); // Extra newline so line numbers visible if enabled.

// Delete all code
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
backspace(textarea);
codeInputElement.setAttribute("language", "JavaScript"); // for placeholder

await waitAsync(100); // Wait for rendered value to update
testAssertion("Core", "Light theme Caret/Placeholder Color Correct", confirm("Are the caret and placeholder near-black? (OK=Yes)"), "user-judged");

if(isHLJS) {
document.getElementById("theme-stylesheet").href = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/dark.min.css";
} else {
document.getElementById("theme-stylesheet").href = "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css";
}
await waitAsync(200); // Wait for colours to update
testAssertion("Core", "Dark theme Caret/Placeholder Color Correct", confirm("Are the caret and placeholder near-white? (OK=Yes)"), "user-judged");

codeInputElement.style.color = "red";
await waitAsync(200); // Wait for colours to update
testAssertion("Core", "Overriden color Caret/Placeholder Color Correct", confirm("Are the caret and placeholder (for Firefox) or just caret (for Chromium/WebKit, for consistency with textareas) red? (OK=Yes)"), "user-judged");

codeInputElement.style.removeProperty("color");
codeInputElement.style.caretColor = "red";
await waitAsync(200); // Wait for colours to update
testAssertion("Core", "Overriden caret-color Caret/Placeholder Color Correct", confirm("Is the caret red and placeholder near-white? (OK=Yes)"), "user-judged");

codeInputElement.style.removeProperty("caret-color");

/*--- Tests for plugins ---*/
// AutoCloseBrackets
testAddingText("AutoCloseBrackets", textarea, function(textarea) {
Expand Down