/** * For export type there should be one entry point, * so we export all types from this file * ------------------------------------ */ import { BlokConfig, I18nConfig, I18nDictionary, BlokMessageKey, BlokMessages, ToolNameMessageKey, SanitizerConfig, } from './configs'; import { InlineToolConstructable, InlineToolConstructorOptions, ToolConstructable, ToolSettings } from './tools'; import { Blocks, Caret, Events, History, InlineToolbar, Listeners, Marks, MarkSpec, Notifier, ReadOnly, Sanitizer, Saver, Selection, Styles, Toolbar, Tooltip, I18n, Ui, Tools, Uploader, Theme, ThemeMode, ResolvedTheme, Width, Placeholder, Tokens, EditorI18n, Handlers, } from './api'; import { LooseOutputData, LooseOutputBlockData, OutputData, OutputBlockData } from './data-formats'; import { BlockMutationEvent, BlockMutationEventMap, BlockMutationType } from './events/block'; import { BlockAddedMutationType, BlockAddedEvent } from './events/block/BlockAdded'; import { BlockChangedMutationType, BlockChangedEvent } from './events/block/BlockChanged'; import { BlockMovedMutationType, BlockMovedEvent } from './events/block/BlockMoved'; import { BlockRemovedMutationType, BlockRemovedEvent } from './events/block/BlockRemoved'; import { BlokEditorEventMap, BlockRenderedPayload, BlocksRenderedPayload, BlockChildrenMountedPayload, I18nChangedPayload, CollaborationParticipant, CollaborationStatusChangedPayload, CollaborationTerminalReason } from './events/editor-events'; /** * Interfaces used for development */ export { BaseTool, BaseToolConstructable, InlineTool, InlineToolConstructable, InlineToolConstructorOptions, BlockToolConstructable, BlockToolConstructorOptions, BlockTool, BlockToolData, BlockOrigin, AssetKind, Tool, ToolConstructable, ToolboxConfig, ToolboxConfigEntry, ToolSettings, ToolConfig, PasteEvent, PasteEventDetail, PatternPasteEvent, PatternPasteEventDetail, HTMLPasteEvent, HTMLPasteEventDetail, FilePasteEvent, FilePasteEventDetail, MenuConfig, MoveEvent, } from './tools'; export {BlockTune, BlockTuneConstructable, BlockTuneRenderContext} from './block-tunes'; export { BlockControlsPosition, BlokConfig, BlokErrorContext, BlokMountOptions, BlokState, ReadOnlyModeConfig, SanitizerConfig, SanitizerRule, ToolSanitizerConfig, PasteConfig, LogLevels, ConversionConfig, I18nDictionary, BlokMessageKey, BlokMessages, ToolNameMessageKey, I18nConfig, UserInfo, OnReadyHandler, OnChangeHandler, OnSaveHandler, OnErrorHandler, OnEnterHandler, OnSubmitHandler, OnBeforeRenderHandler, OnAfterRenderHandler, OnThemeChangeHandler, OnBeforePasteHandler, BlokStyleConfig, BlokLinkConfig, BlokUploader, UploadContext, UploadedAsset, PersistedDocument, SaveContext, SaveResult, } from './configs'; export * from './utils/popover'; export { OutputData, OutputBlockData, LooseOutputData, LooseOutputBlockData, BlokData} from './data-formats/output-data'; export { flattenTree, BlockTreeSpec, BlockRunSpec, BlockTreeNode, FlattenTreeOptions } from './data-formats/block-tree'; export { PropertyType, SelectOption, SelectPropertyConfig, PropertyConfig, PropertyDefinition, PropertyValue, DatabaseRow, ViewType, SortConfig, FilterConfig, DatabaseViewConfig, DatabaseRowData, DatabaseData, DatabaseAdapter, DatabaseConfig, } from './tools/database'; export { BlockId } from './data-formats/block-id'; export { BlockAPI, Blocks, Caret, Events, History, InlineToolbar, Listeners, Marks, MarkSnapshot, MarkSpec, MarkValue, Notifier, ReadOnly, Sanitizer, Saver, Selection, Styles, Toolbar, Tooltip, I18n, EditorI18n, I18nUpdateOptions, Ui, Tools, Uploader, Theme, ThemeMode, ResolvedTheme, Width, EditorWidth, Placeholder, Tokens, Handlers, LiveHandlers, } from './api'; export { BlockMutationType, BlockMutationEvent, BlockMutationEventMap, BlockAddedMutationType, BlockAddedEvent, BlockRemovedMutationType, BlockRemovedEvent, BlockMovedMutationType, BlockMovedEvent, BlockChangedMutationType, BlockChangedEvent, BlokEditorEventMap, BlockRenderedPayload, BlocksRenderedPayload, BlockChildrenMountedPayload, I18nChangedPayload, CollaborationParticipant, CollaborationTerminalReason, CollaborationStatusChangedPayload, } /** * We have a namespace API {@link ./api/index.d.ts} (APIMethods) but we can not use it as interface * So we should create new interface for exporting API type */ export interface API { blocks: Blocks; caret: Caret; tools: Tools; /** Asset uploads routed by asset kind (see {@link Uploader}). */ uploader: Uploader; events: Events; history: History; listeners: Listeners; notifier: Notifier; sanitizer: Sanitizer; saver: Saver; selection: Selection; /** Range-aware inline-mark operations (see {@link Marks}). */ marks: Marks; styles: Styles; toolbar: Toolbar; inlineToolbar: InlineToolbar; tooltip: Tooltip; i18n: I18n; readOnly: ReadOnly; ui: Ui; theme: Theme; /** Runtime setter for the live callback config (see {@link Handlers}). */ handlers: Handlers; /** Read-only view of selected editor configuration. */ config: Readonly>; rectangleSelection: { cancelActiveSelection: () => void; isRectActivated: () => boolean; clearSelection: () => void; startSelection: (pageX: number, pageY: number, shiftKey?: boolean) => void; endSelection: () => void; }; } import { DATA_ATTR, DataAttrKey, DataAttrValue, createSelector } from './data-attributes'; export { DATA_ATTR, DataAttrKey, DataAttrValue, createSelector }; import { BLOK_FONT_SIZE_TOKENS, BlokFontSizeTokens } from './font-size-tokens'; export { BLOK_FONT_SIZE_TOKENS, BlokFontSizeTokens }; /** * Blok version string */ export const version: string; /** * Options for {@link equalsOutputData}. */ export interface EqualsOutputDataOptions { /** * Ignore empty default blocks on both sides before comparing. A fresh editor * always seeds one empty default paragraph, so a pristine document * (`[{ type: 'paragraph', data: { text: '' } }]`) does not content-equal a * saved-empty baseline (`[]`) by default. Turn this on for dirty-vs-baseline * checks ("has the user actually typed anything?"): empty blocks of the * default paragraph tool are dropped from both documents first, so pristine * scaffolding and trailing empty lines don't register as a change. Empty * NON-default blocks (a content-less divider, an empty image) are kept. */ ignoreEmptyDefaultBlocks?: boolean; } /** * Structural equality for saved documents. Compares the `blocks` arrays * deeply; the volatile `time` and `version` envelope fields are ignored, so a * document round-tripped through `save()` compares equal to its echo. Block * ids participate only when BOTH sides carry one — the editor mints fresh ids * for id-less content, so a legacy document still compares equal to its saved * echo. Edit metadata (`lastEditedAt`/`lastEditedBy`) never participates: it * records who touched a block and when, not what it says, so a document whose * only delta is a stamp counts as unchanged. Nullish documents compare equal to * `{ blocks: [] }`. Accepts the loose wire shape ({@link LooseOutputData}) as-is. * * Pass `{ ignoreEmptyDefaultBlocks: true }` for dirty-vs-baseline checks so a * pristine editor (one empty default paragraph) equals a saved-empty baseline. * @param a - first document to compare * @param b - second document to compare * @param options - comparison options (see {@link EqualsOutputDataOptions}) */ export function equalsOutputData( a: OutputData | LooseOutputData | null | undefined, b: OutputData | LooseOutputData | null | undefined, options?: EqualsOutputDataOptions, ): boolean; /** * True when the document carries no user content: it is nullish, has no * blocks, or every block's data holds only empty values (blank/whitespace * strings, empty arrays/objects, nulls). Numbers and booleans are treated as * presentation metadata (`level`, `checked`, …) and never count as content. * Content-less visual blocks (e.g. a divider with `{}` data) therefore count * as empty — check `blocks.length` when mere block presence matters. * @param data - document to inspect */ export function isEmptyOutputData(data: OutputData | LooseOutputData | null | undefined): boolean; /** * Normalizes a whole loose wire document into the strict saved * {@link OutputData} shape at an input boundary. A nullish document becomes * `{ blocks: [] }`; `null` envelope fields (`time`/`version`) are dropped; each * block is normalized (see {@link normalizeOutputBlocks}), so `null`/missing * `data` becomes `{}`, `null`/empty ids are dropped for regeneration and * nullish/empty `parent`/`content` references are dropped as absent. * * Unlike a hand-written `blocks.map(...)` mapper, this preserves every block * passthrough field — `tunes`, real `parent`/`content` references, `indent`, * edit metadata — so a backend DTO can be turned into strict `OutputData` * without silently losing hierarchy or tunes. Idempotent: a strict document * passes through unchanged (shallow-copied). * @param data - a document in the strict or loose wire shape, or nullish */ export function normalizeOutputData(data: OutputData | LooseOutputData | null | undefined): OutputData; /** * Normalizes blocks from the loose wire shape into the strict saved shape: a * `null`/missing `data` becomes `{}`, a `null`/empty `id` is dropped so the * block factory generates a fresh one, and nullish/empty hierarchy references * (`parent: null`, `content: null`, `content: []`) are dropped so a root-level, * childless block is spelled the way `save()` spells it — absent. All other * fields (`tunes`/`indent`/edit metadata) pass through untouched. * Idempotent — strict blocks pass through unchanged (shallow-copied). Use * {@link normalizeOutputData} to normalize a whole document envelope. * @param blocks - blocks in either the strict or the loose wire shape */ export function normalizeOutputBlocks( blocks: Array ): OutputBlockData[]; /** * A shared, deeply frozen empty document. Use in place of a hand-written * `{ blocks: [] }` literal for cleared/pristine baselines. Frozen (blocks array * included) so a shared reference can never be mutated into a stale non-empty * baseline. */ export const EMPTY_OUTPUT_DATA: OutputData; /** * Maps a controlled `data` value to something `render()` / `blocks.render()` * accepts: a whole-document `null` (a controlled "clear to empty") becomes * `{ blocks: [] }`; any real document passes through untouched. `render()`'s * strict guard reads `data.blocks` and would throw on `null`, so route a * nullable controlled value through this first. * @param data - a controlled document, or `null` for an empty document */ export function toRenderableData( data: OutputData | LooseOutputData | null, ): OutputData | LooseOutputData; /** * Creates a bounded window of recently emitted `onSave` payloads for * recognizing controlled-`data` echoes. Deduping against only the LAST emitted * payload is not enough: a host that persists on save and refetches can hand * back a STALE echo (an earlier save arriving after a newer one already * replaced the baseline), and re-rendering it would clobber the caret and any * content typed since. Matching is structural ({@link equalsOutputData}), so * envelopes reshaped in transit (fresh `time`, stripped ids) still count as * echoes. * @param capacity - payloads retained before the oldest is evicted (default 20) */ export function createEmittedEchoWindow(capacity?: number): { /** Records a payload the editor emitted via `onSave`. */ record(data: OutputData | LooseOutputData): void; /** True when the document content-equals any recorded payload. */ matches(data: OutputData | LooseOutputData | null | undefined): boolean; /** Forgets all recorded payloads (external content took over). */ clear(): void; }; /** * Composes a base sanitizer config from a list of per-tool sanitize configs * using the exact merge semantics of the editor's `baseSanitizeConfig`: * a later-wins `Object.assign` fold (inline tools first, then tunes). * Function rules are carried by reference; rules for the same tag are * replaced, never deep-merged. * @param configs - sanitize configs in composition order */ export function composeBaseSanitizeConfig(configs: SanitizerConfig[]): SanitizerConfig; /** * Derive an inline tool's sanitizer rule from its MarkSpec: allowlists the * spec's tag, strips style properties and classes the spec does not declare, * and keeps declared attributes. Function-form values are handled by property * NAME (names are always known even when values are dynamic), so dynamic * values can never be silently dropped on save. * @param spec - mark description */ export function markSanitizerConfig(spec: MarkSpec): SanitizerConfig; /** * Config accepted by {@link defineBlokSchema}. Only `tools`, `inlineToolbar` * and `tunes` participate in schema resolution; everything else is passed * through untouched via `editorConfig`. */ export type BlokSchemaConfig = BlokConfig; /** * A tool resolved from the user config: its constructable plus the Blok-level * settings that accompanied it (everything except `class`). */ export interface ResolvedSchemaTool { toolClass: ToolConstructable; settings: Omit; } /** * The view side of a defined schema: the composed base sanitize allowlist and * the resolved tool map, for consumption by the view renderer. */ export interface BlokViewSchema { baseSanitize: SanitizerConfig; tools: { [toolName: string]: ResolvedSchemaTool }; } /** * Result of {@link defineBlokSchema}. */ export interface DefinedBlokSchema { editorConfig: Config; viewSchema: BlokViewSchema; } /** * Resolves the composed base sanitize allowlist and tool map for a Blok * configuration WITHOUT instantiating an editor. Pure, synchronous and * module-scope-safe (no DOM access), so it can run in Node/RSC contexts. * * Spread `editorConfig` into `new Blok({...})` and hand `viewSchema` to the * view renderer: both sides then share the exact same allowlist composition, * so a viewer can never silently strip marks the editor wrote. * @param config - subset of BlokConfig ({ tools, inlineToolbar, tunes, link, i18n, ... }) */ export function defineBlokSchema(config: Config): DefinedBlokSchema; /** * Compatibility shim for migrating Editor.js custom inline tools. * Adapts a legacy Editor.js-style inline tool class (render()→HTMLElement plus * surround()/checkState()) into a Blok-compatible inline tool whose render() * returns a MenuConfig. * @param LegacyToolClass - an Editor.js-style inline tool class */ export function wrapLegacyInlineTool( LegacyToolClass: new (options: InlineToolConstructorOptions) => { render(): HTMLElement | null | undefined; surround?(range: Range): void; // globalThis.Selection: the DOM type (window.getSelection()), not Blok's // imported Selection API, which shadows the global name in this file. checkState?(selection?: globalThis.Selection | null): boolean | void; renderActions?(): HTMLElement | null | undefined; clear?(): void; } ): InlineToolConstructable; /** * The surface of a Blok instance that is guaranteed to exist synchronously, * immediately after `new Blok()` and before `isReady` has resolved. * * Blok constructs its module APIs (`blocks`, `caret`, `history`, `readOnly`, …) * asynchronously: they only become available once `isReady` resolves. Accessing * them earlier returns `undefined` at runtime. Type a reference you hold during * that window as `PendingBlok` so the not-yet-available members are unreachable, * then await `isReady` to obtain the fully-initialized {@link Blok}: * * @example * const pending: PendingBlok = new Blok(config); * const editor = await pending.isReady; // editor is a ready Blok * editor.blocks.render(data); */ export interface PendingBlok { /** Resolves with the fully-initialized Blok instance once core modules are ready. */ isReady: Promise; /** Synchronous render-readiness flag; false until the first render batch lands in the DOM. */ readonly isRendered: boolean; /** Destroy the instance. Safe to call before `isReady` resolves. */ destroy(): void; /** Theme API, exposed immediately after construction. */ theme: Theme; /** Width API, exposed immediately after construction. */ width: Width; /** Placeholder API, exposed immediately after construction. */ placeholder: Placeholder; /** Runtime theme-tokens API, exposed immediately after construction. */ tokens: Tokens; /** Runtime i18n API (read + `update`), exposed immediately after construction. */ i18n: EditorI18n; } /** How deep a readiness query looks: boot completion, or content in the DOM. */ export type ReadySettleOn = 'ready' | 'rendered'; /** * Narrows a readiness query to a DOM subtree and a readiness depth. * * An instance that has not finished booting *and* whose wrapper is not * attached to the document counts in every scope: it may still turn out to * live inside `within` (framework adapters build the editor holder detached * and append it later). Over-waiting is safe; under-waiting is a bug. */ export interface ReadyScopeOptions { /** Only count instances whose wrapper lives inside this element. */ within?: Element | null; /** Readiness depth. Defaults to `'ready'`. */ settleOn?: ReadySettleOn; } /** Synchronous readiness snapshot for a scope. */ export interface ReadyStateSnapshot { /** Instances matching the scope. */ total: number; /** Matching instances that are not settled yet. */ pending: number; /** True when nothing in the scope is pending (an empty scope is settled). */ ready: boolean; } /** * Main Blok class */ export class Blok { public isReady: Promise; /** * Synchronous render-readiness flag. True once the current render batch has * landed in the DOM (mirrors the `data-blok-rendered` wrapper attribute); * false before first render and while a re-render is in flight. Unlike * `isReady`/`onReady` this needs no await or callback, so consumers * coordinating several editor instances can poll mount state synchronously. */ public readonly isRendered: boolean; /** * Resolves once every Blok instance in scope has finished booting (each * instance's `isReady` has settled — rejections count as settled). * Collective-readiness signal for pages hosting several instances (e.g. a * list of read-only editors plus a composer): await it before autofocusing * or measuring layout, instead of hand-aggregating per-instance `onReady` * callbacks. * * Pass `within` to restrict the wait to instances mounted inside a DOM * subtree you own, and `settleOn: 'rendered'` to extend readiness from * construction to content-in-the-DOM (which also covers post-boot * re-renders). An empty scope resolves immediately. * * Instances that appear while the returned promise is pending extend the * wait; instances constructed after it resolves are not covered — call again * for a fresh aggregate, or use `subscribeReady()` for a live signal. */ public static whenAllReady(options?: ReadyScopeOptions): Promise; /** * Synchronous readiness snapshot for a scope: how many instances match, how * many are still pending, and whether the scope is settled. An empty scope * reports `ready: true`. */ public static readyState(options?: ReadyScopeOptions): ReadyStateSnapshot; /** * Subscribes to readiness changes across all instances (construction, boot, * render-state flip, destroy) and returns an unsubscribe function. The * listener takes no arguments — re-read `Blok.readyState(scope)` when it * fires. Pairs with `useSyncExternalStore` and other store adapters. */ public static subscribeReady(listener: () => void): () => void; /** * Editor-level namespaces. Everything else (`blocks`, `caret`, `tools`, * `uploader`, `marks`, …) comes from the merged `Blok extends Omit` * interface below — the instance inherits the whole API surface at runtime. */ public width: Width; public placeholder: Placeholder; public tokens: Tokens; /** * Runtime i18n API. Everything tools get through `api.i18n` plus * `update({ locale, messages, direction })`, which relabels the editor in * place instead of forcing a recreation. Exposed immediately after * construction; updates issued before `isReady` are replayed once modules exist. */ public i18n: EditorI18n; constructor(configuration?: BlokConfig|string); /** * API shorthands */ /** * @see Saver.save */ public save(): Promise; /** * @see Blocks.clear */ public clear(): Promise; /** * @see Blocks.render */ public render(data: OutputData | LooseOutputData): Promise; /** * @see Caret.focus */ public focus(atEnd?: boolean): boolean; /** * @see Events.on */ public on(eventName: string, callback: (data?: any) => void): void; /** * @see Events.off */ public off(eventName: string, callback: (data?: any) => void): void; /** * @see Events.emit */ public emit(eventName: string, data: any): void; /** * Destroy Blok instance and related DOM elements */ public destroy(): void; } /** * A Blok instance carries the entire {@link API} surface directly: `exportAPI()` * sets the instance's prototype to the API methods object, so anything a tool * reaches through `api.*` is also reachable as `editor.*` — `editor.uploader`, * `editor.marks`, `editor.notifier`, `editor.rectangleSelection`, and so on. * * Declaration merging (rather than a hand-copied member list on the class) is * what keeps the two in sync: a namespace added to `API` shows up here for free. * `i18n` is excluded because the editor widens it to {@link EditorI18n} — the * tool-facing `I18n` plus `update()`. * * These namespaces exist only once `isReady` resolves; before that, type the * reference as {@link PendingBlok}. */ export interface Blok extends Omit {} export { Blok as EditorJS }; export default Blok; /** * Per-tune persisted data, keyed by tune name. Exposed publicly so the * standalone adapter packages (@bloklabs/react, @bloklabs/vue, @bloklabs/angular) can * type block tunes without deep-importing into the types tree. */ export type { BlockTuneData } from './block-tunes/block-tune-data';