@bloklabs/react 1.7.0 → 1.9.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/dist/index.cjs +1 -1
- package/dist/index.mjs +434 -291
- package/package.json +2 -2
- package/src/BlokContent.tsx +11 -1
- package/src/BlokView.tsx +4 -0
- package/src/block-portal-registry.ts +20 -4
- package/src/blok-instance-context.ts +35 -0
- package/src/config-keys.ts +1 -0
- package/src/content-baseline-map.ts +38 -0
- package/src/createReactBlock.tsx +431 -16
- package/src/index.ts +5 -0
- package/src/useBlok.ts +185 -56
- package/src/useBlokHandle.ts +40 -4
- package/src/useBlokView.ts +4 -2
- package/types/index.d.ts +169 -5
package/src/useBlok.ts
CHANGED
|
@@ -10,9 +10,15 @@ import {
|
|
|
10
10
|
type BlockPortalRegistry,
|
|
11
11
|
} from './block-portal-registry';
|
|
12
12
|
import { setRegistry, removeRegistry } from './registry-map';
|
|
13
|
+
import { setContentBaseline, removeContentBaseline } from './content-baseline-map';
|
|
13
14
|
import { bindLiveToolConfigFunctions, buildLiveBlockConfig } from './live-tool-config';
|
|
14
|
-
import {
|
|
15
|
-
|
|
15
|
+
import {
|
|
16
|
+
createEmittedEchoWindow,
|
|
17
|
+
equalsOutputData,
|
|
18
|
+
normalizeReadOnlyConfig,
|
|
19
|
+
toRenderableData,
|
|
20
|
+
} from '@bloklabs/core/adapters';
|
|
21
|
+
import type { Blok, LiveHandlers } from '@/types';
|
|
16
22
|
import type { UseBlokConfig } from './types';
|
|
17
23
|
|
|
18
24
|
interface EditorInstanceState {
|
|
@@ -135,12 +141,15 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
135
141
|
const depsToken = useMemo(() => ({}), deps ?? []);
|
|
136
142
|
|
|
137
143
|
// Tracks the data the editor currently reflects — set when content is seeded
|
|
138
|
-
// or rendered (the reactive `data` effect below)
|
|
139
|
-
// own serialized content via `onSave
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
+
// or rendered (the reactive `data` effect below), when the editor emits its
|
|
145
|
+
// own serialized content via `onSave`, and when an IMPERATIVE call changed the
|
|
146
|
+
// content out of band (the content-baseline channel registered below). The
|
|
147
|
+
// `onSave` writer is what makes a controlled `onSave -> setData -> data`
|
|
148
|
+
// round-trip a no-op: the echoed payload content-equals this baseline, so the
|
|
149
|
+
// data effect skips the redundant render() (which would otherwise reset the
|
|
150
|
+
// caret). Declared here so the `onSave` wrapper below can update it before the
|
|
151
|
+
// consumer's setState re-runs the data effect. `undefined` means "nothing
|
|
152
|
+
// recorded yet" — never an empty document.
|
|
144
153
|
const lastRenderedDataRef = useRef(config.data);
|
|
145
154
|
// Window of recently emitted `onSave` payloads. The baseline above only
|
|
146
155
|
// remembers the LAST one, but a host that persists on save and refetches can
|
|
@@ -172,6 +181,19 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
172
181
|
// Last-applied tool-level `toolbox` setting per tool name (dedupe baseline for
|
|
173
182
|
// the reactive toolbox effect below). Seeded from the construction-time tools.
|
|
174
183
|
const appliedToolboxRef = useRef<Map<string, unknown>>(new Map());
|
|
184
|
+
// Live callback channel (see the handler-presence effect below): the current
|
|
185
|
+
// editor's stable per-handler wrappers (each forwards to the LATEST render's
|
|
186
|
+
// prop through `configRef`, so their identity never changes) and the presence
|
|
187
|
+
// actually installed on that editor — the dedupe baseline.
|
|
188
|
+
const handlerWrappersRef = useRef<LiveHandlers | null>(null);
|
|
189
|
+
const appliedHandlerPresenceRef = useRef<Record<keyof LiveHandlers, boolean>>({
|
|
190
|
+
onChange: false,
|
|
191
|
+
onSave: false,
|
|
192
|
+
onEnter: false,
|
|
193
|
+
onSubmit: false,
|
|
194
|
+
onBeforeRender: false,
|
|
195
|
+
onAfterRender: false,
|
|
196
|
+
});
|
|
175
197
|
|
|
176
198
|
// Main lifecycle effect
|
|
177
199
|
useEffect(() => {
|
|
@@ -212,6 +234,7 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
212
234
|
if (state.editor !== null && !state.isDestroyed) {
|
|
213
235
|
removeHolder(state.editor);
|
|
214
236
|
removeRegistry(state.editor);
|
|
237
|
+
removeContentBaseline(state.editor);
|
|
215
238
|
try {
|
|
216
239
|
state.editor.destroy();
|
|
217
240
|
} catch {
|
|
@@ -259,60 +282,69 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
259
282
|
])
|
|
260
283
|
);
|
|
261
284
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
configRef.current.onReady?.(...args);
|
|
268
|
-
},
|
|
285
|
+
// Stable per-editor wrappers for the live callback config. Each forwards to
|
|
286
|
+
// the LATEST render's prop through `configRef`, so a consumer never has to
|
|
287
|
+
// freeze callback identities — and, because identity is fixed for the
|
|
288
|
+
// editor's life, only PRESENCE has to be synced afterwards (below).
|
|
289
|
+
const handlerWrappers: LiveHandlers = {
|
|
269
290
|
onChange: (...args: Parameters<NonNullable<UseBlokConfig['onChange']>>): void => {
|
|
270
291
|
configRef.current.onChange?.(...args);
|
|
271
292
|
},
|
|
272
|
-
|
|
273
|
-
onEnter: (...args: Parameters<NonNullable<UseBlokConfig['onEnter']>>): boolean | void =>
|
|
274
|
-
configRef.current.onEnter?.(...args),
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Only attach onSubmit when the consumer opted in: its mere presence makes
|
|
278
|
-
// Enter serialize-and-submit instead of splitting a block, so an absent prop
|
|
279
|
-
// must stay absent. The wrapper reads through the ref so the latest callback
|
|
280
|
-
// is always used without recreating the editor.
|
|
281
|
-
if (currentConfig.onSubmit) {
|
|
282
|
-
blokConfig.onSubmit = (...args: Parameters<NonNullable<UseBlokConfig['onSubmit']>>): void => {
|
|
283
|
-
configRef.current.onSubmit?.(...args);
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Only attach onSave when the consumer opted in: its mere presence makes the
|
|
288
|
-
// core serialize on every change batch, so an absent prop must stay absent.
|
|
289
|
-
// The wrapper reads through the ref so the latest callback is always used.
|
|
290
|
-
if (currentConfig.onSave) {
|
|
291
|
-
blokConfig.onSave = (...args: Parameters<NonNullable<UseBlokConfig['onSave']>>): void => {
|
|
293
|
+
onSave: (...args: Parameters<NonNullable<UseBlokConfig['onSave']>>): void => {
|
|
292
294
|
// Record the editor's own serialized output as the rendered baseline so a
|
|
293
295
|
// controlled consumer echoing it straight back into `data` is a no-op —
|
|
294
296
|
// no redundant render(), no caret reset, no round-trip recursion.
|
|
295
297
|
lastRenderedDataRef.current = args[0];
|
|
296
298
|
echoWindowRef.current.record(args[0]);
|
|
297
299
|
configRef.current.onSave?.(...args);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
},
|
|
301
|
+
// Forward the return value: it is the "handled" signal the core acts on.
|
|
302
|
+
onEnter: (...args: Parameters<NonNullable<UseBlokConfig['onEnter']>>): boolean | void =>
|
|
303
|
+
configRef.current.onEnter?.(...args),
|
|
304
|
+
onSubmit: (...args: Parameters<NonNullable<UseBlokConfig['onSubmit']>>): void => {
|
|
305
|
+
configRef.current.onSubmit?.(...args);
|
|
306
|
+
},
|
|
307
|
+
onBeforeRender: (
|
|
306
308
|
...args: Parameters<NonNullable<UseBlokConfig['onBeforeRender']>>
|
|
307
309
|
): ReturnType<NonNullable<UseBlokConfig['onBeforeRender']>> =>
|
|
308
|
-
configRef.current.onBeforeRender?.(...args) ?? args[0]
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (currentConfig.onAfterRender) {
|
|
312
|
-
blokConfig.onAfterRender = (...args: Parameters<NonNullable<UseBlokConfig['onAfterRender']>>): void => {
|
|
310
|
+
configRef.current.onBeforeRender?.(...args) ?? args[0],
|
|
311
|
+
onAfterRender: (...args: Parameters<NonNullable<UseBlokConfig['onAfterRender']>>): void => {
|
|
313
312
|
configRef.current.onAfterRender?.(...args);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
handlerWrappersRef.current = handlerWrappers;
|
|
317
|
+
|
|
318
|
+
// Handler PRESENCE is load-bearing in core — an `onSubmit` turns Enter into
|
|
319
|
+
// serialize-and-submit, an `onSave` arms the change pipeline — so an absent
|
|
320
|
+
// prop must leave the key absent. Attach only the wrappers whose prop the
|
|
321
|
+
// consumer actually passed, and record what was installed so the effect
|
|
322
|
+
// below can push later presence flips instead of recreating the editor.
|
|
323
|
+
const handlerPresence: Record<keyof LiveHandlers, boolean> = {
|
|
324
|
+
onChange: currentConfig.onChange !== undefined,
|
|
325
|
+
onSave: currentConfig.onSave !== undefined,
|
|
326
|
+
onEnter: currentConfig.onEnter !== undefined,
|
|
327
|
+
onSubmit: currentConfig.onSubmit !== undefined,
|
|
328
|
+
onBeforeRender: currentConfig.onBeforeRender !== undefined,
|
|
329
|
+
onAfterRender: currentConfig.onAfterRender !== undefined,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
appliedHandlerPresenceRef.current = handlerPresence;
|
|
333
|
+
|
|
334
|
+
const blokConfig = {
|
|
335
|
+
...currentConfig,
|
|
336
|
+
...(liveTools === undefined ? {} : { tools: liveTools as UseBlokConfig['tools'] }),
|
|
337
|
+
holder,
|
|
338
|
+
onReady: (...args: Parameters<NonNullable<UseBlokConfig['onReady']>>): void => {
|
|
339
|
+
configRef.current.onReady?.(...args);
|
|
340
|
+
},
|
|
341
|
+
...(handlerPresence.onChange ? { onChange: handlerWrappers.onChange } : {}),
|
|
342
|
+
...(handlerPresence.onSave ? { onSave: handlerWrappers.onSave } : {}),
|
|
343
|
+
...(handlerPresence.onEnter ? { onEnter: handlerWrappers.onEnter } : {}),
|
|
344
|
+
...(handlerPresence.onSubmit ? { onSubmit: handlerWrappers.onSubmit } : {}),
|
|
345
|
+
...(handlerPresence.onBeforeRender ? { onBeforeRender: handlerWrappers.onBeforeRender } : {}),
|
|
346
|
+
...(handlerPresence.onAfterRender ? { onAfterRender: handlerWrappers.onAfterRender } : {}),
|
|
347
|
+
};
|
|
316
348
|
|
|
317
349
|
const blok = new BlokRuntime(blokConfig);
|
|
318
350
|
state.editor = blok;
|
|
@@ -321,6 +353,19 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
321
353
|
constructedDataRef.current = currentConfig.data;
|
|
322
354
|
setHolder(blok, holder);
|
|
323
355
|
setRegistry(blok, registry);
|
|
356
|
+
// Imperative-content channel: `useBlokHandle().clear()` / `.render()` change
|
|
357
|
+
// the content OUT OF BAND of the reactive `data` effect below. They report
|
|
358
|
+
// the new content here so the dedupe baseline and the emitted-echo window
|
|
359
|
+
// (both read by that effect) describe the editor as it actually is —
|
|
360
|
+
// otherwise restoring a document the editor itself emitted is dismissed as
|
|
361
|
+
// an echo and never rendered.
|
|
362
|
+
setContentBaseline(blok, {
|
|
363
|
+
markRendered: (content): void => {
|
|
364
|
+
echoWindowRef.current.clear();
|
|
365
|
+
lastRenderedDataRef.current = content;
|
|
366
|
+
pendingDataRef.current = undefined;
|
|
367
|
+
},
|
|
368
|
+
});
|
|
324
369
|
|
|
325
370
|
void blok.isReady
|
|
326
371
|
.then(() => {
|
|
@@ -333,6 +378,7 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
333
378
|
if (state.editor === blok && !state.isDestroyed) {
|
|
334
379
|
removeHolder(blok);
|
|
335
380
|
removeRegistry(blok);
|
|
381
|
+
removeContentBaseline(blok);
|
|
336
382
|
try {
|
|
337
383
|
blok.destroy();
|
|
338
384
|
} catch {
|
|
@@ -445,6 +491,74 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
445
491
|
}
|
|
446
492
|
});
|
|
447
493
|
|
|
494
|
+
// Reactive: onChange
|
|
495
|
+
// Reactive: onSave
|
|
496
|
+
// Reactive: onEnter
|
|
497
|
+
// Reactive: onSubmit
|
|
498
|
+
// Reactive: onBeforeRender
|
|
499
|
+
// Reactive: onAfterRender
|
|
500
|
+
//
|
|
501
|
+
// Callback PRESENCE was frozen at construction, and in core the presence of a
|
|
502
|
+
// handler IS the semantics: an `onSubmit` makes Enter serialize-and-submit
|
|
503
|
+
// instead of splitting the block, an `onSave` arms the whole change-observation
|
|
504
|
+
// pipeline. A host whose "Enter sends" toggle (or `onSave` subscription) lives
|
|
505
|
+
// in React state therefore had to bump `deps` and destroy/rebuild the editor,
|
|
506
|
+
// losing caret and undo history. Instead, diff each handler's presence against
|
|
507
|
+
// what is installed and push genuine flips through the runtime `handlers.set`
|
|
508
|
+
// API — passing `undefined` for a prop that disappeared, which is what makes
|
|
509
|
+
// the change reversible rather than a one-way latch. Identity never changes
|
|
510
|
+
// (the wrappers forward through `configRef`), so only presence is diffed.
|
|
511
|
+
// Applies once the editor is ready — module APIs attach at isReady — and the
|
|
512
|
+
// ready transition re-renders, so a flip made earlier is not lost.
|
|
513
|
+
useEffect(() => {
|
|
514
|
+
const state = stateRef.current;
|
|
515
|
+
const wrappers = handlerWrappersRef.current;
|
|
516
|
+
|
|
517
|
+
if (state.editor === null || state.isDestroyed || !state.isEditorReady || wrappers === null) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const latest = configRef.current;
|
|
522
|
+
const applied = appliedHandlerPresenceRef.current;
|
|
523
|
+
const diff: LiveHandlers = {};
|
|
524
|
+
const changed: { value: boolean } = { value: false };
|
|
525
|
+
|
|
526
|
+
const sync = <K extends keyof LiveHandlers>(key: K, write: (value: LiveHandlers[K]) => void): void => {
|
|
527
|
+
const present = latest[key] !== undefined;
|
|
528
|
+
|
|
529
|
+
if (present === applied[key]) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
applied[key] = present;
|
|
534
|
+
changed.value = true;
|
|
535
|
+
write(present ? wrappers[key] : undefined);
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
sync('onChange', (value) => {
|
|
539
|
+
diff.onChange = value;
|
|
540
|
+
});
|
|
541
|
+
sync('onSave', (value) => {
|
|
542
|
+
diff.onSave = value;
|
|
543
|
+
});
|
|
544
|
+
sync('onEnter', (value) => {
|
|
545
|
+
diff.onEnter = value;
|
|
546
|
+
});
|
|
547
|
+
sync('onSubmit', (value) => {
|
|
548
|
+
diff.onSubmit = value;
|
|
549
|
+
});
|
|
550
|
+
sync('onBeforeRender', (value) => {
|
|
551
|
+
diff.onBeforeRender = value;
|
|
552
|
+
});
|
|
553
|
+
sync('onAfterRender', (value) => {
|
|
554
|
+
diff.onAfterRender = value;
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
if (changed.value) {
|
|
558
|
+
state.editor.handlers.set(diff);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
448
562
|
// Reactive: readOnly (object form normalized so the effect deps are stable booleans).
|
|
449
563
|
// The object form additionally syncs `hideControls` through the options
|
|
450
564
|
// parameter of `readOnly.set` — a plain boolean prop keeps the historical
|
|
@@ -597,11 +711,12 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
597
711
|
//
|
|
598
712
|
// `data` seeds the editor at construction. Afterwards, changing the prop to
|
|
599
713
|
// new *content* re-renders the editor via the public render() API — no
|
|
600
|
-
// recreation. Updates are
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
//
|
|
714
|
+
// recreation. Updates are content-deduped against the editor's current
|
|
715
|
+
// baseline (`lastRenderedDataRef`, declared above and also updated by the
|
|
716
|
+
// `onSave` wrapper and the imperative content-baseline channel) so a new
|
|
717
|
+
// reference with the same content — including the editor's own serialized
|
|
718
|
+
// output echoed back, however the host reshaped it in transit — is a no-op and
|
|
719
|
+
// won't clobber the caret. Renders are serialized so rapid changes can't overlap.
|
|
605
720
|
const { data } = config;
|
|
606
721
|
useEffect(() => {
|
|
607
722
|
if (editor === null || data === undefined) {
|
|
@@ -644,7 +759,20 @@ export function useBlok(configInput: UseBlokConfig, deps?: DependencyList): Blok
|
|
|
644
759
|
// Skip when the editor already reflects this content (last SUCCESSFUL render)
|
|
645
760
|
// OR a render of the same content is already queued/in-flight — re-rendering
|
|
646
761
|
// identical content would needlessly reset the caret.
|
|
647
|
-
|
|
762
|
+
//
|
|
763
|
+
// Compared with the SAME structural lens the echo window uses
|
|
764
|
+
// (`equalsOutputData`, not a raw deep-equal): a host that persists the
|
|
765
|
+
// editor's own document and hands back a stripped copy — fresh `time`,
|
|
766
|
+
// dropped ids, no `lastEditedAt` stamp — is still echoing content the editor
|
|
767
|
+
// already shows. `undefined` means "nothing recorded yet", which is NOT an
|
|
768
|
+
// empty document, so it must never match.
|
|
769
|
+
const baseline = lastRenderedDataRef.current;
|
|
770
|
+
const inFlight = pendingDataRef.current;
|
|
771
|
+
|
|
772
|
+
if (
|
|
773
|
+
(baseline !== undefined && equalsOutputData(data, baseline)) ||
|
|
774
|
+
(inFlight !== undefined && equalsOutputData(data, inFlight))
|
|
775
|
+
) {
|
|
648
776
|
return;
|
|
649
777
|
}
|
|
650
778
|
|
|
@@ -701,6 +829,7 @@ function deferDestroy(
|
|
|
701
829
|
if (state.editor !== null) {
|
|
702
830
|
removeHolder(state.editor);
|
|
703
831
|
removeRegistry(state.editor);
|
|
832
|
+
removeContentBaseline(state.editor);
|
|
704
833
|
try {
|
|
705
834
|
state.editor.destroy();
|
|
706
835
|
} catch {
|
package/src/useBlokHandle.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { useRef, useMemo, type RefCallback } from 'react';
|
|
2
|
+
import { EMPTY_OUTPUT_DATA } from '@bloklabs/core/adapters';
|
|
3
|
+
import { getContentBaseline } from './content-baseline-map';
|
|
2
4
|
import type { Blok, OutputData, LooseOutputData } from '@/types';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -32,12 +34,22 @@ export interface BlokEditorHandle {
|
|
|
32
34
|
* @param atEnd - place the caret at the end of the focused block
|
|
33
35
|
*/
|
|
34
36
|
focus(atEnd?: boolean): boolean;
|
|
35
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Clear all content. Resolves immediately (no-op) before ready.
|
|
39
|
+
*
|
|
40
|
+
* Safe to mix with a controlled `data` prop: the adapter's content baseline is
|
|
41
|
+
* updated once the clear lands, so setting `data` back to the document the
|
|
42
|
+
* editor itself emitted re-renders it instead of being dismissed as an echo.
|
|
43
|
+
*/
|
|
36
44
|
clear(): Promise<void>;
|
|
37
45
|
/** Serialize the current content, or resolve to `null` before ready. */
|
|
38
46
|
save(): Promise<OutputData | null>;
|
|
39
47
|
/**
|
|
40
48
|
* Render new content in place. Resolves immediately (no-op) before ready.
|
|
49
|
+
*
|
|
50
|
+
* Safe to mix with a controlled `data` prop: the rendered document becomes the
|
|
51
|
+
* adapter's content baseline, so a later `data` change back to the previous
|
|
52
|
+
* document still re-renders.
|
|
41
53
|
* @param data - the document to render
|
|
42
54
|
*/
|
|
43
55
|
render(data: OutputData | LooseOutputData): Promise<void>;
|
|
@@ -81,11 +93,35 @@ export function useBlokHandle(): BlokEditorHandle {
|
|
|
81
93
|
return instanceRef.current !== null;
|
|
82
94
|
},
|
|
83
95
|
focus: (atEnd?: boolean): boolean => instanceRef.current?.focus(atEnd) ?? false,
|
|
84
|
-
clear
|
|
96
|
+
// clear/render change the content OUT OF BAND of the controlled `data`
|
|
97
|
+
// effect, so they report the result to the adapter's content baseline (see
|
|
98
|
+
// content-baseline-map). Reported only once the call RESOLVES: a failed one
|
|
99
|
+
// leaves the editor on its previous content, and a baseline pointing at
|
|
100
|
+
// content the editor never showed would dedupe away the correcting update.
|
|
101
|
+
clear: (): Promise<void> => {
|
|
102
|
+
const instance = instanceRef.current;
|
|
103
|
+
|
|
104
|
+
if (instance === null) {
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return instance.clear().then(() => {
|
|
109
|
+
getContentBaseline(instance)?.markRendered(EMPTY_OUTPUT_DATA);
|
|
110
|
+
});
|
|
111
|
+
},
|
|
85
112
|
save: (): Promise<OutputData | null> =>
|
|
86
113
|
instanceRef.current !== null ? instanceRef.current.save() : Promise.resolve(null),
|
|
87
|
-
render: (data: OutputData | LooseOutputData): Promise<void> =>
|
|
88
|
-
instanceRef.current
|
|
114
|
+
render: (data: OutputData | LooseOutputData): Promise<void> => {
|
|
115
|
+
const instance = instanceRef.current;
|
|
116
|
+
|
|
117
|
+
if (instance === null) {
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return instance.render(data).then(() => {
|
|
122
|
+
getContentBaseline(instance)?.markRendered(data);
|
|
123
|
+
});
|
|
124
|
+
},
|
|
89
125
|
setReadOnly: (state?: boolean): Promise<boolean> => {
|
|
90
126
|
const instance = instanceRef.current;
|
|
91
127
|
|
package/src/useBlokView.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { viewNodesToReact } from './view-nodes-to-react';
|
|
|
18
18
|
* bindings map from remains `@experimental` — but consumers of this hook never
|
|
19
19
|
* touch it directly.
|
|
20
20
|
* @param data - saved document (strict or loose wire shape; nullish tolerated)
|
|
21
|
-
* @param options - schema / renderers / unknown-block policy / toolAttributes / blockIds / transformUrl
|
|
21
|
+
* @param options - schema / renderers / inlineRenderers / unknown-block policy / toolAttributes / blockIds / transformUrl
|
|
22
22
|
*/
|
|
23
23
|
export const useBlokView = (
|
|
24
24
|
data: OutputData | LooseOutputData | null | undefined,
|
|
@@ -30,6 +30,7 @@ export const useBlokView = (
|
|
|
30
30
|
const toolAttributes = options?.toolAttributes;
|
|
31
31
|
const blockIds = options?.blockIds;
|
|
32
32
|
const transformUrl = options?.transformUrl;
|
|
33
|
+
const inlineRenderers = options?.inlineRenderers;
|
|
33
34
|
const classes = options?.classes;
|
|
34
35
|
|
|
35
36
|
return useMemo(() => {
|
|
@@ -44,6 +45,7 @@ export const useBlokView = (
|
|
|
44
45
|
toolAttributes,
|
|
45
46
|
blockIds,
|
|
46
47
|
transformUrl,
|
|
48
|
+
inlineRenderers,
|
|
47
49
|
/**
|
|
48
50
|
* Opt-in, NOT defaulted on. Parity rendering wraps every block in the
|
|
49
51
|
* core's holder → content scaffolding, which would contradict this
|
|
@@ -58,5 +60,5 @@ export const useBlokView = (
|
|
|
58
60
|
})
|
|
59
61
|
)
|
|
60
62
|
);
|
|
61
|
-
}, [data, schema, renderers, onUnknownBlock, toolAttributes, blockIds, transformUrl, classes]);
|
|
63
|
+
}, [data, schema, renderers, onUnknownBlock, toolAttributes, blockIds, transformUrl, inlineRenderers, classes]);
|
|
62
64
|
};
|