@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.
- package/dist/index.cjs +1 -0
- package/dist/index.mjs +313 -0
- package/package.json +40 -0
- package/src/BlokContent.tsx +46 -0
- package/src/BlokEditor.tsx +157 -0
- package/src/blocks-snapshot.ts +27 -0
- package/src/config-keys.ts +56 -0
- package/src/deep-equal.ts +7 -0
- package/src/holder-map.ts +17 -0
- package/src/index.ts +16 -0
- package/src/provide-blok.tsx +70 -0
- package/src/types.ts +33 -0
- package/src/useBlocks.ts +91 -0
- package/src/useBlok.ts +365 -0
- package/types/index.d.ts +538 -0
package/types/index.d.ts
ADDED
|
@@ -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;
|