@itrocks/contenteditable
v0.1.0
Published
Respectful contenteditable: avoids <div>, respects white-space: pre* styles line breaks
Maintainers
Readme
contenteditable
Respectful contenteditable: avoids <div>, respects white-space: pre* styles line breaks.
Installation
npm install @itrocks/contenteditableUsage
import { ContentEditable } from '@itrocks/contenteditable'
document.querySelectorAll('[contenteditable]')
.forEach(element => new ContentEditable(element))More examples are available in the demo folder of the
GitHub repository.
Behaviour
This feature manages a contenteditable element during typing to maintain clean and consistent text.
The last line break is preserved as well.
Line break matching white-space rule
The line break characters inserted in your contenteditable element
depend on its white-space style attribute:
pre,pre-lineorpre-wrap: inserts a\ncharacter,- other
white-spacevalues result in a<br>element.
Any content ending with a line break will have a trailing <br> added to ensure proper display in your browser.
Avoiding block elements in content
This module deliberately avoids inserting block-level elements such as <div> inside the editable content.
contenteditable elements tend to introduce <div> wrappers when handling line breaks,
especially when pressing Enter.
This leads to inconsistent DOM structures, harder text processing,
and unexpected rendering differences across browsers.
@itrocks/contenteditable enforces a simpler and predictable model:
- Line breaks are represented only by newline characters (
\n) when the computedwhite-spacestarts withpre,<br>elements otherwise - No block-level elements are created inside the editable content
- The resulting DOM remains flat and text-oriented
This makes the editable content:
- easier to parse and serialise
- consistent across browsers
- closer to plain text semantics while preserving visual line breaks
ContentEditable API
The following properties and methods are publicly available in the ContentEditable class.
element
element: HTMLEditableElementThe element associated with the ContentEditable,
extended as an HTMLEditableElement with an editable property referencing the ContentEditable instance.
ContentEditable() constructor
new ContentEditable(element)Applies the @itrocks/contenteditable feature to an element.
Invokes activate() for initialisation.
Starts a mutation observer to:
- automatically invoke deactivate() when the
contenteditableattribute is removed, - automatically invoke activate() when the
contenteditableattribute is re-added.
Parameters
elementAn HTMLElement to apply the feature to.
activate()
activate()Sets the contenteditable attribute on your element if it's not already set,
Enables a keydown event listener to keep line breaks in sync with a clean content structure following the line break matching white-space rule.
br()
br()Determines and returns the current line break string used for your contenteditable element,
in accordance with the line break matching white-space rule.
brNode()
brNode()Creates a node that matches the current line break string. Result is either a TextNode containing '\n' or an HTMLBRElement.
deactivate()
deactivate()Removes the contenteditable attribute from your element.
Deactivates the now unnecessary keydown event listener.
If the value contains no line break, removes the trailing <br>.
onKeyDown()
onKeyDown(event)Handles the keydown event
for the contenteditable element, ensuring the correct behaviour when the 'Enter' key is pressed.
By default, this method determines the appropriate action for the 'Enter' key
based on the library's implementation of line break management.
Extending onKeyDown
This method is designed to be extensible
using Aspect-Oriented Programming (AOP) principles.
Developers can intercept and augment the behaviour of onKeyDown to customize keyboard event handling
while carefully managing execution priority relative to the library's default behaviour.
This extension can be performed before, after, or even instead of the default behaviour.
Why extend onKeyDown instead of adding a keydown listener?
Control execution order:
Adding akeydownevent listener viaaddEventListenerdoes not provide fine-grained control over execution priority relative to the library's built-in handling. ExtendingonKeyDownallows you to execute your logic either before, after, or instead of the library's processing of the event.Avoid unintended interference:
Preventing the library's default'Enter'key behaviour by stopping event propagation (event.stopPropagation()orevent.preventDefault()) may inadvertently interfere with other components or listeners unrelated to this library. By extendingonKeyDown, you ensure your changes are isolated and do not disrupt other listeners.
Example: Preventing multiline input using AOP
The following example demonstrates how to extend onKeyDown for a specific ContentEditable instance
to prevent the default behaviour of the 'Enter' key when the element does not have a data-multiline attribute:
const contentEditable = new ContentEditable(document.querySelector('#my-editable'))
const superOnKeyDown = contentEditable.onKeyDown
contentEditable.onKeyDown = function (event: KeyboardEvent): void {
if ((event.key === 'Enter') && !this.element?.hasAttribute('data-multiline')) {
event.preventDefault()
return
}
superOnKeyDown.call(this, event)
}Key Points
- By overriding
onKeyDown, you can implement custom behaviours tailored to your requirements. - Ensure you call the original method (
superOnKeyDown) when appropriate to preserve the default functionality, unless you intend to completely replace it. - This approach provides precise control over execution order without impacting unrelated components or listeners.
- Using this pattern, multiple plugins can safely and independently modify the behaviour of the same instance.
This makes it easier to apply different behaviours to individual contentEditable instances
without relying on inheritance or risking conflicts with other plugins.
value()
value()Returns the effective value of the edited content.
This corresponds to the innerHTML of the content,
excluding the trailing <br> added to display the final empty line in the browser
(see Line break matching white-space rule).
