UNPKG

@itrocks/contenteditable

Version:

Respectful contenteditable: avoids <div>, respects white-space: pre* styles line breaks

210 lines (146 loc) 8.11 kB
[![npm version](https://img.shields.io/npm/v/@itrocks/contenteditable?logo=npm)](https://www.npmjs.org/package/@itrocks/contenteditable) [![npm downloads](https://img.shields.io/npm/dm/@itrocks/contenteditable)](https://www.npmjs.org/package/@itrocks/contenteditable) [![GitHub](https://img.shields.io/github/last-commit/itrocks-ts/contenteditable?color=2dba4e&label=commit&logo=github)](https://github.com/itrocks-ts/contenteditable) [![issues](https://img.shields.io/github/issues/itrocks-ts/contenteditable)](https://github.com/itrocks-ts/contenteditable/issues) [![discord](https://img.shields.io/discord/1314141024020467782?color=7289da&label=discord&logo=discord&logoColor=white)](https://25.re/ditr) # contenteditable Respectful `contenteditable`: avoids `<div>`, respects `white-space: pre*` styles line breaks. ## Installation ```bash npm install @itrocks/contenteditable ``` ## Usage ```ts 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](https://github.com/itrocks-ts/contenteditable). ## 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-line` or `pre-wrap`: inserts a `\n` character, - other `white-space` values 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 computed `white-space` starts with `pre`, `<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 ```ts element: HTMLEditableElement ``` The [element](#parameters) associated with the `ContentEditable`, extended as an `HTMLEditableElement` with an `editable` property referencing the `ContentEditable` instance. ### ContentEditable() constructor ```ts new ContentEditable(element) ``` Applies the `@itrocks/contenteditable` feature to an element. Invokes [activate()](#activate) for initialisation. Starts a [mutation observer](http://developer.mozilla.org/docs/Web/API/MutationObserver) to: - automatically invoke [deactivate()](#deactivate) when the `contenteditable` attribute is removed, - automatically invoke [activate()](#activate) when the `contenteditable` attribute is re-added. #### Parameters - `element` An [HTMLElement](https://developer.mozilla.org/docs/Web/API/HTMLElement) to apply the feature to. ### activate() ```ts activate() ``` Sets the `contenteditable` attribute on your element if it's not already set, Enables a [keydown event listener](https://developer.mozilla.org/docs/Web/API/Element/keydown_event) to keep line breaks in sync with a clean content structure following the [line break matching white-space rule](#line-break-matching-white-space-rule). ### br() ```ts br() ``` Determines and returns the current line break string used for your `contenteditable` element, in accordance with the [line break matching white-space rule](#line-break-matching-white-space-rule). ### brNode() ```ts brNode() ``` Creates a node that matches the current line break string. Result is either a [TextNode](https://developer.mozilla.org/docs/Web/API/Document/createTextNode) containing '\n' or an [HTMLBRElement](https://developer.mozilla.org/docs/Web/API/HTMLBRElement). ### deactivate() ```ts deactivate() ``` Removes the `contenteditable` attribute from your element. Deactivates the now unnecessary [keydown event listener](https://developer.mozilla.org/docs/Web/API/Element/keydown_event). If the value contains no line break, removes the trailing `<br>`. ### onKeyDown() ```ts onKeyDown(event) ``` Handles the [keydown](https://developer.mozilla.org/docs/Web/API/Element/keydown_event) 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)](https://en.wikipedia.org/wiki/Aspect-oriented_programming) 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? 1. **Control execution order:** Adding a [`keydown`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) event listener via `addEventListener` does not provide fine-grained control over execution priority relative to the library's built-in handling. Extending `onKeyDown` allows you to execute your logic either **before**, **after**, or **instead of** the library's processing of the event. 2. **Avoid unintended interference:** Preventing the library's default `'Enter'` key behaviour by stopping event propagation (`event.stopPropagation()` or `event.preventDefault()`) may inadvertently interfere with other components or listeners unrelated to this library. By extending `onKeyDown`, 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: ```ts 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() ```ts 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](#line-break-matching-white-space-rule)).