@bloklabs/react 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,538 @@
1
+ import type { BlokConfig, BlockToolData, OutputBlockData, OutputData } from '@bloklabs/core';
2
+ import type { Blok, EditorWidth, BlockRenderedPayload, BlocksRenderedPayload } from '@bloklabs/core';
3
+ import type { BlockTuneData } from '@bloklabs/core';
4
+ import type { MarkdownImportConfig } from '@bloklabs/core/markdown';
5
+ import type React from 'react';
6
+
7
+ /**
8
+ * Configuration for the useBlok hook.
9
+ * Accepts all BlokConfig properties except `holder`, plus a React-only `width` prop.
10
+ *
11
+ * Reactive props (sync after mount without recreation):
12
+ * - `readOnly` — calls `editor.readOnly.set(value)`
13
+ * - `autofocus` — calls `editor.focus()` when changed to true
14
+ * - `theme` — calls `editor.theme.set(value)`
15
+ * - `width` — calls `editor.width.set(value)`
16
+ * - `placeholder` — calls `editor.placeholder.set(value)`
17
+ * - `data` — re-renders via `editor.render(value)` when the content changes
18
+ * (deep-equal–deduped and serialized; seeds the initial content at creation)
19
+ *
20
+ * All other config is consumed once at editor creation.
21
+ */
22
+ export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
23
+ /** Editor content width mode. Synced reactively after mount. */
24
+ width?: EditorWidth;
25
+ }
26
+
27
+ /**
28
+ * Props for the BlokContent component.
29
+ * Renders a `<div>` that becomes the Blok editor's DOM mount point.
30
+ * Passes through all standard HTML div attributes.
31
+ */
32
+ export interface BlokContentProps extends React.HTMLAttributes<HTMLDivElement> {
33
+ /** The Blok editor instance from useBlok. Null during SSR or before initialization. */
34
+ editor: Blok | null;
35
+ }
36
+
37
+ /**
38
+ * React hook that creates and manages a Blok editor instance.
39
+ *
40
+ * @param config - Editor configuration (all BlokConfig props except `holder`)
41
+ * @param deps - Optional dependency array. When any dep changes, the editor is destroyed and recreated. Keep each value referentially stable (primitives or useMemo-stable objects) so the editor isn't recreated every render.
42
+ * @returns The Blok editor instance, or null during SSR / before initialization.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const editor = useBlok({
47
+ * tools: defaultTools,
48
+ * data: savedData,
49
+ * readOnly: false,
50
+ * onChange: (api, event) => console.log(event),
51
+ * });
52
+ * ```
53
+ */
54
+ export function useBlok(
55
+ config: UseBlokConfig,
56
+ deps?: React.DependencyList
57
+ ): Blok | null;
58
+
59
+ /**
60
+ * Component that provides the DOM mount point for a Blok editor.
61
+ * Renders a `<div>` and adopts the editor's DOM tree into it.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * <BlokContent editor={editor} className="my-editor" />
66
+ * ```
67
+ */
68
+ export declare const BlokContent: React.ForwardRefExoticComponent<BlokContentProps & React.RefAttributes<HTMLDivElement>>;
69
+
70
+ /**
71
+ * Props for the BlokEditor component — all useBlok config (except `onReady`,
72
+ * re-typed to receive the ready instance) plus every standard HTML div attribute
73
+ * (forwarded to the container, like `BlokContent`) and an optional `deps` list
74
+ * that forces recreation when changed. `style`/`onChange` keep editor-config meaning.
75
+ */
76
+ export interface BlokEditorProps
77
+ extends Omit<UseBlokConfig, 'onReady'>,
78
+ Omit<React.HTMLAttributes<HTMLDivElement>, 'style' | 'onChange'> {
79
+ /**
80
+ * When any value changes, the editor is destroyed and recreated. Keep each
81
+ * value referentially stable (primitives or useMemo-stable objects) — a dep
82
+ * whose identity changes every render recreates the editor each time.
83
+ */
84
+ deps?: React.DependencyList;
85
+ /** Test id forwarded to the editor container element (via data-testid). */
86
+ 'data-testid'?: string;
87
+ /** Called once the editor is ready, with the live Blok instance (ref is also committed). */
88
+ onReady?: (editor: Blok) => void;
89
+ /**
90
+ * Called after a batch render completes (core `blocks:rendered` event). The
91
+ * declarative analog of `ref.current.on('blocks:rendered', …)` — mirrors the
92
+ * Vue/Angular adapters' rendered-lifecycle outputs.
93
+ */
94
+ onBlocksRendered?: (payload: BlocksRenderedPayload) => void;
95
+ /** Called for each block rendered into the DOM (core `block:rendered` event). */
96
+ onBlockRendered?: (payload: BlockRenderedPayload) => void;
97
+ }
98
+
99
+ /**
100
+ * The recommended all-in-one React component. Wires useBlok + BlokContent and
101
+ * forwards a ref to the live Blok instance (null before the editor is ready).
102
+ *
103
+ * Don't wrap this component in `styled()` or any HOC that reserves the `theme`
104
+ * prop — styled-components claims `theme` for its own `ThemeProvider`, so it
105
+ * never reaches the editor and theme sync silently breaks. Render it directly
106
+ * and style the container via `className`.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * const ref = useRef<Blok | null>(null);
111
+ * <BlokEditor ref={ref} tools={tools} data={data} theme="dark" />;
112
+ * ```
113
+ */
114
+ export declare const BlokEditor: React.ForwardRefExoticComponent<
115
+ BlokEditorProps & React.RefAttributes<Blok | null>
116
+ >;
117
+
118
+ /**
119
+ * Registers app-wide Blok defaults for every `useBlok` / `<BlokEditor>` rendered
120
+ * beneath it. Per-instance config overrides these, and the `tools` registry is
121
+ * merged (shared registry composes with per-instance additions) rather than
122
+ * replaced. Mirrors Vue's and Angular's `provideBlok`.
123
+ *
124
+ * @example
125
+ * ```tsx
126
+ * <BlokProvider defaults={{ theme: 'dark', tools: sharedTools }}>
127
+ * <App />
128
+ * </BlokProvider>
129
+ * ```
130
+ */
131
+ export declare function BlokProvider(props: {
132
+ defaults: Partial<UseBlokConfig>;
133
+ children?: React.ReactNode;
134
+ }): React.ReactElement;
135
+
136
+ /** Reads the app-wide Blok defaults from the nearest `BlokProvider` (or `{}`). */
137
+ export declare function useBlokDefaults(): Partial<UseBlokConfig>;
138
+
139
+ /**
140
+ * A plain, serializable view of one block in the tree.
141
+ *
142
+ * Snapshot-volatile: every read allocates a fresh `BlockNode`, and `contentIds`
143
+ * is DERIVED per read from the children that currently name this block as parent
144
+ * (it is not a stored field). Read a node in render and re-read after a change —
145
+ * don't stash one in a `useMemo`/`useEffect` dependency array expecting stable
146
+ * identity; depend on the `id` instead.
147
+ */
148
+ export interface BlockNode {
149
+ id: string;
150
+ type: string;
151
+ parentId: string | null;
152
+ contentIds: readonly string[];
153
+ }
154
+
155
+ /** Where to place a block among its siblings. */
156
+ export type InsertPosition = 'start' | 'end' | { before: string } | { after: string };
157
+
158
+ /**
159
+ * Where to place the caret within a block. `position` selects the input
160
+ * (`'start'`/`'end'`/`'default'`) and `offset` is the character offset within it.
161
+ */
162
+ export interface CaretTarget {
163
+ position?: 'start' | 'end' | 'default';
164
+ offset?: number;
165
+ }
166
+
167
+ export interface InsertSpec {
168
+ type?: string;
169
+ data?: BlockToolData;
170
+ parentId?: string | null;
171
+ position?: InsertPosition;
172
+ /**
173
+ * Move the caret into the new block. Defaults to `false`: a programmatic
174
+ * insert from React must not steal focus from wherever the user is typing.
175
+ * Set `true` for an explicit "add a block and start editing it" flow.
176
+ */
177
+ focus?: boolean;
178
+ /**
179
+ * Replace the block at the resolved slot instead of inserting a new one — a
180
+ * programmatic "turn into". Combine with a `position` that targets the block
181
+ * to replace, e.g. `{ position: { before: id }, replace: true }`.
182
+ */
183
+ replace?: boolean;
184
+ /**
185
+ * Explicit id for the new block (generated when omitted). Passing a stable id
186
+ * makes the insert idempotent: if a block with this id already exists the
187
+ * existing node is returned and nothing is inserted ("insert if absent"),
188
+ * so an effect that re-runs won't create duplicates.
189
+ */
190
+ id?: string;
191
+ /** Block tune data to apply at creation, keyed by tune name. */
192
+ tunes?: { [name: string]: BlockTuneData };
193
+ /**
194
+ * Place the caret inside the newly-created block at a specific position/offset
195
+ * (e.g. `{ offset: 3 }`). Implies focus. Applied ONLY when a block is actually
196
+ * created — an insert-if-absent hit (existing id) does not move the caret.
197
+ */
198
+ caret?: CaretTarget;
199
+ }
200
+
201
+ /**
202
+ * One node of a pre-built nested subtree for {@link UseBlocksApi.insertTree}.
203
+ *
204
+ * Each node maps to one block; `children` are inserted nested under it (their
205
+ * `parentId` set to this node's id) in array order, recursively. Placement
206
+ * options (`parentId`/`position`) are ROOT-ONLY — they position the whole
207
+ * subtree among existing blocks and are ignored on nested children, whose parent
208
+ * is always their enclosing node.
209
+ */
210
+ export interface TreeInsertSpec {
211
+ type?: string;
212
+ data?: BlockToolData;
213
+ tunes?: { [name: string]: BlockTuneData };
214
+ /**
215
+ * Explicit id for this node (generated when omitted). Unlike `insert`, this is
216
+ * NOT insert-if-absent: a tree insert always creates fresh blocks. A colliding
217
+ * id — one that already exists in the document, or is reused by another node
218
+ * in the same spec — is REJECTED up front: nothing is inserted and `insertTree`
219
+ * returns `null` (a duplicate id would corrupt every id-keyed lookup).
220
+ */
221
+ id?: string;
222
+ /** Direct children, inserted nested under this node, in array order. */
223
+ children?: TreeInsertSpec[];
224
+ /** Root-only: where to place the whole subtree. Ignored on nested children. */
225
+ parentId?: string | null;
226
+ /** Root-only: slot among siblings of `parentId`. Ignored on nested children. */
227
+ position?: InsertPosition;
228
+ }
229
+
230
+ /**
231
+ * Where to move an existing block.
232
+ *
233
+ * `before`/`after` are POSITION targets, not parent assignments: the block is
234
+ * relocated to that flat slot and — because Blok keeps the flat array as the
235
+ * canonical document order — ADOPTS the parent of wherever it lands. Moving a
236
+ * nested block to `{ after: someRootBlock }` therefore unnests it to root, and
237
+ * moving a root block in among a container's children nests it. Use
238
+ * `nest`/`unnest` when you want to change the parent without choosing a sibling
239
+ * slot. `toIndex` is an absolute flat index (clamped into range).
240
+ */
241
+ export type MoveTarget = { before: string } | { after: string } | { toIndex: number };
242
+
243
+ export interface UseBlocksApi {
244
+ getById(id: string): BlockNode | null;
245
+ getChildren(parentId: string | null): BlockNode[];
246
+ /**
247
+ * Insert one block; returns the created node or null when rejected (unknown
248
+ * tool type, dangling `parentId`, or a `replace` whose target is missing). An
249
+ * explicit `id` that already exists is insert-if-absent (returns the existing
250
+ * node, creates nothing). Atomic — one undo step. The returned node is
251
+ * {@link BlockNode}-volatile; read it now, don't put it in a dep array.
252
+ *
253
+ * `replace: true` is a positional "turn into": it REQUIRES a `before`/`after`
254
+ * `position` whose ref IS the block to overwrite. It returns `null` if
255
+ * `replace` is set without an object `position`, or if that target ref does
256
+ * not exist. The replacement keeps the overwritten block's own parent (the
257
+ * caller's `parentId` is ignored under `replace`).
258
+ */
259
+ insert(spec?: InsertSpec): BlockNode | null;
260
+ /**
261
+ * Insert several blocks atomically, in array order, as ONE undo step. Each
262
+ * spec is a full {@link InsertSpec} (own type/data/parentId/position), routed
263
+ * through the same single-`insert` path, so per-spec parent assertion and
264
+ * positioning still apply. Specs that fail to insert (e.g. a dangling
265
+ * parentId, or a replace whose target is missing) are dropped; the returned
266
+ * array holds only the successfully created nodes. An empty input is a no-op
267
+ * (returns `[]`, opens no transaction). Like `insert`, the returned nodes are
268
+ * fresh-snapshot volatile — read them now, don't stash them in dep arrays.
269
+ */
270
+ insertMany(specs: InsertSpec[]): BlockNode[];
271
+ /**
272
+ * Insert a pre-built NESTED subtree in ONE atomic operation (one undo step).
273
+ * Each {@link TreeInsertSpec} node becomes a block; its `children` are inserted
274
+ * nested under it (recursively, in array order) so the whole hierarchy lands in
275
+ * a single call — no follow-up `nest` round-trips. Delegates to core's
276
+ * tree-aware `blocks.insertMany`, which composes the flat DFS pre-order array
277
+ * honoring each node's `parent`/`content` links.
278
+ *
279
+ * Placement is root-only: the root node's `parentId`/`position` position the
280
+ * whole subtree among existing blocks (default: appended at the document end);
281
+ * nested children ignore those fields (their parent is their enclosing node). A
282
+ * dangling root `parentId` is rejected — nothing is inserted and `null` is
283
+ * returned (mirrors {@link insert}). Returns the root {@link BlockNode}, which
284
+ * is fresh-snapshot volatile — read it now, don't stash it in a dep array.
285
+ */
286
+ insertTree(spec: TreeInsertSpec): BlockNode | null;
287
+ /**
288
+ * Convert a Markdown string to blocks and insert them ADDITIVELY at a
289
+ * position, WITHOUT clearing the document (unlike core's `importMarkdown` /
290
+ * `renderFromHTML`, which replace the whole document). This is the React
291
+ * "paste markdown → blocks appear" path.
292
+ *
293
+ * Async: the markdown converter is lazy-loaded (kept out of the main bundle),
294
+ * so this is the ONE async creator in the API — `await` the returned promise.
295
+ * The whole batch is inserted as a single atomic undo step.
296
+ *
297
+ * `position` (default `'end'`) places the converted run among `parentId`'s
298
+ * children (or root siblings when `parentId` is omitted/null), reusing the
299
+ * same `start`/`end`/`before`/`after` semantics as {@link insert}.
300
+ *
301
+ * `parentId` (default `null` = root) nests the import: every TOP-LEVEL
302
+ * converted block (one the converter left un-parented) is reparented under
303
+ * `parentId`, while blocks the markdown nested internally (e.g. table-cell
304
+ * children) keep their intra-import parent. A dangling `parentId` is a no-op
305
+ * (returns `[]`, opens no transaction), matching {@link insert}; it is
306
+ * re-checked AFTER the async convert, so a parent removed mid-flight also
307
+ * no-ops instead of orphaning the blocks.
308
+ *
309
+ * `config` (optional {@link MarkdownImportConfig}) is forwarded to the
310
+ * converter so custom-tool consumers can map markdown nodes into their tools
311
+ * (`toolMap`/`onUnknownNode`), toggle GFM, or add micromark/mdast extensions.
312
+ *
313
+ * Returns ALL created {@link BlockNode}s in document order — including any the
314
+ * markdown nested internally (e.g. a table's cell children), not just the
315
+ * top-level run (this differs from {@link insertTree}, which returns only the
316
+ * root). Empty or whitespace-only markdown, a dangling `parentId`, and a
317
+ * converter failure (chunk-load or parse error, swallowed) all return `[]` and
318
+ * open no transaction. The nodes are fresh-snapshot volatile — read them now,
319
+ * don't stash them in dep arrays.
320
+ */
321
+ insertMarkdown(
322
+ markdown: string,
323
+ options?: { parentId?: string | null; position?: InsertPosition; config?: MarkdownImportConfig }
324
+ ): Promise<BlockNode[]>;
325
+ /**
326
+ * Serialize the WHOLE document to a Markdown string — the outbound twin of
327
+ * core's `blocks.exportMarkdown` (and the read-side counterpart of the
328
+ * additive {@link insertMarkdown}). Async: the serializer is lazy-loaded, like
329
+ * the importer. Markdown cannot express every block, so some structure is
330
+ * dropped (table `colspan`/`rowspan`, heading columns). Returns `''` for an
331
+ * empty document. Pre-ready: resolves to `''`.
332
+ */
333
+ exportMarkdown(): Promise<string>;
334
+ /**
335
+ * Move `id` to a flat slot, as a single operation. No-op when `id` is unknown.
336
+ * For a relative `{ before|after }` target it is also a no-op when the ref is
337
+ * `id` itself, a descendant of `id` (a block can't be a sibling of its own
338
+ * child), OR does not exist — a missing ref is NOT relocated to the document
339
+ * end. A `{ toIndex }` target is an absolute flat index, clamped into range,
340
+ * and no-ops if the clamped slot would land inside the block's own subtree.
341
+ *
342
+ * Parent-adoption side effect: `before`/`after` are POSITION targets, so the
343
+ * moved block ADOPTS the parent of wherever it lands (moving into a
344
+ * container's children nests it; moving out to a root sibling unnests it).
345
+ * Use {@link nest}/{@link unnest} to change the parent without choosing a
346
+ * sibling slot. Returns void.
347
+ */
348
+ move(id: string, target: MoveTarget): void;
349
+ /**
350
+ * Nest `id` (and its whole subtree) under `parentId`, as one undo step.
351
+ * Graceful no-op when either id is unknown, when `parentId === id`, or when
352
+ * `parentId` is a DESCENDANT of `id` (a block can't be nested under its own
353
+ * child).
354
+ *
355
+ * Column boundary caveat: nesting a block that lives inside a `column` (or
356
+ * into one) is a GRACEFUL no-op — Blok's move() clamps cross-column-boundary
357
+ * moves, so the relocation can't run and the parent isn't changed. Use the
358
+ * drag UI for column membership changes. Returns void.
359
+ */
360
+ nest(id: string, parentId: string): void;
361
+ /**
362
+ * Promote `id` (and its subtree) to root, as one undo step. Graceful no-op
363
+ * when the id is unknown or already at root. Same column-boundary caveat as
364
+ * {@link nest}: unnesting out of a `column` is a graceful no-op. Returns void.
365
+ */
366
+ unnest(id: string): void;
367
+ /**
368
+ * Remove a block by id. DESTRUCTIVE: this deletes the block AND its entire
369
+ * subtree (all transitive descendants), NOT just the single block. Deletion
370
+ * runs deepest-first (descending flat index) so each parent is childless when
371
+ * it's removed — this avoids Blok's single-block delete, which would otherwise
372
+ * PROMOTE a (non-columns) container's children to root and orphan the nested
373
+ * structure the caller meant to discard. The whole cascade is one undo step.
374
+ * An unknown id is a silent no-op. Returns void.
375
+ */
376
+ remove(id: string): void;
377
+ /**
378
+ * Update a block's data and/or tunes by id. Delegates to core's async
379
+ * `blocks.update`, which forms its OWN undo step — the call is NOT wrapped in
380
+ * `transact` (that would close the group before the async write lands). An
381
+ * unknown id is a silent no-op; a rejected update is swallowed so it can't
382
+ * surface as an unhandled rejection. Reads refresh reactively once core emits
383
+ * 'block changed'. Returns `void`.
384
+ */
385
+ update(id: string, data?: BlockToolData, tunes?: { [name: string]: BlockTuneData }): void;
386
+ /**
387
+ * Convert a block to another type ("turn into") by id. Delegates to core's
388
+ * async `blocks.convert`; both tools must provide a `conversionConfig` or core
389
+ * rejects — that rejection (and any other) is swallowed so a non-convertible
390
+ * block is a graceful no-op rather than an unhandled rejection. An unknown id
391
+ * is a silent no-op. Not wrapped in `transact` (core owns its history step).
392
+ * Returns `void`.
393
+ */
394
+ convert(
395
+ id: string,
396
+ newType: string,
397
+ dataOverrides?: BlockToolData,
398
+ options?: { caret?: CaretTarget }
399
+ ): void;
400
+ /**
401
+ * Run `fn` as a single atomic undo step: every mutation it makes is grouped
402
+ * into one history entry, so a single undo reverts the whole batch. Delegates
403
+ * to core's `blocks.transact` when available and otherwise just invokes `fn`
404
+ * directly. (`insert`/`insertMany`/`nest`/`unnest`/`remove` already wrap
405
+ * themselves; use this to group several calls into one step.) Returns void.
406
+ */
407
+ transact(fn: () => void): void;
408
+ /**
409
+ * Run `fn` as one atomic operation that is NOT captured in the undo history —
410
+ * the React-surface counterpart of core's `transactWithoutCapture`. Use for
411
+ * silent auto-repair/normalization that a user's undo should never step
412
+ * through. Mutations inside still emit reactively. Pre-ready it just runs `fn`.
413
+ */
414
+ transactWithoutCapture(fn: () => void): void;
415
+ /** The current block count. Reactive (re-reads on 'block changed'). Pre-ready: 0. */
416
+ getBlocksCount(): number;
417
+ /** The flat index of the block holding the caret, or -1 when none. Pre-ready: -1. */
418
+ getCurrentBlockIndex(): number;
419
+ /** The block at a flat index as a snapshot {@link BlockNode}, or null. */
420
+ getBlockByIndex(index: number): BlockNode | null;
421
+ /**
422
+ * The absolute flat index of a block by id, or null when unknown. The
423
+ * counterpart to {@link getBlockByIndex} — use it to target an off-caret
424
+ * {@link splitBlock} (whose `insertIndex` is absolute) without the ref. Unknown
425
+ * ids return null silently (no console warn). Pre-ready: null.
426
+ */
427
+ getBlockIndex(id: string): number | null;
428
+ /**
429
+ * Read a block's current `data` and `tunes` by id WITHOUT mutating anything —
430
+ * the synchronous last-extracted view (the same snapshot clipboard ops use).
431
+ * Makes a client-side duplicate composable from the hook alone: read a node,
432
+ * then `insert({ type, data, tunes, position })`, no ref escape hatch. Unknown
433
+ * id returns null. Pre-ready: null.
434
+ */
435
+ getBlockData(id: string): { data: BlockToolData; tunes: { [name: string]: BlockTuneData } } | null;
436
+ /**
437
+ * The block whose holder contains/equals `element`, as a snapshot
438
+ * {@link BlockNode}, or null. Maps a DOM event target back to a block.
439
+ */
440
+ getBlockByElement(element: HTMLElement): BlockNode | null;
441
+ /**
442
+ * Read a tool's default empty data WITHOUT inserting anything — delegates to
443
+ * core's `composeBlockData`. Async; rejects for an unknown tool. Pre-ready: `{}`.
444
+ */
445
+ composeBlockData(toolName: string): Promise<BlockToolData>;
446
+ /**
447
+ * Replace the WHOLE document with blocks parsed from an HTML string —
448
+ * delegates to core's `renderFromHTML`. Unlike {@link insertMarkdown} (which is
449
+ * additive), this CLEARS existing content first, so it's a document-load
450
+ * primitive, not an insert. Async. Pre-ready: resolves immediately (no-op).
451
+ */
452
+ renderFromHTML(html: string): Promise<void>;
453
+ /**
454
+ * Insert a flat array of already-serialized {@link OutputBlockData} (the
455
+ * `save()` shape) directly, honoring each block's `parent`/`content` links —
456
+ * the raw counterpart of core's `blocks.insertMany`. Use to re-insert a saved
457
+ * document fragment without reshaping it into {@link TreeInsertSpec}. One atomic
458
+ * undo step. Returns the created nodes; pre-ready: `[]` (no insert).
459
+ */
460
+ insertOutputData(blocks: OutputBlockData[], options?: { index?: number }): BlockNode[];
461
+ /**
462
+ * Atomically split a block: update `currentBlockId` with `currentBlockData`
463
+ * and insert a new `newBlockType` block at `insertIndex`, as ONE undo step.
464
+ * Delegates to core's `splitBlock`. Returns the new node, or null pre-ready.
465
+ */
466
+ splitBlock(
467
+ currentBlockId: string,
468
+ currentBlockData: Partial<BlockToolData>,
469
+ newBlockType: string,
470
+ newBlockData: BlockToolData,
471
+ insertIndex: number
472
+ ): BlockNode | null;
473
+ /**
474
+ * Insert a single child block under `parentId` at flat `insertIndex`, atomically
475
+ * (block creation AND parent assignment in ONE undo step) — delegates to core's
476
+ * `blocks.insertInsideParent`. This is the atomic nested-child creator: prefer it
477
+ * over `insert()` + `nest()`, which is TWO undo steps. A dangling `parentId` is a
478
+ * no-op returning `null` (mirrors {@link insert}); an unknown child tool returns
479
+ * `null`. `childData` defaults to an empty paragraph. Returns the created
480
+ * {@link BlockNode}, fresh-snapshot volatile — read it now, don't stash it in a
481
+ * dep array. Pre-ready: `null`.
482
+ */
483
+ insertInsideParent(parentId: string, insertIndex: number, childData?: BlockToolData): BlockNode | null;
484
+ /**
485
+ * Replace the WHOLE document with blocks parsed from saved {@link OutputData}
486
+ * (the `save()` shape) — delegates to core's `blocks.render`. Unlike
487
+ * {@link insertOutputData}/{@link insertMarkdown} (which are ADDITIVE), this
488
+ * CLEARS existing content first: a document-LOAD primitive, not an insert. The
489
+ * HTML counterpart is {@link renderFromHTML}. Async. Pre-ready: resolves
490
+ * immediately (no-op).
491
+ */
492
+ render(data: OutputData): Promise<void>;
493
+ /**
494
+ * Remove EVERY block from the document — delegates to core's `blocks.clear`. A
495
+ * document-reset primitive (pairs with {@link render}). Async. Pre-ready:
496
+ * resolves immediately (no-op).
497
+ */
498
+ clear(): Promise<void>;
499
+ /**
500
+ * Whether a Yjs sync (undo/redo) is currently in progress — the React mirror of
501
+ * core's `blocks.isSyncingFromYjs`. A METHOD (not a property) so it reads the
502
+ * LIVE flag at call time even though the api handle is memoized. Use it to skip
503
+ * cleanup that would fight Yjs state during an undo/redo. Pre-ready: `false`.
504
+ */
505
+ isSyncingFromYjs(): boolean;
506
+ }
507
+
508
+ /**
509
+ * React hook exposing an id/parentId-relative, reactive view of the block tree.
510
+ * Re-renders whenever the editor emits 'block changed' — including programmatic
511
+ * `nest`/`unnest` reparents, which Blok surfaces as a structural mutation.
512
+ *
513
+ * Reactivity contract: reads refresh ONLY in response to the editor's
514
+ * 'block changed' event. A mutation that advances the editor state WITHOUT
515
+ * emitting it (a tool that mutates its own data without a sync, say) would leave
516
+ * these reads frozen on the previous snapshot until the next emission. The
517
+ * built-in mutators here all route through paths that emit, so this only bites
518
+ * out-of-band tool writes.
519
+ *
520
+ * Pre-ready contract: while `editor` is null (before `useBlok` resolves) the
521
+ * returned API is a stable handle — `insert`/`getById` return `null`,
522
+ * `getChildren` returns `[]`, and the mutators (`move`/`nest`/`unnest`/`remove`/
523
+ * `update`/`convert`/`insertMany`) are no-ops. The one exception is `transact`,
524
+ * which still RUNS its callback even when the editor is null (it is `fn => fn()`)
525
+ * — so it is NOT a pure no-op like the others. Calls made before the editor is
526
+ * ready are otherwise silently dropped, so guard on a non-null editor (or
527
+ * render-gate on it) when an insert must not be lost.
528
+ *
529
+ * Referential stability: the returned API object is stable across renders (it
530
+ * only changes when `editor` does), but each `getById`/`getChildren` call
531
+ * allocates fresh `BlockNode` objects/arrays from a live snapshot. Read them in
532
+ * render and re-read after a change — do NOT put a `getById`/`getChildren`
533
+ * result (or the api handle) in a `useMemo`/`useEffect` dependency array
534
+ * expecting it to change identity per mutation; depend on the node `id`s instead.
535
+ *
536
+ * @param editor - the Blok instance from useBlok, or null before it is ready
537
+ */
538
+ export declare function useBlocks(editor: Blok | null): UseBlocksApi;