Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2c6f354
Remove janky blur/focus
WebCoder49 Jun 20, 2025
11c3227
Fix infinite recursion (bug unfixed - see #131)
WebCoder49 Jun 27, 2025
d53c19b
Use classes for templates; keep but deprecate use of function templat…
WebCoder49 Jul 9, 2025
fe408d3
Use classes for templates in README and tests
WebCoder49 Jul 11, 2025
9eeca46
Merge pull request #137 from WebCoder49/classes-for-templates
WebCoder49 Jul 14, 2025
caa9569
Fix AutoCloseBrackets and Indent across WebKit and others (Fixes #131…
WebCoder49 Jul 15, 2025
40aaf38
Add test for automatic unindent
WebCoder49 Jul 15, 2025
4700894
Remove unnecessary line of testing code
WebCoder49 Jul 15, 2025
2f6ed5d
Merge pull request #139 from WebCoder49/webkit-exec-command
WebCoder49 Jul 15, 2025
68f98df
Auto Minified JS and CSS files
WebCoder49 Jul 15, 2025
985cf13
Enforce box sizing for 100%-width elements (Fixes #135)
WebCoder49 Jul 15, 2025
c72882c
Merge pull request #142 from WebCoder49/box-sizing-consistency
WebCoder49 Jul 15, 2025
f024f1b
Auto Minified JS and CSS files
WebCoder49 Jul 15, 2025
1db035c
Make FindAndReplace and GoToLine close buttons screenreader accessibl…
WebCoder49 Jul 15, 2025
c2bc95d
Merge pull request #143 from WebCoder49/accessible-close-buttons
WebCoder49 Jul 15, 2025
90355d0
Auto Minified JS and CSS files
WebCoder49 Jul 15, 2025
b834cbb
Release 2.5.1
WebCoder49 Jul 15, 2025
926fb75
Merge pull request #144 from WebCoder49/main
WebCoder49 Jul 16, 2025
ac67d60
Merge pull request #147 from WebCoder49/classes-for-templates
WebCoder49 Jul 18, 2025
350e422
Auto Minified JS and CSS files
WebCoder49 Jul 18, 2025
7fca3b2
Merge pull request #149 from WebCoder49/code-input-scrolls-but-no-blu…
WebCoder49 Jul 18, 2025
2f46027
Auto Minified JS and CSS files
WebCoder49 Jul 18, 2025
b8d78f0
Make completely blank newline remove indentation on created lines bel…
WebCoder49 Jul 18, 2025
37e0ecc
Merge pull request #150 from WebCoder49/indent-unindent-fix
WebCoder49 Jul 18, 2025
9467517
Auto Minified JS and CSS files
WebCoder49 Jul 18, 2025
21dc4a4
Make selectionchange work with popup; Add selectionStart parameter to…
WebCoder49 Jul 18, 2025
75f835d
Merge pull request #152 from WebCoder49/popup-update-onselectionchange
WebCoder49 Jul 18, 2025
3aeb959
Auto Minified JS and CSS files
WebCoder49 Jul 18, 2025
1c3119a
Add special characters inside their generated spans so find and repla…
WebCoder49 Jul 18, 2025
4e1790c
Add disclaimer about special chars bugs
WebCoder49 Jul 18, 2025
1663fab
Merge pull request #155 from WebCoder49/specialchars-and-findandreplace
WebCoder49 Jul 18, 2025
56eec90
Auto Minified JS and CSS files
WebCoder49 Jul 18, 2025
7b1c88d
Merge branch 'main' into esm-support
WebCoder49 Jul 19, 2025
d9d2c55
Merge pull request #157 from WebCoder49/esm-support
WebCoder49 Jul 19, 2025
6a51063
Auto Minified JS and CSS files
WebCoder49 Jul 19, 2025
f39479b
Mirror optionality of plugin parameters between JavaScript and TypeSc…
WebCoder49 Jul 19, 2025
b561b95
Delete duplicated code from merge
WebCoder49 Jul 19, 2025
8af07e7
Auto Minified JS and CSS files
WebCoder49 Jul 19, 2025
a5b6ff3
Make FindAndReplace scroll work in Firefox/Prism (fixes #119) ; Make …
WebCoder49 Jul 24, 2025
0b45d55
Add test for FindAndReplace scrolling to match
WebCoder49 Jul 25, 2025
e7c5cdc
Merge pull request #160 from WebCoder49/prism-plugin-scroll
WebCoder49 Jul 25, 2025
26b096b
Auto Minified JS and CSS files
WebCoder49 Jul 25, 2025
1a7c4f7
Fix accidentally unlocalised string in FindAndReplace plugin
WebCoder49 Jul 26, 2025
728b117
Improve plugin dialog accessibility with GoToLine status message (fix…
WebCoder49 Jul 27, 2025
6b25e0f
Merge pull request #161 from WebCoder49/gotoline-accessibility
WebCoder49 Jul 27, 2025
fca52ff
Auto Minified JS and CSS files
WebCoder49 Jul 27, 2025
92d5f05
Make sizes work better with code-input scrolling (#148)
WebCoder49 Jul 27, 2025
bf72dc2
Add GoToLine translations to TypeScript declarations and i18n test
WebCoder49 Jul 28, 2025
5987088
Prevent Prism.js and highlight.js automatic highlighting interfering …
WebCoder49 Jul 28, 2025
dda0b5d
Merge pull request #163 from WebCoder49/sizing-with-v2-scroll
WebCoder49 Jul 28, 2025
57b4b16
Auto Minified JS and CSS files
WebCoder49 Jul 28, 2025
96a0f80
Add no-JavaScript fallback (Fixes #134)
WebCoder49 Jul 28, 2025
06296b0
Merge pull request #165 from WebCoder49/textarea-fallback
WebCoder49 Jul 28, 2025
6a34502
Auto Minified JS and CSS files
WebCoder49 Jul 28, 2025
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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ The next step is to set up a `template` to link `code-input` to your syntax-high

- *Highlight.js:*
```js
codeInput.registerTemplate("syntax-highlighted", codeInput.templates.hljs(hljs, [] /* Array of plugins (see below) */));
codeInput.registerTemplate("syntax-highlighted", new codeInput.templates.Hljs(hljs, [] /* Array of plugins (see below) */));
```

- *Prism.js:*
```js
codeInput.registerTemplate("syntax-highlighted", codeInput.templates.prism(Prism, [] /* Array of plugins (see below) */));
codeInput.registerTemplate("syntax-highlighted", new codeInput.templates.Prism(Prism, [] /* Array of plugins (see below) */));
```

- *Custom:*
Expand Down Expand Up @@ -106,7 +106,7 @@ The next step is to set up a `template` to link `code-input` to your syntax-high
<!--...-->
<script>
codeInput.registerTemplate("syntax-highlighted",
codeInput.templates.hljs(
new codeInput.templates.Hljs(
hljs,
[
new codeInput.plugins.Autodetect(),
Expand All @@ -122,13 +122,13 @@ The next step is to set up a `template` to link `code-input` to your syntax-high
To see a full list of plugins and their functions, please see [plugins/README.md](./plugins/README.md).

### 4. Using the component
Now that you have registered a template, you can use the custom `<code-input>` element in HTML. If you have more than one template registered, you need to add the template name as the `template` attribute. With the element, using the `language` attribute will add a `language-{value}` class to the `pre code` block. You can now use HTML attributes and events, as well as CSS styles, to make your element as simple or interactive as you like, as if it were a `textarea` element!
Now that you have registered a template, you can use the custom `<code-input>` element in HTML. I recommend it surrounds a fallback `<textarea code-input-fallback>` element which will be used instead when JavaScript support is absent, and will pass its attributes to the `<code-input>` element otherwise, as shown below. If you have more than one template registered, you need to add the template name as the `template` attribute. With the element, using the `language` attribute will add a `language-{value}` class to the `pre code` block. You can now use HTML attributes and events, as well as CSS styles, to make your element as simple or interactive as you like, as if it were a `textarea` element!
```HTML
<code-input language="HTML"></code-input>
<code-input language="HTML"><textarea code-input-fallback></textarea></code-input>
```
*or*
```HTML
<code-input language="HTML" placeholder="Type code here" template="syntax-highlighted" onchange="console.log('Your code is', this.value)">&lt; href='https://github.com/WebCoder49/code-input'>code-input&lt;/a></code-input>
<code-input language="HTML" template="syntax-highlighted" onchange="console.log('Your code is', this.value)"><textarea code-input-fallback placeholder="Type code here">&lt; href='https://github.com/WebCoder49/code-input'>code-input&lt;/a></textarea></code-input>
```

> ⚠️ At the moment, you need to set the `--padding` property rather than `padding` for a `code-input` element's CSS. All other properties should work as normal.
Expand Down
71 changes: 50 additions & 21 deletions code-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ code-input {
top: 0;
left: 0;

color: black;
background-color: white;

/* Normal inline styles */
margin: 8px;
--padding: 16px;
Expand All @@ -38,8 +41,9 @@ 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);
min-width: calc(100% - var(--padding, 16px) * 2);
min-height: calc(100% - var(--padding, 16px) * 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;
resize: none;
grid-row: 1;
Expand All @@ -56,8 +60,6 @@ code-input:not(.code-input_pre-element-styled) pre, code-input.code-input_pre-el
/* Remove all margin and padding from others */
margin: 0px!important;
padding: 0px!important;
width: 100%;
height: 100%;
}

code-input textarea, code-input pre, code-input pre * {
Expand Down Expand Up @@ -91,7 +93,7 @@ code-input textarea, code-input pre {

/* Move the textarea in front of the result */

code-input textarea {
code-input textarea:not([code-input-fallback]) {
z-index: 1;
}
code-input pre {
Expand All @@ -100,7 +102,7 @@ code-input pre {

/* Make textarea almost completely transparent, except for caret and placeholder */

code-input textarea {
code-input textarea:not([code-input-fallback]) {
color: transparent;
background: transparent;
caret-color: inherit!important; /* Or choose your favourite color */
Expand All @@ -123,7 +125,7 @@ code-input textarea {
outline: none!important;
}
code-input:has(textarea:focus):not(.code-input_mouse-focused) {
outline: 2px solid black;
outline: 2px solid currentColor;
}

/* Before registering give a hint about how to register. */
Expand All @@ -135,19 +137,30 @@ code-input:not(.code-input_registered) {

code-input:not(.code-input_registered)::after {
/* Display message to register */
content: "Use codeInput.registerTemplate to set up.";
content: "No-JavaScript fallback. For highlighting and plugins: as a user use a newer browser/enable JavaScript support; as a developer use codeInput.registerTemplate.";
display: block;
position: absolute;
bottom: var(--padding);
left: var(--padding);
width: calc(100% - 2 * var(--padding));
bottom: 0;
left: var(--padding, 16px);
width: calc(100% - 2 * var(--padding, 16px));
overflow-x: auto;

border-top: 1px solid grey;
outline: var(--padding) solid white;
background-color: white;
border-top: 1px solid currentColor;
outline-top: 0;
background-color: inherit;
color: inherit;

margin: 0;
padding: 0;
height: 2em;
}

code-input:not(.code-input_loaded) pre, code-input:not(.code-input_loaded) textarea {
code-input:not(.code-input_registered) textarea {
/* Don't overlap with message */
min-height: calc(100% - var(--padding, 16px) * 2 - 2em);
}

code-input:not(.code-input_loaded) pre, code-input:not(.code-input_loaded) textarea:not([code-input-fallback]) {
opacity: 0;
}

Expand All @@ -166,12 +179,16 @@ code-input .code-input_dialog-container {

margin: 0;
padding: 0;
width: 100%;
height: 0;
width: 100%;

/* 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, 16px) - 2px);
}

[dir=rtl] code-input .code-input_dialog-container, code-input[dir=rtl] .code-input_dialog-container {
left: unset;
right: 0;
Expand Down Expand Up @@ -200,16 +217,28 @@ code-input:has(pre[dir=rtl]) .code-input_dialog-container .code-input_keyboard-n
right: 0;
}

code-input:not(:has(textarea:focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions,
code-input:not(:has(textarea:not([code-input-fallback]):focus)) .code-input_dialog-container .code-input_keyboard-navigation-instructions,
code-input.code-input_mouse-focused .code-input_dialog-container .code-input_keyboard-navigation-instructions,
code-input .code-input_dialog-container .code-input_keyboard-navigation-instructions:empty {
/* When not keyboard-focused / no instructions don't show instructions */
display: none;
}

/* Things with padding when instructions are present */
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;
code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused) textarea,
code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):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:not([code-input-fallback]):focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre {
padding-top: calc(var(--padding, 16px) + 3em)!important;
}
code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):focus):not(.code-input_mouse-focused) textarea, code-input:not(:has(.code-input_keyboard-navigation-instructions:empty)):has(textarea:not([code-input-fallback]):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:not([code-input-fallback]):focus):not(.code-input_mouse-focused).code-input_pre-element-styled pre {
min-height: calc(100% - var(--padding, 16px) * 2 - 3em);
}

/* No JavaScript fallback - styles to override all previous */

code-input textarea[code-input-fallback] {
overflow: auto;
background-color: inherit;
color: inherit;
height: max-content;
}
32 changes: 11 additions & 21 deletions code-input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export namespace plugins {
* Create an auto-close brackets plugin to pass into a template
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
*/
constructor(bracketPairs: Object);
constructor(bracketPairs?: Object);
}
// ESM-SUPPORT-END-PLUGIN-auto-close-brackets Do not (re)move this - it's needed for ESM generation

Expand All @@ -103,9 +103,9 @@ export namespace plugins {
class Autocomplete extends Plugin {
/**
* Pass in a function to create a plugin that displays the popup that takes in (popup element, textarea, textarea.selectionEnd).
* @param {(popupElement: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number) => void} updatePopupCallback a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
* @param {(popupElement: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number, selectionStart?: number) => void} updatePopupCallback a function to display the popup that takes in (popup element, textarea, textarea.selectionEnd).
*/
constructor(updatePopupCallback: (popupElem: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number) => void);
constructor(updatePopupCallback: (popupElem: HTMLElement, textarea: HTMLTextAreaElement, selectionEnd: number, selectionStart?: number) => void);
}
// ESM-SUPPORT-END-PLUGIN-autocomplete Do not (re)move this - it's needed for ESM generation

Expand All @@ -120,21 +120,6 @@ export namespace plugins {
}
// ESM-SUPPORT-END-PLUGIN-autodetect Do not (re)move this - it's needed for ESM generation

// E doesn't exist? SM-SUPPORT-START-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation
/**
* Debounce the update and highlighting function
* https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
* Files: debounce-update.js
*/
class DebounceUpdate extends Plugin {
/**
* Create a debounced update plugin to pass into a template.
* @param {Number} delayMs Delay, in ms, to wait until updating the syntax highlighting
*/
constructor(delayMs: number);
}
// E doesn't exist? SM-SUPPORT-END-PLUGIN-debounce-update Do not (re)move this - it's needed for ESM generation

// ESM-SUPPORT-START-PLUGIN-find-and-replace Do not (re)move this - it's needed for ESM generation
/**
* Add Find-and-Replace (Ctrl+F for find, Ctrl+H for replace by default) functionality to the code editor.
Expand Down Expand Up @@ -189,10 +174,15 @@ export namespace plugins {
* @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
* @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the English text.
*/
constructor(useCtrlG: boolean,
constructor(useCtrlG?: boolean,
instructionTranslations?: {
closeDialog?: string;
input?: string;
guidanceFormat?: string;
guidanceLineRange?: (current:Number, max: Number) => string;
guidanceColumnRange?: (line: Number, current: Number, max: Number) => string;
guidanceValidLine?: (line: Number) => string;
guidanceValidColumn?: (line: Number, column: Number) => string;
});
/**
* Show a search-like dialog prompting line number.
Expand All @@ -217,7 +207,7 @@ export namespace plugins {
* @param {boolean} escTabToChangeFocus Whether pressing the Escape key before (Shift+)Tab should make this keypress focus on a different element (Tab's default behaviour). You should always either enable this or use this plugin's disableTabIndentation and enableTabIndentation methods linked to other keyboard shortcuts, for accessibility.
* @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the English text.
*/
constructor(defaultSpaces?: boolean, numSpaces?: Number, bracketPairs?: Object, escTabToChangeFocus?: boolean, instructionTranslations?: {
constructor(defaultSpaces?: boolean, numSpaces?: Number, bracketPairs?: Object, escTabToChangeFocus?: boolean, instructionTwranslations?: {
tabForIndentation?: string;
tabForNavigation?: string;
});
Expand Down Expand Up @@ -270,7 +260,7 @@ export namespace plugins {
* @param {string} selectedClass The CSS class that will be present on tokens only when they are part of the selected text in the `<code-input>` element. Defaults to "code-input_select-token-callbacks_selected".
* @returns {TokenSelectorCallbacks} A new TokenSelectorCallbacks instance that encodes this behaviour.
*/
static createClassSynchronisation(selectedClass: string): codeInput.plugins.SelectTokenCallbacks.TokenSelectorCallbacks;
static createClassSynchronisation(selectedClass?: string): codeInput.plugins.SelectTokenCallbacks.TokenSelectorCallbacks;
}
}
// ESM-SUPPORT-END-PLUGIN-select-token-callbacks Do not (re)move this - it's needed for ESM generation
Expand Down
53 changes: 30 additions & 23 deletions code-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,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;

/**
Expand Down Expand Up @@ -519,14 +518,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");
}

Expand Down Expand Up @@ -614,16 +605,34 @@ var codeInput = {

this.pluginEvt("beforeElementsAdded");

const fallbackTextarea = this.querySelector("textarea[code-input-fallback]");
let value;
if(fallbackTextarea) {
// Fallback textarea exists
// Sync attributes; existing code-input attributes take priority
let textareaAttributeNames = fallbackTextarea.getAttributeNames();
for(let i = 0; i < textareaAttributeNames.length; i++) {
const attr = textareaAttributeNames[i];
if(!this.hasAttribute(attr)) {
this.setAttribute(attr, fallbackTextarea.getAttribute(attr));
}
}
// Sync value
value = fallbackTextarea.value;
} else {
value = this.unescapeHtml(this.innerHTML);
}
value = value || this.getAttribute("value") || "";

// First-time attribute sync
let lang = this.getAttribute("language") || this.getAttribute("lang");
let placeholder = this.getAttribute("placeholder") || this.getAttribute("language") || this.getAttribute("lang") || "";
let value = this.unescapeHtml(this.innerHTML) || this.getAttribute("value") || "";
// Value attribute deprecated, but included for compatibility
const lang = this.getAttribute("language") || this.getAttribute("lang");
const placeholder = this.getAttribute("placeholder") || lang || "";


this.initialValue = value; // For form reset

// Create textarea
let textarea = document.createElement("textarea");
const textarea = document.createElement("textarea");
textarea.placeholder = placeholder;
if(value != "") {
textarea.value = value;
Expand All @@ -642,9 +651,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
Expand Down Expand Up @@ -868,22 +875,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 {
Expand Down Expand Up @@ -943,10 +948,12 @@ var codeInput = {
if (val === null || val === undefined) {
val = "";
}

// Save in editable textarea element
this.textareaElement.value = val;
// Trigger highlight
this.scheduleHighlight();

return val;
}

Expand Down
Loading