Перейти к содержимому
ФреймворкJavaScript

React-компонент BlokEditor

Компонент-редактор «всё в одном», который поставляют адаптеры фреймворков: <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, …) и не пробрасывает атрибуты хоста на контейнерный 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)

Раздельный путь монтирования, стоящий за `<BlokEditor>`: экземпляр вы создаёте сами и передаёте ему точку монтирования. `useBlok` принимает ТЕ ЖЕ опции, что и компонент, — его тип конфигурации `UseBlokConfig`, то есть `BlokConfig` без `holder` (элементом монтирования владеет адаптер) плюс `width` уровня адаптера. Подмножество, реактивное после монтирования, описано на самом `UseBlokConfig`: `readOnly`, `hideToolbar`, `toolbarPosition`, `inlineToolbar`, `autofocus`, `theme`, `width`, `placeholder`, `style.tokens`, `i18n` и `data` синхронизируются на месте на том же экземпляре; любая другая опция считывается один раз при создании редактора. Он возвращает null, пока редактора не существует (SSR, первый рендер). React принимает вторым аргументом СПИСОК зависимостей `deps`; Vue принимает реактивный источник конфигурации (ref или геттер) и ОДИН `recreateKey` вторым аргументом. Эквивалент в Angular — директива `[blokContent]` (`BlokContentDirective`), которая строит экземпляр в своём собственном хост-элементе и отдаёт его через сигнал `instance` / output `(ready)`.

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

Точка монтирования для экземпляра, созданного через `useBlok`. Рендерит `<div>` и переносит в него откреплённый holder редактора. Единственный собственный проп: `editor: Blok | null` (`BlokContentProps`) — передайте null до появления экземпляра, и компонент просто отрисует пустой контейнер. В React он к тому же расширяет `React.HTMLAttributes<HTMLDivElement>`, поэтому `className`, `id` и остальное пробрасываются на этот div, а ref пробрасывается на него же. Аналог в Angular — директива `[blokContent]`, которая создаёт экземпляр сама, а не получает его.

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

Регистрирует значения по умолчанию для Blok на уровне всего приложения, чтобы каждый редактор внутри наследовал общий реестр инструментов, тему или конфигурацию i18n, а не повторял их для каждого экземпляра. В React это `<BlokProvider defaults={…}>` (плюс `useBlokDefaults()`, чтобы прочитать их обратно), во Vue — `provideBlok(defaults)`, вызванный в `setup` родителя (за ним стоит ключ внедрения `BLOK_DEFAULT_CONFIG`, а для чтения есть `useBlokDefaults()`), в Angular — `provideBlok(defaults)`, возвращающий `EnvironmentProviders` для массива `providers` (за ним стоит токен внедрения `BLOK_DEFAULT_CONFIG`). Правило слияния, одинаковое во всех трёх: заданное значение конфигурации отдельного экземпляра перекрывает значение по умолчанию, КРОМЕ `tools`, где два реестра сливаются — общий реестр складывается с добавлениями конкретного экземпляра, а не заменяется ими.

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 сохраняет своё значение из конфигурации).