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