@brandup/ui-richeditor
v1.0.36
Published
Rich text editor over a contenteditable element (bold, italic, strike, underline).
Maintainers
Readme
@brandup/ui-richeditor
Редактор текста на базе contenteditable. Получает в конструкторе элемент, делает его редактируемым и берёт на себя всю работу с вводом: форматирование (жирный, курсив, зачёркивание, подчёркивание), панель инструментов, режим набора, нормализацию пробелов и сериализацию значения в HTML или Markdown.
Реализован на чистом Selection/Range API — без устаревшего document.execCommand, поэтому разметка всегда семантическая и предсказуема между браузерами.
Установка
npm i @brandup/ui-richeditorИспользование
import RichEditor from "@brandup/ui-richeditor";
const elem = document.getElementById("editor") as HTMLElement;
const editor = new RichEditor(elem, {
format: true,
tools: ["bold", "italic", "strike", "underline"],
storage: "html", // или "markdown"
placeholder: "Введите текст",
multiline: true,
});
editor.onChange(({ value }) => console.log(value));Конструктор оборачивает переданный элемент в div.ui-richeditor (к нему привязан UIElement) и делает элемент редактируемым (div.ui-richeditor-input).
Панель форматирования
Панель — общая для всех редакторов (div.ui-richeditor-toolbar). При фокусе редактора она перестраивается под его инструменты, позиционируется над ним и показывается; при потере фокуса — скрывается. Кнопки диспатчат форматирование напрямую активному редактору.
По умолчанию панель живёт в document.body (position: fixed) — это защищает её от обрезки overflow: hidden у родителей. Если задан toolbarContainer, панель монтируется в него и позиционируется относительно него (position: absolute, над контейнером) — например, TextBox передаёт свой контейнер .ui-textbox.
Опции (RichEditorOptions)
| Опция | Тип | Описание |
|---|---|---|
| format | boolean | Включает форматирование и панель инструментов |
| tools | FormatTool[] | Состав инструментов (по умолчанию все) |
| storage | "html" \| "markdown" | Формат сериализации значения (по умолчанию html) |
| markers | Partial<FormatMarkers> | Переопределение markdown-маркеров по инструментам |
| placeholder | string \| null | Текст-заглушка |
| multiline | boolean | Многострочный режим |
| readonly | boolean | Только для чтения — запрещает ввод и изменение текста (выделение и копирование остаются) |
| toolbarContainer | HTMLElement \| null | Контейнер для панели; по умолчанию document.body (position: fixed). Если задан — панель монтируется в него и позиционируется над ним (position: absolute). Контейнер должен быть position: relative |
| value | string | Начальное значение |
| filterChar | (char) => boolean | Хук: false — отклонить вводимый символ |
| filterPaste | (text) => string \| null | Хук: null — отклонить вставку; иначе очищенный текст |
| onReject | () => void | Хук: ввод отклонён (символ/вставка) |
| onEnter | () => void | Хук: Enter в однострочном режиме |
Хуки filterChar/filterPaste/onReject/onEnter позволяют хосту (например, @brandup/ui-textbox) накладывать собственные ограничения — фильтрацию по типу, submit формы, индикацию ошибки — не вмешиваясь в работу редактора.
API
| Член | Описание |
|---|---|
| editable | Редактируемый элемент |
| format, formatTools, formatStorage, formatMarkers, multiline | Параметры экземпляра |
| getValue(): string | Сериализованное значение (по storage) |
| setValue(value: string): void | Установить значение (нормализует, генерирует change) |
| getLength(): number | Длина текста (без учёта переводов строк) |
| focus(): void | Установить фокус |
| onChange(handler) | Подписка на событие richeditor-change |
| destroy(): void | Разворачивает элемент обратно и освобождает ресурсы |
Поведение форматирования
- Формат — переключатель (toggle): повторное применение снимает его.
- Применяется к слову целиком: курсор внутри слова или выделение его части → формат охватывает всё слово; исходное выделение/каретка сохраняются.
- Режим набора: на пустом месте (между пробелами / в пустом поле) кнопка/хоткей включают «ожидающий» формат — он применится к следующему введённому тексту. Сбрасывается при перемещении каретки, клике или потере фокуса.
- Хоткеи
Ctrl/Cmd+B/I/U. Зачёркивание — только кнопкой. - Отмена/повтор:
Ctrl/Cmd+Z— отмена,Ctrl+YилиCtrl/Cmd+Shift+Z— повтор. История форматирования, абзацев, переносов и печати ведётся редактором (нативный undo не видит ручных DOM-правок), поэтому доступна только при включённом форматировании (format: true). Печать коалесится в один шаг отмены по паузе ~300 мс; глубина истории — 100 шагов. - При потере фокуса и после
setValueпробелы нормализуются (схлопывание повторов + обрезка краёв строк).
Многострочный режим: абзацы и переносы
При multiline: true контент структурируется по абзацам:
- Enter → новый абзац (
<p>); - Shift+Enter или Ctrl/Cmd+Enter → мягкий перенос (
<br>) внутри абзаца; - блуждающий текст и
<div>нормализуются в<p>при вводе.
Хвостовой перенос абзаца отбрасывается (это <br>-заполнитель); пустая строка делается отдельным абзацем.
При нормализации (потеря фокуса, setValue, инициализация) пустые абзацы удаляются.
Вставка форматированного текста
При включённом форматировании вставка (paste) сохраняет форматирование из буфера обмена (text/html):
- разметка санитизируется до включённых инструментов (синонимы
STRONG/EM/DEL/INS→ каноническиеb/i/s/u, всё прочее —span, стили, классы,<style>/<script>— отбрасывается, текст сохраняется); - multiline сохраняет абзацы
<p>и мягкие переносы<br>, разбивая текущий абзац по каретке; single-line — инлайн, абзацы/переносы становятся пробелами; - если
text/htmlнет — простая текстовая вставка (как раньше); - хук
filterPasteостаётся в силе: вернулnull— вставка отклоняется; изменил текст (обрезка по длине, фильтр по типу) — форматирование не сохраняется, вставляется очищенный текст.
Вся вставка — один шаг истории (Ctrl+Z откатывает целиком).
Формат хранения
| storage | Хранение | Абзац / мягкий перенос | Маркеры форматирования |
|---|---|---|---|
| html | Санитизированный HTML | <p>…</p> / <br> | <b>, <i>, <s>, <u> |
| markdown | Лёгкая разметка | \n\n / \n | **жирный**, *курсив*, ~~зачёркнутый~~, ++подчёркнутый++ |
Без форматирования (plain) значение хранится как markdown без инструментов: абзацы \n\n, мягкий перенос \n.
Маркеры markdown настраиваются через markers. При разборе применяются по убыванию длины, поэтому длинный маркер срабатывает раньше короткого-префикса. Глубоко вложенные комбинации гарантированно сохраняются только в режиме html.
CSS
Подключается richeditor.less. Цветовые переменные --input-* берутся из @brandup/ui-kit (с fallback-значениями для standalone). Классы: focused на обёртке (поле в фокусе), visible на общей панели (показана), active на кнопке инструмента (формат активен на выделении).
