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

Компонент 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).

Пропы ниже описывают специфичную для адаптеров поверхность. Остальное совпадает с опциями раздела «Конфигурация».

Обновлено 5 окт. 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 синхронизируются на месте на том же экземпляре. Любая другая опция считывается один раз при создании редактора.

Пока редактора нет (SSR, первый рендер), возвращается null.

  • 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]) принимают вместо этого ОДНО значение.
    • Они пересоздают редактор при смене идентичности этого значения, поэтому передавайте новый литерал объекта или массива либо увеличенный счётчик.
    • Во Vue и Angular deps не существует.

Держите каждое значение референциально стабильным. Функции внутри конфигурации инструментов сюда НЕ входят в 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(). Императивную часть (get / set / toggle) смотрите в Width API.

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 отключает и чего не отключает, смотрите в таблице «Конфигурация». Менять плейсхолдер на лету через editor.placeholder.set() позволяет Placeholder API.

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.

Для частых сокращений без таких проверок есть useBlokHandle() из @bloklabs/react: он возвращает стабильный хендл, безопасный к null. Привяжите его через ref={handle.ref} и вызывайте handle.focus()/save()/clear()/render()/setReadOnly() напрямую. Каждый вызов безопасно ничего не делает, пока редактор не готов, а handle.current даёт доступ к полному экземпляру.

Если содержимое вы задаёте ещё и пропом data, для clear()/render() предпочитайте хендл. Он обновляет контролируемый базовый снимок. Прямой вызов ref.current.render()/clear() меняет содержимое в обход адаптера, и следующее значение data, равное ранее выданному редактором документу, будет отброшено как эхо.

className, id, …HTMLAttributes<HTMLDivElement>

Любой проп, не являющийся опцией конфигурации редактора, пробрасывается на контейнерный div. Стилизуйте редактор через className (style сохраняет своё значение из конфигурации редактора).