@bloklabs/angular 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/index.d.ts ADDED
@@ -0,0 +1,675 @@
1
+ import * as i0 from '@angular/core';
2
+ import { AfterViewInit, EventEmitter, OnDestroy, InjectionToken, EnvironmentProviders, Type, Signal } from '@angular/core';
3
+ import { ControlValueAccessor } from '@angular/forms';
4
+ import { BlokConfig, EditorWidth, Blok, OutputData, API, BlockMutationEvent, ResolvedTheme, BlocksRenderedPayload, BlockRenderedPayload, OutputBlockData, ThemeMode, BlockToolData, BlockTuneData, MarkdownImportConfig, ToolboxConfig, BlockAPI, BlockToolConstructorOptions } from '@bloklabs/core';
5
+
6
+ /**
7
+ * Configuration for the Angular adapter. Same as `BlokConfig` but without
8
+ * `holder` (the adapter owns the host element), plus the reactive `width` prop.
9
+ * Mirrors the React adapter's `UseBlokConfig`.
10
+ */
11
+ type BlokAngularConfig = Omit<BlokConfig, 'holder'> & {
12
+ /** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */
13
+ width?: EditorWidth;
14
+ };
15
+
16
+ /**
17
+ * The blessed all-in-one Angular component for embedding Blok (mirrors React's
18
+ * `BlokEditor`). Delegates instance lifecycle to an internal `BlokContentDirective`
19
+ * and layers the typed reactive input/output API on top.
20
+ *
21
+ * Reactive inputs (`readOnly`, `theme`, `width`, `placeholder`, `autofocus`) are
22
+ * synced in place after mount via effects; everything else seeds construction.
23
+ *
24
+ * Implementation note: classic `@Input()`/`@ViewChild()` decorators are used
25
+ * (not signal `input()`/`viewChild()`) for JIT compatibility — see
26
+ * `BlokContentDirective`. Each reactive input is backed by an internal `signal`
27
+ * so effect-based syncing still works; the public template API is unchanged.
28
+ */
29
+ declare class BlokEditorComponent implements AfterViewInit, ControlValueAccessor {
30
+ private readonly ngZone;
31
+ /** App-wide defaults from `provideBlok()`; merged UNDER per-instance inputs. */
32
+ private readonly defaults;
33
+ private contentQuery?;
34
+ /** Signal bridge for the @ViewChild directive so effects react to its arrival. */
35
+ private readonly content;
36
+ /**
37
+ * Output half of two-way `[(data)]`. Emits the editor's serialized content on
38
+ * every change. Wiring the core `onSave` callback is gated on this being
39
+ * observed, since its mere presence makes the core serialize on every batch.
40
+ */
41
+ /** Emits the live Blok instance once ready, after `instance()` is populated. */
42
+ readonly ready: EventEmitter<Blok>;
43
+ readonly dataChange: EventEmitter<OutputData>;
44
+ /** Fires with the full serialized content on every change (notification half). */
45
+ readonly save: EventEmitter<OutputData>;
46
+ /** Raw block mutation channel (core `onChange`). */
47
+ readonly change: EventEmitter<{
48
+ api: API;
49
+ event: BlockMutationEvent | BlockMutationEvent[];
50
+ }>;
51
+ /** Fires after the editor finishes (re-)rendering (core `onAfterRender`). */
52
+ readonly afterRender: EventEmitter<API>;
53
+ /** Fires with the resolved theme whenever it changes (core `onThemeChange`). */
54
+ readonly themeChange: EventEmitter<ResolvedTheme>;
55
+ /** Fires after a batch render completes (core `blocks:rendered` event). */
56
+ readonly blocksRendered: EventEmitter<BlocksRenderedPayload>;
57
+ /** Fires for each block rendered into the DOM (core `block:rendered` event). */
58
+ readonly blockRendered: EventEmitter<BlockRenderedPayload>;
59
+ /**
60
+ * Transform hook applied to blocks before render (core `onBeforeRender`). Must
61
+ * return the (possibly modified) block list. An input, not an output, because
62
+ * it returns a value.
63
+ */
64
+ onBeforeRender?: (blocks: OutputBlockData[]) => OutputBlockData[];
65
+ /**
66
+ * Transform hook applied to pasted HTML (core `onBeforePaste`). Returns the
67
+ * (possibly modified) html, or null to drop the paste.
68
+ */
69
+ onBeforePaste?: (html: string) => string | null;
70
+ private readonly readOnly$;
71
+ set readOnly(value: boolean | undefined);
72
+ private readonly theme$;
73
+ set theme(value: ThemeMode | undefined);
74
+ private readonly width$;
75
+ set width(value: EditorWidth | undefined);
76
+ private readonly placeholder$;
77
+ set placeholder(value: string | false | undefined);
78
+ private readonly autofocus$;
79
+ set autofocus(value: boolean | undefined);
80
+ private readonly data$;
81
+ set data(value: OutputData | undefined);
82
+ tools?: BlokConfig['tools'];
83
+ /**
84
+ * Escape hatch: a full config object for keys without a dedicated input
85
+ * (i18n, sanitizer, minHeight, inlineToolbar, …). Layered between provideBlok
86
+ * defaults and the discrete inputs.
87
+ */
88
+ config?: Partial<BlokAngularConfig>;
89
+ /** Changing this input's identity destroys and recreates the editor (≙ React `deps`). */
90
+ recreateKey: unknown;
91
+ /**
92
+ * Content the editor currently reflects. Set when seeded or rendered (the data
93
+ * effect below). A controlled `data` echo that deep-equals this baseline is a
94
+ * no-op, so it won't clobber the caret. Renders are serialized via `renderChain`.
95
+ */
96
+ private lastRenderedData?;
97
+ private seededEditor;
98
+ private renderChain;
99
+ /** Registered by `ControlValueAccessor.registerOnChange` (Angular forms). */
100
+ private cvaOnChange?;
101
+ private cvaOnTouched?;
102
+ /**
103
+ * Core `onSave` wrapper. Records the editor's own serialized output as the
104
+ * rendered baseline BEFORE notifying Angular, so a controlled consumer echoing
105
+ * it straight back into `data` deep-equals the baseline and is deduped to a
106
+ * no-op (no redundant render, no caret reset). Re-enters the Angular zone since
107
+ * the editor runs outside it.
108
+ */
109
+ private readonly coreOnSave;
110
+ /** Live Blok instance, or null until `isReady` resolves / after destroy. */
111
+ readonly instance: i0.Signal<Blok | null>;
112
+ /** Guards `ready` to one emission per editor instance. */
113
+ private lastReadyInstance;
114
+ /**
115
+ * Construction config handed to the directive. Seeds the editor's initial
116
+ * values; the directive only reads it once (at construction), so post-mount
117
+ * changes to construction-only inputs are no-ops by design.
118
+ */
119
+ buildConfig(): Partial<BlokAngularConfig>;
120
+ ngAfterViewInit(): void;
121
+ /** Serialize the current content. Resolves undefined until the editor is ready. */
122
+ save$(): Promise<OutputData> | undefined;
123
+ /** Move the caret into the editor. */
124
+ focus(atEnd?: boolean): void;
125
+ /** Replace the editor content. Resolves undefined until the editor is ready. */
126
+ render(data: OutputData): Promise<void> | undefined;
127
+ constructor();
128
+ /** Seeds/renders an externally-set form value through the same dedup machinery. */
129
+ writeValue(value: OutputData | null): void;
130
+ registerOnChange(fn: (data: OutputData) => void): void;
131
+ registerOnTouched(fn: () => void): void;
132
+ /** Form disabled state maps to the editor's read-only mode (via the readOnly effect). */
133
+ setDisabledState(isDisabled: boolean): void;
134
+ static ɵfac: i0.ɵɵFactoryDeclaration<BlokEditorComponent, never>;
135
+ static ɵcmp: i0.ɵɵComponentDeclaration<BlokEditorComponent, "blok-editor", ["blok"], { "onBeforeRender": { "alias": "onBeforeRender"; "required": false; }; "onBeforePaste": { "alias": "onBeforePaste"; "required": false; }; "readOnly": { "alias": "readOnly"; "required": false; }; "theme": { "alias": "theme"; "required": false; }; "width": { "alias": "width"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; "data": { "alias": "data"; "required": false; }; "tools": { "alias": "tools"; "required": false; }; "config": { "alias": "config"; "required": false; }; "recreateKey": { "alias": "recreateKey"; "required": false; }; }, { "ready": "ready"; "dataChange": "dataChange"; "save": "save"; "change": "change"; "afterRender": "afterRender"; "themeChange": "themeChange"; "blocksRendered": "blocksRendered"; "blockRendered": "blockRendered"; }, never, never, true, never>;
136
+ }
137
+
138
+ /**
139
+ * Escape-hatch directive and lifecycle engine for Blok (mirrors React's
140
+ * `useBlok` + `BlokContent`). Constructs a Blok instance into its own host
141
+ * element and tears it down on destroy.
142
+ *
143
+ * The editor renders directly into the directive's host element — so the host
144
+ * IS the holder. This sidesteps the fact that Blok core exposes no public
145
+ * `holder` accessor: the adapter never has to read it back, it owns it.
146
+ *
147
+ * `BlokEditorComponent` applies this directive to its internal `<div>` and reads
148
+ * the instance back; consumers can also use it directly for full control:
149
+ * `<div blokContent [config]="cfg" (ready)="onReady($event)"></div>`.
150
+ *
151
+ * Implementation note: classic `@Input()`/`@Output()` are used (not signal
152
+ * `input()`/`output()`) because the repo's Vitest+Analog harness compiles the
153
+ * adapter via JIT, which does not register signal-based members. `instance`
154
+ * remains a plain `signal` (runtime API, JIT-safe) for reactive consumption.
155
+ */
156
+ declare class BlokContentDirective implements OnDestroy {
157
+ private readonly host;
158
+ private readonly ngZone;
159
+ private readonly isBrowser;
160
+ /**
161
+ * App-wide defaults from `provideBlok()`, merged UNDER the bound `[config]` so
162
+ * the escape-hatch path honors them just like `<blok-editor>` (which also
163
+ * merges, idempotently). Mirrors React's `useBlok` merging context defaults.
164
+ */
165
+ private readonly defaults;
166
+ private readonly envInjector;
167
+ private readonly appRef;
168
+ private readonly errorHandler;
169
+ /** Construction-time Blok config (everything except `holder`, which the directive owns). */
170
+ config: Partial<BlokAngularConfig>;
171
+ /** Changing this input's identity (after the first build) destroys + recreates the editor. */
172
+ set recreateKey(value: unknown);
173
+ /** Emits the live Blok instance once it is ready, after `instance` is populated. */
174
+ readonly ready: EventEmitter<Blok>;
175
+ /** The live Blok instance, or null before `isReady` resolves / after destroy. */
176
+ readonly instance: i0.WritableSignal<Blok | null>;
177
+ /** The editor created by the most recent construction; used as the staleness key. */
178
+ private current;
179
+ private destroyed;
180
+ private built;
181
+ private currentKey;
182
+ /** The portal registry for the current editor (Angular-block mounting). */
183
+ private registry;
184
+ constructor();
185
+ /** Construct a Blok into the host element and publish it once ready. */
186
+ private build;
187
+ ngOnDestroy(): void;
188
+ static ɵfac: i0.ɵɵFactoryDeclaration<BlokContentDirective, never>;
189
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BlokContentDirective, "[blokContent]", never, { "config": { "alias": "config"; "required": false; }; "recreateKey": { "alias": "recreateKey"; "required": false; }; }, { "ready": "ready"; }, never, never, true, never>;
190
+ }
191
+
192
+ /**
193
+ * DI token holding app-wide Blok defaults (shared tools registry, default theme,
194
+ * i18n). Merged under per-instance inputs by `BlokEditorComponent`.
195
+ */
196
+ declare const BLOK_DEFAULT_CONFIG: InjectionToken<Partial<BlokAngularConfig>>;
197
+ /**
198
+ * Standalone provider registering app-wide Blok defaults.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * bootstrapApplication(AppComponent, {
203
+ * providers: [provideBlok({ theme: 'dark', tools: sharedTools })],
204
+ * });
205
+ * ```
206
+ */
207
+ declare function provideBlok(defaults: Partial<BlokAngularConfig>): EnvironmentProviders;
208
+
209
+ /**
210
+ * Where to place the caret within a block. `position` selects the input
211
+ * (`'start'`/`'end'`/`'default'`) and `offset` is the character offset within it
212
+ * — the same shape core's `caret.setToBlock` accepts.
213
+ */
214
+ interface CaretTarget {
215
+ position?: 'start' | 'end' | 'default';
216
+ offset?: number;
217
+ }
218
+ /**
219
+ * A plain, serializable view of one block in the tree.
220
+ *
221
+ * Snapshot-volatile: every read allocates a fresh `BlockNode`, and `contentIds`
222
+ * is DERIVED per read from the children that currently name this block as parent
223
+ * (it is not a stored field). Read a node in render and re-read after a change —
224
+ * don't stash one in a `useMemo`/`useEffect` dependency array expecting stable
225
+ * identity; depend on the `id` instead.
226
+ */
227
+ interface BlockNode {
228
+ id: string;
229
+ type: string;
230
+ parentId: string | null;
231
+ contentIds: readonly string[];
232
+ }
233
+ /** Where to place a block among its siblings. */
234
+ type InsertPosition = 'start' | 'end' | {
235
+ before: string;
236
+ } | {
237
+ after: string;
238
+ };
239
+ interface InsertSpec {
240
+ type?: string;
241
+ data?: BlockToolData;
242
+ parentId?: string | null;
243
+ position?: InsertPosition;
244
+ /**
245
+ * Move the caret into the new block. Defaults to `false`: a programmatic
246
+ * insert from React must not steal focus from wherever the user is typing.
247
+ * Set `true` for an explicit "add a block and start editing it" flow.
248
+ */
249
+ focus?: boolean;
250
+ /**
251
+ * Replace the block at the resolved slot instead of inserting a new one — a
252
+ * programmatic "turn into". Combine with a `position` that targets the block
253
+ * to replace, e.g. `{ position: { before: id }, replace: true }`.
254
+ */
255
+ replace?: boolean;
256
+ /**
257
+ * Explicit id for the new block (generated when omitted). Passing a stable id
258
+ * makes the insert idempotent: if a block with this id already exists the
259
+ * existing node is returned and nothing is inserted ("insert if absent"),
260
+ * so an effect that re-runs won't create duplicates.
261
+ */
262
+ id?: string;
263
+ /** Block tune data to apply at creation, keyed by tune name. */
264
+ tunes?: {
265
+ [name: string]: BlockTuneData;
266
+ };
267
+ /**
268
+ * Place the caret inside the newly-created block at a specific position/offset
269
+ * (e.g. `{ offset: 3 }`). Implies focus. Applied ONLY when a block is actually
270
+ * created — an insert-if-absent hit (existing id) does not move the caret.
271
+ * Use this instead of the boolean `focus` when you need a specific offset.
272
+ */
273
+ caret?: CaretTarget;
274
+ }
275
+ /**
276
+ * One node of a pre-built nested subtree for {@link UseBlocksApi.insertTree}.
277
+ *
278
+ * Each node maps to one block; `children` are inserted nested under it (their
279
+ * `parentId` set to this node's id) in array order, recursively. Placement
280
+ * options (`parentId`/`position`) are ROOT-ONLY — they position the whole
281
+ * subtree among existing blocks and are ignored on nested children, whose parent
282
+ * is always their enclosing node.
283
+ */
284
+ interface TreeInsertSpec {
285
+ type?: string;
286
+ data?: BlockToolData;
287
+ tunes?: {
288
+ [name: string]: BlockTuneData;
289
+ };
290
+ /**
291
+ * Explicit id for this node (generated when omitted). Unlike `insert`, this is
292
+ * NOT insert-if-absent: a tree insert always creates fresh blocks. A colliding
293
+ * id — one that already exists in the document, or is reused by another node
294
+ * in the same spec — is REJECTED up front: nothing is inserted and `insertTree`
295
+ * returns `null` (a duplicate id would corrupt every id-keyed lookup).
296
+ */
297
+ id?: string;
298
+ /** Direct children, inserted nested under this node, in array order. */
299
+ children?: TreeInsertSpec[];
300
+ /** Root-only: where to place the whole subtree. Ignored on nested children. */
301
+ parentId?: string | null;
302
+ /** Root-only: slot among siblings of `parentId`. Ignored on nested children. */
303
+ position?: InsertPosition;
304
+ }
305
+ /**
306
+ * Where to move an existing block.
307
+ *
308
+ * `before`/`after` are POSITION targets, not parent assignments: the block is
309
+ * relocated to that flat slot and — because Blok keeps the flat array as the
310
+ * canonical document order — ADOPTS the parent of wherever it lands. Moving a
311
+ * nested block to `{ after: someRootBlock }` therefore unnests it to root, and
312
+ * moving a root block in among a container's children nests it. Use
313
+ * `nest`/`unnest` when you want to change the parent without choosing a sibling
314
+ * slot. `toIndex` is an absolute flat index (clamped into range).
315
+ */
316
+ type MoveTarget = {
317
+ before: string;
318
+ } | {
319
+ after: string;
320
+ } | {
321
+ toIndex: number;
322
+ };
323
+ interface UseBlocksApi {
324
+ getById(id: string): BlockNode | null;
325
+ getChildren(parentId: string | null): BlockNode[];
326
+ /**
327
+ * Insert one block; returns the created node or null when rejected (unknown
328
+ * tool type, dangling `parentId`, or a `replace` whose target is missing). An
329
+ * explicit `id` that already exists is insert-if-absent (returns the existing
330
+ * node, creates nothing). Atomic — one undo step. The returned node is
331
+ * {@link BlockNode}-volatile; read it now, don't put it in a dep array.
332
+ */
333
+ insert(spec?: InsertSpec): BlockNode | null;
334
+ /**
335
+ * Insert several blocks atomically, in array order, as ONE undo step. Each
336
+ * spec is a full {@link InsertSpec} (own type/data/parentId/position), routed
337
+ * through the same single-`insert` path, so per-spec parent assertion and
338
+ * positioning still apply. Specs that fail to insert (e.g. a dangling
339
+ * parentId, or a replace whose target is missing) are dropped; the returned
340
+ * array holds only the successfully created nodes. An empty input is a no-op
341
+ * (returns `[]`, opens no transaction). Like `insert`, the returned nodes are
342
+ * fresh-snapshot volatile — read them now, don't stash them in dep arrays.
343
+ */
344
+ insertMany(specs: InsertSpec[]): BlockNode[];
345
+ /**
346
+ * Insert a pre-built NESTED subtree in ONE atomic operation (one undo step).
347
+ * Each {@link TreeInsertSpec} node becomes a block; its `children` are inserted
348
+ * nested under it (recursively, in array order) so the whole hierarchy lands in
349
+ * a single call — no follow-up `nest` round-trips. Delegates to core's
350
+ * tree-aware `blocks.insertMany`, which composes the flat DFS pre-order array
351
+ * honoring each node's `parent`/`content` links.
352
+ *
353
+ * Placement is root-only: the root node's `parentId`/`position` position the
354
+ * whole subtree among existing blocks (default: appended at the document end);
355
+ * nested children ignore those fields (their parent is their enclosing node). A
356
+ * dangling root `parentId` is rejected — nothing is inserted and `null` is
357
+ * returned (mirrors {@link insert}). Returns the root {@link BlockNode}, which
358
+ * is fresh-snapshot volatile — read it now, don't stash it in a dep array.
359
+ */
360
+ insertTree(spec: TreeInsertSpec): BlockNode | null;
361
+ /**
362
+ * Convert a Markdown string to blocks and insert them ADDITIVELY at a
363
+ * position, WITHOUT clearing the document (unlike core's `importMarkdown` /
364
+ * `renderFromHTML`, which replace the whole document). This is the React
365
+ * "paste markdown → blocks appear" path.
366
+ *
367
+ * Async: the markdown converter is lazy-loaded (kept out of the main bundle),
368
+ * so this is the ONE async creator in the API — `await` the returned promise.
369
+ * The whole batch is inserted as a single atomic undo step.
370
+ *
371
+ * `position` (default `'end'`) places the converted run among `parentId`'s
372
+ * children (or root siblings when `parentId` is omitted/null), reusing the
373
+ * same `start`/`end`/`before`/`after` semantics as {@link insert}.
374
+ *
375
+ * `parentId` (default `null` = root) nests the import: every TOP-LEVEL
376
+ * converted block (one the converter left un-parented) is reparented under
377
+ * `parentId`, while blocks the markdown nested internally (e.g. table-cell
378
+ * children) keep their intra-import parent. A dangling `parentId` is a no-op
379
+ * (returns `[]`, opens no transaction), matching {@link insert}.
380
+ *
381
+ * `config` (optional {@link MarkdownImportConfig}) is forwarded to the
382
+ * converter so custom-tool consumers can map markdown nodes into their tools
383
+ * (`toolMap`/`onUnknownNode`), toggle GFM, or add micromark/mdast extensions.
384
+ *
385
+ * Returns ALL created {@link BlockNode}s in document order — including any the
386
+ * markdown nested internally (e.g. a table's cell children), not just the
387
+ * top-level run (this differs from {@link insertTree}, which returns only the
388
+ * root). Empty or whitespace-only markdown, a dangling `parentId` (checked
389
+ * again after the async convert, so a parent removed mid-flight also no-ops),
390
+ * and a converter failure (chunk-load or parse error, swallowed) all return
391
+ * `[]` and open no transaction. The nodes are fresh-snapshot volatile — read
392
+ * them now, don't stash them in dep arrays.
393
+ */
394
+ insertMarkdown(markdown: string, options?: {
395
+ parentId?: string | null;
396
+ position?: InsertPosition;
397
+ config?: MarkdownImportConfig;
398
+ }): Promise<BlockNode[]>;
399
+ /**
400
+ * Serialize the WHOLE document to a Markdown string — the outbound twin of
401
+ * core's `blocks.exportMarkdown` (and the read-side counterpart of the
402
+ * additive {@link insertMarkdown}). Async: the serializer is lazy-loaded, like
403
+ * the importer. Markdown cannot express every block, so some structure is
404
+ * dropped (table `colspan`/`rowspan`, heading columns). Returns `''` for an
405
+ * empty document. Pre-ready: resolves to `''`.
406
+ */
407
+ exportMarkdown(): Promise<string>;
408
+ move(id: string, target: MoveTarget): void;
409
+ nest(id: string, parentId: string): void;
410
+ unnest(id: string): void;
411
+ remove(id: string): void;
412
+ /**
413
+ * Update a block's data and/or tunes by id. Delegates to core's async
414
+ * `blocks.update`, which forms its OWN undo step — the call is NOT wrapped in
415
+ * `transact` (that would close the group before the async write lands). An
416
+ * unknown id is a silent no-op; a rejected update is swallowed so it can't
417
+ * surface as an unhandled rejection. Reads refresh reactively once core emits
418
+ * 'block changed'. Returns `void`.
419
+ */
420
+ update(id: string, data?: BlockToolData, tunes?: {
421
+ [name: string]: BlockTuneData;
422
+ }): void;
423
+ /**
424
+ * Convert a block to another type ("turn into") by id. Delegates to core's
425
+ * async `blocks.convert`; both tools must provide a `conversionConfig` or core
426
+ * rejects — that rejection (and any other) is swallowed so a non-convertible
427
+ * block is a graceful no-op rather than an unhandled rejection. An unknown id
428
+ * is a silent no-op. Not wrapped in `transact` (core owns its history step).
429
+ * Returns `void`.
430
+ */
431
+ convert(id: string, newType: string, dataOverrides?: BlockToolData, options?: {
432
+ caret?: CaretTarget;
433
+ }): void;
434
+ transact(fn: () => void): void;
435
+ /**
436
+ * Run `fn` as one atomic operation that is NOT captured in the undo history —
437
+ * the React-surface counterpart of core's `transactWithoutCapture`. Use for
438
+ * silent auto-repair/normalization that a user's CMD+Z should never step
439
+ * through. Mutations inside still emit reactively. Pre-ready it just runs `fn`.
440
+ */
441
+ transactWithoutCapture(fn: () => void): void;
442
+ /**
443
+ * The current block count. Reactive (re-reads on 'block changed'). Pre-ready: 0.
444
+ */
445
+ getBlocksCount(): number;
446
+ /**
447
+ * The flat index of the block holding the caret, or -1 when none. Pre-ready: -1.
448
+ */
449
+ getCurrentBlockIndex(): number;
450
+ /** The block at a flat index as a snapshot {@link BlockNode}, or null. */
451
+ getBlockByIndex(index: number): BlockNode | null;
452
+ /**
453
+ * The absolute flat index of a block by id, or null when unknown. The
454
+ * counterpart to {@link getBlockByIndex} — use it to target an off-caret
455
+ * {@link splitBlock} (whose `insertIndex` is absolute) without the ref. Unknown
456
+ * ids return null silently (no console warn). Pre-ready: null.
457
+ */
458
+ getBlockIndex(id: string): number | null;
459
+ /**
460
+ * Read a block's current `data` and `tunes` by id WITHOUT mutating anything —
461
+ * the synchronous last-extracted view (the same snapshot clipboard ops use).
462
+ * Makes a client-side duplicate composable from the hook alone: read a node,
463
+ * then `insert({ type, data, tunes, position })`, no ref escape hatch. Unknown
464
+ * id returns null. Pre-ready: null.
465
+ */
466
+ getBlockData(id: string): {
467
+ data: BlockToolData;
468
+ tunes: {
469
+ [name: string]: BlockTuneData;
470
+ };
471
+ } | null;
472
+ /**
473
+ * The block whose holder contains/equals `element`, as a snapshot
474
+ * {@link BlockNode}, or null. Useful for mapping a DOM event target back to a
475
+ * block.
476
+ */
477
+ getBlockByElement(element: HTMLElement): BlockNode | null;
478
+ /**
479
+ * Read a tool's default empty data WITHOUT inserting anything — delegates to
480
+ * core's `composeBlockData`. Async (a tool's data may be composed lazily).
481
+ * Rejects (via core) for an unknown tool. Pre-ready: resolves to `{}`.
482
+ */
483
+ composeBlockData(toolName: string): Promise<BlockToolData>;
484
+ /**
485
+ * Replace the WHOLE document with blocks parsed from an HTML string —
486
+ * delegates to core's `renderFromHTML`. Unlike {@link insertMarkdown} (which is
487
+ * additive), this CLEARS existing content first, so it's a document-load
488
+ * primitive, not an insert. Async. Pre-ready: resolves immediately (no-op).
489
+ */
490
+ renderFromHTML(html: string): Promise<void>;
491
+ /**
492
+ * Insert a flat array of already-serialized {@link OutputBlockData} (the
493
+ * `save()` shape) directly, honoring each block's `parent`/`content` links —
494
+ * the raw counterpart of core's `blocks.insertMany`. Use to re-insert a saved
495
+ * document fragment without reshaping it into {@link TreeInsertSpec}. One
496
+ * atomic undo step. Returns the created nodes; pre-ready: `[]` (no insert).
497
+ */
498
+ insertOutputData(blocks: OutputBlockData[], options?: {
499
+ index?: number;
500
+ }): BlockNode[];
501
+ /**
502
+ * Atomically split a block: update `currentBlockId` with `currentBlockData`
503
+ * and insert a new `newBlockType` block at `insertIndex`, as ONE undo step.
504
+ * Delegates to core's `splitBlock`. Returns the new node, or null pre-ready /
505
+ * on an unknown id.
506
+ */
507
+ splitBlock(currentBlockId: string, currentBlockData: Partial<BlockToolData>, newBlockType: string, newBlockData: BlockToolData, insertIndex: number): BlockNode | null;
508
+ /**
509
+ * Insert a single child block under `parentId` at flat `insertIndex`, atomically
510
+ * (block creation AND parent assignment in ONE undo step) — delegates to core's
511
+ * `blocks.insertInsideParent`. This is the atomic nested-child creator: prefer it
512
+ * over `insert()` + `nest()`, which is TWO undo steps. A dangling `parentId` is a
513
+ * no-op returning `null` (mirrors {@link insert}); an unknown child tool returns
514
+ * `null`. `childData` defaults to an empty paragraph. Returns the created
515
+ * {@link BlockNode}, fresh-snapshot volatile — read it now, don't stash it in a
516
+ * dep array. Pre-ready: `null`.
517
+ */
518
+ insertInsideParent(parentId: string, insertIndex: number, childData?: BlockToolData): BlockNode | null;
519
+ /**
520
+ * Replace the WHOLE document with blocks parsed from saved {@link OutputData}
521
+ * (the `save()` shape) — delegates to core's `blocks.render`. Unlike
522
+ * {@link insertOutputData}/{@link insertMarkdown} (which are ADDITIVE), this
523
+ * CLEARS existing content first: a document-LOAD primitive, not an insert. The
524
+ * HTML counterpart is {@link renderFromHTML}. Async. Pre-ready: resolves
525
+ * immediately (no-op).
526
+ */
527
+ render(data: OutputData): Promise<void>;
528
+ /**
529
+ * Remove EVERY block from the document — delegates to core's `blocks.clear`. A
530
+ * document-reset primitive (pairs with {@link render}). Async. Pre-ready:
531
+ * resolves immediately (no-op).
532
+ */
533
+ clear(): Promise<void>;
534
+ /**
535
+ * Whether a Yjs sync (undo/redo) is currently in progress — the React mirror of
536
+ * core's `blocks.isSyncingFromYjs`. A METHOD (not a property) so it reads the
537
+ * LIVE flag at call time even though the api handle is memoized. Use it to skip
538
+ * cleanup that would fight Yjs state during an undo/redo. Pre-ready: `false`.
539
+ */
540
+ isSyncingFromYjs(): boolean;
541
+ }
542
+
543
+ /** One field of a block's prop schema. */
544
+ interface PropSchemaEntry {
545
+ /** Default value, used when the incoming data omits this key. */
546
+ default: unknown;
547
+ /** Optional allowed values (advisory; not enforced at runtime in v1). */
548
+ values?: readonly unknown[];
549
+ }
550
+ /**
551
+ * Declarative data shape. The keys here are EXACTLY the keys `save()` returns to
552
+ * Yjs — this closes the per-key-sync key-resurrection gap (a cleared field is
553
+ * written as its explicit default, never dropped).
554
+ */
555
+ type PropSchema = Record<string, PropSchemaEntry>;
556
+
557
+ /** Spec for {@link createAngularBlock}. Authored as a standalone component. */
558
+ interface CreateAngularBlockSpec<Data = BlockToolData> {
559
+ /** Tool type name (registered key). */
560
+ type: string;
561
+ /** Optional toolbox entry. */
562
+ toolbox?: ToolboxConfig;
563
+ /** Declarative defaults that also define the exact `save()` key set. */
564
+ propSchema: PropSchema;
565
+ /**
566
+ * The standalone Angular component to render for each block. It injects the
567
+ * per-block context via `inject(BLOK_BLOCK_CONTEXT)`.
568
+ */
569
+ component: Type<unknown>;
570
+ /** Optional lifecycle callbacks mapped from Blok's block hooks. */
571
+ onRendered?: (block: BlockAPI) => void;
572
+ onMoved?: (block: BlockAPI) => void;
573
+ onRemoved?: (block: BlockAPI) => void;
574
+ }
575
+ /**
576
+ * Author a first-party Angular block. Returns a `BlockToolConstructable`
577
+ * registered exactly like a vanilla tool (`tools: { type: { class:
578
+ * createAngularBlock(...) } }`).
579
+ *
580
+ * The factory owns the host element (`data-blok-mutation-free`), a frozen
581
+ * defaults-filled data mirror, and signals the component reads. It mounts the
582
+ * component into the host via the editor's shared portal registry (the analog of
583
+ * Vue's Teleport registry), bridging Blok's block lifecycle to Angular:
584
+ * - `render()` creates the host and registers the portal entry (mounted sync).
585
+ * - `setData()` dedups, swaps the reactive snapshot, flushes CD, resolves true.
586
+ * - `save()` returns the complete frozen mirror (never the DOM, never partial).
587
+ * - `commit()` merges a patch and fires `dispatchChange` exactly once.
588
+ * - `setReadOnly()` flips a reactive flag and flushes CD (in-place, no remount).
589
+ * - `removed()`/`destroy()` unregister the portal (deterministic unmount).
590
+ */
591
+ declare function createAngularBlock<Data = BlockToolData>(spec: CreateAngularBlockSpec<Data>): (new (options: BlockToolConstructorOptions) => {
592
+ render(): HTMLElement;
593
+ save(): BlockToolData;
594
+ setData(newData: BlockToolData): Promise<boolean>;
595
+ setReadOnly(state: boolean): void;
596
+ rendered(): void;
597
+ moved(): void;
598
+ removed(): void;
599
+ destroy(): void;
600
+ }) & {
601
+ readonly __isBlokAngularBlock: true;
602
+ readonly toolbox: ToolboxConfig | undefined;
603
+ readonly isReadOnlySupported: boolean;
604
+ };
605
+
606
+ /**
607
+ * Angular factory exposing an id/parentId-relative, reactive view of the block
608
+ * tree. Reads refresh whenever the editor emits `block changed`; mutators route
609
+ * through the editor-level `blocks` API (core's chokepoints), so undo/redo and
610
+ * Yjs sync are inherited rather than re-implemented.
611
+ *
612
+ * The block-tree logic is framework-agnostic and lives in the shared
613
+ * {@link createBlocksApiForEditor} core — the SAME implementation behind React's
614
+ * and Vue's `useBlocks`, so all three adapters expose the identical 28-method
615
+ * surface and cannot drift. This wrapper supplies only Angular's reactivity:
616
+ *
617
+ * - A private `version` signal is bumped on every `block changed`. The shared API
618
+ * is built with an `onRead` seam (`() => { version() }`) that every read method
619
+ * calls, so reading inside a `computed`/template tracks `version` and re-runs on
620
+ * each structural mutation.
621
+ * - The bound API is rebuilt (via `bindToEditor`, called eagerly on first call and
622
+ * then tracked by an `effect` for subsequent identity changes) when the editor
623
+ * IDENTITY changes; the returned facade is stable across that swap.
624
+ *
625
+ * Call it in an injection context (component constructor / field initializer),
626
+ * passing the editor signal (e.g. `BlokEditorComponent.instance` /
627
+ * `BlokContentDirective.instance`). Pre-ready (editor null) the bound API is the
628
+ * shared {@link EMPTY_API}: every mutator a no-op, reads empty/null — except
629
+ * `transact`/`transactWithoutCapture`, which still run their callback.
630
+ *
631
+ * Note: Angular 20's `effect()` is scheduled (not eager) — it does not fire
632
+ * synchronously on first call. The initial binding therefore runs synchronously
633
+ * in the `injectBlocks` body itself (mirroring Vue's `{ immediate: true }` watch),
634
+ * and the effect re-binds only on subsequent editor identity changes. When the
635
+ * effect fires for the first time with the same editor already bound, the guard
636
+ * `ed === sub.editor` makes it a no-op, so no double-subscription occurs.
637
+ *
638
+ * @param editor - a signal of the Blok instance, or null pre-ready
639
+ */
640
+ declare function injectBlocks(editor: Signal<Blok | null>): UseBlocksApi;
641
+
642
+ /**
643
+ * Context handed to a `createAngularBlock` component. Delivered via DI (the
644
+ * BLOK_BLOCK_CONTEXT token) rather than @Input, because core constructs the tool
645
+ * outside Angular and the signal-input() form does not compile under the repo's
646
+ * JIT test harness. The ONLY data write path is `commit`.
647
+ */
648
+ interface AngularBlockRenderContext<Data> {
649
+ /** Reactive, FROZEN snapshot of the block data. Read `data()`; never mutate. */
650
+ data: Signal<Readonly<Data>>;
651
+ /** The ONLY data write path: merge a partial patch and sync once. */
652
+ commit: (patch: Partial<Data>) => void;
653
+ /** This block's per-block API (id, getChildren, dispatchChange…). */
654
+ block: BlockAPI;
655
+ /**
656
+ * Reactive read-only flag. Read `readOnly()` in the template to disable
657
+ * editing (drop `contenteditable`, hide controls). Toggled IN PLACE by core's
658
+ * read-only switch — the component reacts without a remount, so ephemeral
659
+ * state survives. A block that ignores it stays interactive when the editor is
660
+ * read-only (same contract as a vanilla tool's `setReadOnly`).
661
+ */
662
+ readOnly: Signal<boolean>;
663
+ /**
664
+ * Container blocks only: append this block's real child holders into `host`
665
+ * (a `data-blok-nested` element the author owns). Call it once in
666
+ * `ngAfterViewInit`; the factory re-runs the same mount on every data change
667
+ * so late-added children appear. Angular must NOT manage these child holders.
668
+ */
669
+ mountChildren: (host: HTMLElement) => void;
670
+ }
671
+ /** DI token carrying the per-block render context into the authored component. */
672
+ declare const BLOK_BLOCK_CONTEXT: InjectionToken<AngularBlockRenderContext<unknown>>;
673
+
674
+ export { BLOK_BLOCK_CONTEXT, BLOK_DEFAULT_CONFIG, BlokContentDirective, BlokEditorComponent, createAngularBlock, injectBlocks, provideBlok };
675
+ export type { AngularBlockRenderContext, BlockNode, BlokAngularConfig, CaretTarget, CreateAngularBlockSpec, InsertPosition, InsertSpec, MoveTarget, PropSchema, PropSchemaEntry, TreeInsertSpec, UseBlocksApi };