Skip to content
ФреймворкJavaScript

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). Пропы ниже описывают специфичную для адаптеров поверхность; остальное совпадает с опциями раздела «Конфигурация».

Обновлено 17 июл. 2026 г.Редактировать на GitHub

Как получить экземпляр редактора

Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.

TypeScript
// 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.

TypeScript
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

The 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.

TypeScript
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

Registers 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.

TypeScript
// 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 })],
});
TypeScript
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

СвойствоОписание
toolsRecord<string, ToolConstructable | ToolSettings>Регистрируемые блочные инструменты. Только React: функции в любом месте конфигурации инструмента (например, колбэк загрузчика) автоматически перепривязываются к последнему рендеру, поэтому инлайновые замыкания безопасны, и запись в `deps` нужна только при смене КЛАССА инструмента. У Vue и Angular аналога нет — замыкание в конфигурации инструмента захватывается при создании редактора и устаревает, поэтому держите его в стабильном ref/поле либо форсируйте пересборку сменой `recreateKey`.
dataOutputData | 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 / recreateKeyDependencyList (React) | unknown (Vue, Angular)Значения, при смене идентичности которых редактор уничтожается и создаётся заново (для структурной конфигурации вроде классов инструментов). React принимает массив — `deps` — и пересоздаёт редактор при смене идентичности любого элемента. Vue (`:recreate-key`) и Angular ([recreateKey]) принимают вместо этого ОДНО значение и пересоздают редактор при смене его идентичности; передавайте новый литерал объекта/массива или увеличенный счётчик. `deps` во Vue и Angular не существует. Держите каждое значение референциально стабильным. Функции внутри конфигурации инструментов сюда НЕ входят в React — они автоматически перепривязываются к последнему рендеру.
readOnlyboolean | 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().
styleBlokConfig['style']Конфигурация стилей. `style.tokens` реактивен: изменённые переопределения `--blok-*` синхронизируются на месте после монтирования через editor.tokens.set() (с дедупликацией по глубокому сравнению), поэтому переключателю светлой/тёмной темы на стороне хоста перемонтирование не нужно. Семантика замены — передавайте палитру целиком; токены, убранные из неё, перестают применяться. У Angular входа `style` нет: он предоставляет только `style.tokens` — как отдельный вход `[styleTokens]` (`Record<string, string>`). Остальные ключи `style` — `fontSize`, `contentAlign`, `nativeSelection` — задаются через запасной вход Angular `[config]`.
i18nBlokConfig['i18n']Конфигурация интернационализации (реактивная). Изменённые `locale`, `messages` или `direction` синхронизируются на месте после монтирования через editor.i18n.update() (с дедупликацией по глубокому сравнению), поэтому переключатель языка меняет подписи редактора без перемонтирования — каретка, фокус, выделение и история отмен сохраняются. Исключение — `defaultLocale`: он читается только при создании. В Angular это вход [i18n].
localestringТолько React. Нейтральное к библиотекам сокращение BCP-47 для `i18n.locale`: значение вкладывается в конфигурацию i18n и применяется на месте через editor.i18n.update({ locale }), поэтому переключение языка сохраняет каретку, фокус и историю отмен. При одновременном указании оно ПОБЕЖДАЕТ `i18n.locale`. Используйте его вместе с `getDirection` / `normalizeLocale`, реэкспортируемыми из @bloklabs/react, чтобы самостоятельно вычислять `dir` и проверять теги. Во Vue и Angular такого пропа нет — передавайте локаль внутри пропа/входа `i18n`.
autofocusbooleanСфокусировать редактор после монтирования.
placeholderstring | 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`.
refRef<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 сохраняет своё значение из конфигурации).