Компонент BlokEditor для React, Vue и Angular
Компонент-редактор «всё в одном», который поставляют адаптеры фреймворков. Это <BlokEditor> в @bloklabs/react и @bloklabs/vue и <blok-editor> (BlokEditorComponent) в @bloklabs/angular.
React и Vue принимают любую опцию конфигурации редактора как проп. Неизвестные пропы и атрибуты они пробрасывают на контейнерный div.
Angular устроен иначе. Он объявляет фиксированный набор @Input(): tools, data, readOnly, hideToolbar, toolbarPosition, inlineToolbar, theme, width, placeholder, styleTokens, i18n, autofocus, migrations, onBeforeRender, onBeforePaste и onError.
Все остальные ключи конфигурации задаются через запасной вход [config]. Это sanitizer, minHeight, defaultBlock, dataModel, link, linkPaste, tunes, user, resolveUser, uploader, server, ticket, persistence, collaboration, notifier, logLevel, onEnter, onSubmit, scrollToBlock и так далее. Ещё Angular не пробрасывает атрибуты хоста на контейнерный div.
Живой экземпляр Blok в каждом адаптере вы читаете по-своему.
- В React это ref/onReady.
- Во Vue это
instanceна шаблонной ссылке или эмит@ready. - В Angular это сигнал
instanceили output(ready).
Пропы ниже описывают специфичную для адаптеров поверхность. Остальное совпадает с опциями раздела «Конфигурация».
Как получить экземпляр редактора
Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;
// Call any API method on it.
editor.caret.setToLastBlock('end');Методы
useBlok(config, deps?)
Blok | null (React) | Ref<Blok | null> (Vue)Раздельный путь монтирования, который стоит за <BlokEditor>: экземпляр вы создаёте сами и передаёте ему точку монтирования. useBlok принимает ТЕ ЖЕ опции, что и компонент. Его тип конфигурации UseBlokConfig: это BlokConfig без holder (элементом монтирования владеет адаптер) плюс width уровня адаптера.
Сам UseBlokConfig описывает подмножество, которое остаётся реактивным после монтирования: readOnly, hideToolbar, toolbarPosition, inlineToolbar, autofocus, theme, width, placeholder, style.tokens, i18n и data синхронизируются на месте на том же экземпляре. Любая другая опция считывается один раз при создании редактора.
Пока редактора нет (SSR, первый рендер), возвращается null.
- React принимает вторым аргументом СПИСОК зависимостей
deps. - Vue принимает реактивный источник конфигурации (ref или геттер) и ОДИН
recreateKeyвторым аргументом. - Эквивалент в Angular: директива
[blokContent](BlokContentDirective).- Она строит экземпляр в своём хост-элементе и отдаёт его через сигнал
instance/ output(ready).
- Она строит экземпляр в своём хост-элементе и отдаёт его через сигнал
import { useBlok, BlokContent } from '@bloklabs/react';
import { Header, Paragraph } from '@bloklabs/core/tools';
export function Editor() {
const editor = useBlok({
tools: { paragraph: Paragraph, header: Header },
readOnly: false,
});
return <BlokContent editor={editor} className="my-editor" />;
}BlokContent
React/Vue componentТочка монтирования для экземпляра, созданного через useBlok. Рендерит <div> и переносит в него откреплённый holder редактора.
Единственный собственный проп: editor: Blok | null (BlokContentProps). Передайте null до появления экземпляра, и компонент отрисует пустой контейнер.
В React он ещё расширяет React.HTMLAttributes<HTMLDivElement>, поэтому className, id и остальное пробрасываются на этот div, и ref пробрасывается туда же. Аналог в Angular: директива [blokContent]. Она создаёт экземпляр сама, а не получает его.
import { useBlok, BlokContent } from '@bloklabs/react';
const editor = useBlok({ tools });
// `editor` is null until the instance exists — BlokContent handles that
<BlokContent editor={editor} className="prose" />provideBlok(defaults)
void | EnvironmentProvidersРегистрирует значения по умолчанию для Blok на уровне всего приложения. Каждый редактор внутри наследует общий реестр инструментов, тему или конфигурацию i18n и не повторяет их для каждого экземпляра.
- В React это
<BlokProvider defaults={…}>, а прочитать значения обратно помогаетuseBlokDefaults(). - Во Vue это
provideBlok(defaults), вызванный вsetupродителя; за ним стоит ключ внедренияBLOK_DEFAULT_CONFIG, а для чтения естьuseBlokDefaults(). - В Angular это
provideBlok(defaults), который возвращаетEnvironmentProvidersдля массиваproviders; за ним стоит токен внедренияBLOK_DEFAULT_CONFIG.
Правило слияния одинаково во всех трёх: заданное значение конфигурации экземпляра перекрывает значение по умолчанию. Исключение составляет tools. Там два реестра сливаются: общий реестр складывается с добавлениями конкретного экземпляра, а не заменяется ими.
// React
import { BlokProvider } from '@bloklabs/react';
<BlokProvider defaults={{ theme: 'dark', tools: sharedTools }}>
<App />
</BlokProvider>
// Vue — inside a parent component's setup()
import { provideBlok } from '@bloklabs/vue';
provideBlok({ theme: 'dark', tools: sharedTools });
// Angular
import { provideBlok } from '@bloklabs/angular';
bootstrapApplication(AppComponent, {
providers: [provideBlok({ theme: 'dark', tools: sharedTools })],
});import { useState } from 'react';
import { BlokEditor } from '@bloklabs/react';
import { Header, Paragraph, List } from '@bloklabs/core/tools';
import type { OutputData } from '@bloklabs/core';
export function Editor() {
const [data, setData] = useState<OutputData>();
// data + onSave form a controlled component: onSave fires (debounced)
// with the serialized document; echoing it back is deduped and
// caret-stable, while genuine external data changes re-render in place.
return (
<BlokEditor
tools={{ paragraph: Paragraph, header: Header, list: List }}
data={data}
onSave={setData}
theme="auto"
className="my-editor"
/>
);
}Компонент BlokEditor
| Свойство | Описание | |
|---|---|---|
tools | Record<string, ToolConstructable | ToolSettings> | Регистрируемые блочные инструменты. Только React: функции в любом месте конфигурации инструмента (например, колбэк загрузчика) автоматически перепривязываются к последнему рендеру. Поэтому инлайновые замыкания безопасны, и запись в У Vue и Angular аналога нет. Там замыкание в конфигурации инструмента захватывается при создании редактора и потом устаревает. Держите его в стабильном ref/поле или форсируйте пересборку сменой |
data | OutputData | LooseOutputData | null | Содержимое редактора (реактивное). Задаёт начальный документ. После монтирования новое содержимое перерисовывается на месте на том же экземпляре и никогда не пересоздаёт редактор. Это касается и переходов к пустому документу и обратно. Обновления дедуплицируются той же структурной проверкой, что и Императивные вызовы живут в том же мире. Документ целиком, равный Нестрогие backend-DTO принимаются как есть. Angular расширяет тип до |
onSave | (data: OutputData, api: API) => void | Выходная половина контролируемого компонента. Срабатывает (с дебаунсом) с полным сериализованным документом при каждом изменении содержимого, поэтому вручную опрашивать save() не нужно. Связка onSave={setData} безопасна и не зацикливается.
Можно также использовать |
onChange | (api: API, event: BlockMutationEvent | BlockMutationEvent[]) => void | Низкоуровневые события мутаций (блок добавлен, изменён, перемещён, удалён). Нужны, когда важна гранулярность отдельных мутаций, а не сериализованный вывод. Пакет мутаций приходит МАССИВОМ, поэтому проверяйте Два позиционных аргумента, это арность React. Эмит |
onReady | (editor: Blok) => void | Вызывается с живым экземпляром Blok ровно один раз на экземпляр редактора. Срабатывает после фиксации пробрасываемого ref, поэтому ref.current к этому моменту уже заполнен. Редактор пересоздаётся, и onReady срабатывает снова, только когда меняются deps/recreateKey или компонент перемонтируется. Изменения data, включая переходы к пустому документу и обратно, перерисовываются на месте и никогда не вызывают его повторно. Во Vue и Angular это эмит/output |
deps / recreateKey | DependencyList (React) | unknown (Vue, Angular) | Значения, при смене идентичности которых редактор уничтожается и создаётся заново. Нужны для структурной конфигурации вроде классов инструментов.
Держите каждое значение референциально стабильным. Функции внутри конфигурации инструментов сюда НЕ входят в React: они автоматически перепривязываются к последнему рендеру. |
readOnly | boolean | ReadOnlyModeConfig | Режим «только чтение». Реактивный: после монтирования переключается на месте, без перемонтирования. |
theme | 'light' | 'dark' | 'auto' | Цветовая тема (реактивная). Не оборачивайте компонент в styled() или в HOC, который резервирует проп theme. В таком случае он не дойдёт до редактора. |
onThemeChange | (resolvedTheme: 'light' | 'dark') => void | Вызывается с разрешённой темой при каждом её изменении, например когда 'auto' следует за ОС. Во Vue и Angular это эмит |
width | 'narrow' | 'full' | Режим ширины содержимого (реактивный). После монтирования синхронизируется через editor.width.set(). Императивную часть (get / set / toggle) смотрите в Width API. |
style | BlokConfig['style'] | Конфигурация стилей. Работает замена, а не слияние: передавайте палитру целиком, потому что убранные из неё токены перестают применяться. У Angular входа |
i18n | BlokConfig['i18n'] | Конфигурация интернационализации (реактивная). Изменённые
|
locale | string | Только React. Нейтральное к библиотекам сокращение BCP-47 для Если заданы оба, оно ПОБЕЖДАЕТ Во Vue и Angular такого пропа нет. Там передавайте локаль внутри пропа или входа |
autofocus | boolean | Сфокусировать редактор после монтирования. |
placeholder | string | false | Плейсхолдер, который получает каждый блок инструмента по умолчанию, а не только первый блок. Со встроенным параграфом он показывается, пока блок пуст и находится в фокусе. Что именно |
onBlocksRendered | (payload: BlocksRenderedPayload) => void | Вызывается после завершения пакетного рендера (событие ядра blocks:rendered). Это декларативный аналог editor.on('blocks:rendered', …). Во Vue и Angular это эмит |
onBlockRendered | (payload: BlockRenderedPayload) => void | Вызывается для каждого блока, отрендеренного в DOM (событие ядра block:rendered). Во Vue и Angular это эмит |
ref | Ref<Blok | null> | Пробрасывается к живому экземпляру Blok для императивных вызовов (save, render, blocks, caret, …). До монтирования редактора он равен null, поэтому вызовы должны проверять ref.current. Для частых сокращений без таких проверок есть Если содержимое вы задаёте ещё и пропом |
className, id, … | HTMLAttributes<HTMLDivElement> | Любой проп, не являющийся опцией конфигурации редактора, пробрасывается на контейнерный div. Стилизуйте редактор через className (style сохраняет своё значение из конфигурации редактора). |