React-компонент BlokEditor
Компонент-редактор «всё в одном», который поставляют адаптеры фреймворков: <BlokEditor> в @bloklabs/react и @bloklabs/vue, <blok-editor> (BlokEditorComponent) в @bloklabs/angular. React и Vue принимают любую опцию конфигурации редактора как проп и пробрасывают неизвестные пропы/атрибуты на контейнерный div. Angular устроен иначе: он объявляет ограниченный набор `@Input()` — tools, data, readOnly, hideToolbar, inlineToolbar, theme, width, placeholder, styleTokens, i18n, autofocus, migrations, onBeforeRender, onBeforePaste, onError — плюс запасной вход `[config]` для всех остальных ключей конфигурации (sanitizer, minHeight, defaultBlock, dataModel, link, linkPaste, tunes, user, resolveUser, uploader, notifier, logLevel, onEnter, onSubmit, scrollToBlock, …) и не пробрасывает атрибуты хоста на контейнерный div. Живой экземпляр Blok читается через ref/onReady (React), через `instance` на шаблонной ссылке или эмит `@ready` (Vue) и через сигнал `instance` или output `(ready)` (Angular). Пропы ниже описывают специфичную для адаптеров поверхность; остальное совпадает с опциями раздела «Конфигурация».
Как получить экземпляр редактора
Методы ниже вызываются на редакторе, созданном через 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)The split mount path behind `<BlokEditor>`: create the instance yourself and hand it a mount point. `useBlok` takes the SAME options as the component — its config type is `UseBlokConfig`, which is `BlokConfig` minus `holder` (the adapter owns the mount element) plus an adapter-level `width`. The reactive-after-mount subset is documented on `UseBlokConfig` itself: `readOnly`, `hideToolbar`, `toolbarPosition`, `inlineToolbar`, `autofocus`, `theme`, `width`, `placeholder`, `style.tokens`, `i18n` and `data` sync in place on the same instance; every other option is consumed once at editor creation. It returns null until the editor exists (SSR, first render). React takes a `deps` dependency LIST as the second argument; Vue takes a reactive config source (ref or getter) and a SINGLE `recreateKey` as the second argument. Angular's equivalent is the `[blokContent]` directive (`BlokContentDirective`), which builds the instance into its own host element and exposes it as the `instance` signal / `(ready)` output.
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 componentThe mount point for an instance created by `useBlok`. Renders a `<div>` and adopts the editor's detached holder into it. Its only own prop is `editor: Blok | null` (`BlokContentProps`) — pass null before the instance exists and it simply renders the empty container. In React it also extends `React.HTMLAttributes<HTMLDivElement>`, so `className`, `id` and the rest are forwarded to that div, and it forwards a ref to it. Angular's counterpart is the `[blokContent]` directive, which creates the instance itself rather than receiving one.
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 | EnvironmentProvidersRegisters app-wide Blok defaults so every editor beneath it inherits a shared tools registry, theme or i18n config instead of repeating them per instance. React spells it as `<BlokProvider defaults={…}>` (with `useBlokDefaults()` to read them back), Vue as `provideBlok(defaults)` called in a parent's `setup` (backed by the `BLOK_DEFAULT_CONFIG` injection key, with `useBlokDefaults()` to read), and Angular as `provideBlok(defaults)` returning `EnvironmentProviders` for a `providers` array (backed by the `BLOK_DEFAULT_CONFIG` injection token). Merge rule, identical in all three: a defined per-instance config value overrides the default, EXCEPT `tools`, where the two registries are merged — the shared registry composes with per-instance additions rather than being replaced.
// 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: функции в любом месте конфигурации инструмента (например, колбэк загрузчика) автоматически перепривязываются к последнему рендеру, поэтому инлайновые замыкания безопасны, и запись в `deps` нужна только при смене КЛАССА инструмента. У Vue и Angular аналога нет — замыкание в конфигурации инструмента захватывается при создании редактора и устаревает, поэтому держите его в стабильном ref/поле либо форсируйте пересборку сменой `recreateKey`. |
data | OutputData | LooseOutputData | null | Содержимое редактора (реактивное). Задаёт начальный документ; после монтирования новое содержимое — включая переходы к пустому документу и обратно — перерисовывается на месте на том же экземпляре (редактор никогда не пересоздаётся). Обновления дедуплицируются той же структурной проверкой, что и `equalsOutputData`, поэтому возврат собственного вывода редактора никогда не сбивает каретку — даже если слой хранения его обрезал (новые `time`/`version`, потерянные id и отсутствие метки `lastEditedAt` всё равно считаются отсутствием изменений). Императивные вызовы живут в том же мире: `useBlokHandle().clear()` / `.render()` в React и `BlokEditorComponent.render()` в Angular обновляют этот базовый снимок сразу после выполнения, поэтому возврат `data` к документу, который редактор сам же и выдал, приводит к перерисовке, а не отбрасывается как эхо. Документ целиком, равный `null`, — это контролируемое значение «очистить до пустого» (при собственном вызове render() пропускайте его через `toRenderableData`), а нестрогие backend-DTO принимаются как есть. Angular расширяет тип до `… | undefined`; проп Vue объявлен как `PropType<OutputData>` и является узким исключением. |
onSave | (data: OutputData, api: API) => void | Выходная половина контролируемого компонента: срабатывает (с дебаунсом) с полным сериализованным документом при каждом изменении содержимого — вручную опрашивать save() не нужно. Связка onSave={setData} безопасна и не зацикливается. Арность `(data, api)` — это React, где проп передаётся прямо в конфигурацию ядра. Vue отображает его на эмит `save`, а Angular — на output `save`, и оба несут только `OutputData`: `@save="(data) => …"` / `(save)="…"` — либо используйте `v-model:data` / `[(data)]`, за которыми стоят эмит `update:data` во Vue и output `dataChange` в Angular. |
onChange | (api: API, event: BlockMutationEvent | BlockMutationEvent[]) => void | Низкоуровневые события мутаций (блок добавлен/изменён/перемещён/удалён) — когда нужна гранулярность отдельных мутаций, а не сериализованный вывод. Пакет мутаций приходит МАССИВОМ, поэтому проверяйте `Array.isArray(event)` перед чтением `event.detail`. Два позиционных аргумента — это арность React; эмит `change` во Vue и output `change` в Angular отдают вместо этого ОДИН объект — `@change="({ api, event }) => …"` / `(change)="…"` с `$event.api` и `$event.event`. |
onReady | (editor: Blok) => void | Вызывается с живым экземпляром Blok ровно один раз на экземпляр редактора. Срабатывает после фиксации пробрасываемого ref, поэтому ref.current к этому моменту уже заполнен. Редактор пересоздаётся (и onReady срабатывает снова), только когда меняются deps/recreateKey или компонент перемонтируется — изменения data, включая переходы к пустому документу и обратно, перерисовываются на месте и никогда его не вызывают повторно. Во Vue и Angular это эмит/output `ready` (`@ready` / `(ready)`). |
deps / recreateKey | DependencyList (React) | unknown (Vue, Angular) | Значения, при смене идентичности которых редактор уничтожается и создаётся заново (для структурной конфигурации вроде классов инструментов). React принимает массив — `deps` — и пересоздаёт редактор при смене идентичности любого элемента. Vue (`:recreate-key`) и Angular ([recreateKey]) принимают вместо этого ОДНО значение и пересоздают редактор при смене его идентичности; передавайте новый литерал объекта/массива или увеличенный счётчик. `deps` во Vue и Angular не существует. Держите каждое значение референциально стабильным. Функции внутри конфигурации инструментов сюда НЕ входят в React — они автоматически перепривязываются к последнему рендеру. |
readOnly | boolean | ReadOnlyModeConfig | Режим «только чтение». Реактивный: после монтирования переключается на месте, без перемонтирования. |
theme | 'light' | 'dark' | 'auto' | Цветовая тема (реактивная). Не оборачивайте компонент в styled() или HOC, резервирующий проп theme — он не дойдёт до редактора. |
onThemeChange | (resolvedTheme: 'light' | 'dark') => void | Вызывается с разрешённой темой при каждом её изменении (например, когда 'auto' следует за ОС). Во Vue и Angular это эмит `theme-change` / output `themeChange` (`@theme-change` / `(themeChange)`). |
width | 'narrow' | 'full' | Режим ширины содержимого (реактивный). После монтирования синхронизируется через editor.width.set(). |
style | BlokConfig['style'] | Конфигурация стилей. `style.tokens` реактивен: изменённые переопределения `--blok-*` синхронизируются на месте после монтирования через editor.tokens.set() (с дедупликацией по глубокому сравнению), поэтому переключателю светлой/тёмной темы на стороне хоста перемонтирование не нужно. Семантика замены — передавайте палитру целиком; токены, убранные из неё, перестают применяться. У Angular входа `style` нет: он предоставляет только `style.tokens` — как отдельный вход `[styleTokens]` (`Record<string, string>`). Остальные ключи `style` — `fontSize`, `contentAlign`, `nativeSelection` — задаются через запасной вход Angular `[config]`. |
i18n | BlokConfig['i18n'] | Конфигурация интернационализации (реактивная). Изменённые `locale`, `messages` или `direction` синхронизируются на месте после монтирования через editor.i18n.update() (с дедупликацией по глубокому сравнению), поэтому переключатель языка меняет подписи редактора без перемонтирования — каретка, фокус, выделение и история отмен сохраняются. Исключение — `defaultLocale`: он читается только при создании. В Angular это вход [i18n]. |
locale | string | Только React. Нейтральное к библиотекам сокращение BCP-47 для `i18n.locale`: значение вкладывается в конфигурацию i18n и применяется на месте через editor.i18n.update({ locale }), поэтому переключение языка сохраняет каретку, фокус и историю отмен. При одновременном указании оно ПОБЕЖДАЕТ `i18n.locale`. Используйте его вместе с `getDirection` / `normalizeLocale`, реэкспортируемыми из @bloklabs/react, чтобы самостоятельно вычислять `dir` и проверять теги. Во Vue и Angular такого пропа нет — передавайте локаль внутри пропа/входа `i18n`. |
autofocus | boolean | Сфокусировать редактор после монтирования. |
placeholder | string | false | Текст-заполнитель, который получает каждый блок инструмента по умолчанию, — не только первый блок; со встроенным параграфом он показывается, пока блок пуст и в фокусе. Что именно `false` отключает (и чего не отключает), см. в таблице «Конфигурация», а Placeholder API позволяет менять его на лету через editor.placeholder.set(). |
onBlocksRendered | (payload: BlocksRenderedPayload) => void | Вызывается после завершения пакетного рендера (событие ядра blocks:rendered) — декларативный аналог editor.on('blocks:rendered', …). Во Vue и Angular это эмит `blocks-rendered` / output `blocksRendered`. |
onBlockRendered | (payload: BlockRenderedPayload) => void | Вызывается для каждого блока, отрендеренного в DOM (событие ядра block:rendered). Во Vue и Angular это эмит `block-rendered` / output `blockRendered`. |
ref | Ref<Blok | null> | Пробрасывается к живому экземпляру Blok для императивных вызовов (save, render, blocks, caret, …). Равен null до монтирования редактора, поэтому вызовы должны проверять ref.current. Если содержимое также задаётся пропом `data`, для clear()/render() предпочитайте хендл `useBlokHandle()` из @bloklabs/react: он обновляет контролируемый базовый снимок, тогда как прямой вызов ref.current.render()/clear() меняет содержимое в обход адаптера — и следующее значение `data`, равное ранее выданному редактором документу, будет отброшено как эхо. |
className, id, … | HTMLAttributes<HTMLDivElement> | Любой проп, не являющийся опцией конфигурации редактора, пробрасывается на контейнерный div. Стилизуйте редактор через className (style сохраняет своё значение из конфигурации). |