@bloklabs/react 1.8.0 → 1.10.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 +242 -251
- package/package.json +2 -2
- package/src/BlokView.tsx +4 -0
- package/src/createReactBlock.tsx +74 -51
- package/src/index.ts +1 -0
- package/src/useBlocks.ts +32 -4
- package/src/useBlokView.ts +4 -2
- package/types/index.d.ts +72 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloklabs/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "React block editor — official React bindings for Blok, the headless Notion-like rich text editor. Hooks and components that render blocks and save to JSON.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "JackUait",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"test": "yarn run -T vitest run"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@bloklabs/core": "^1.
|
|
57
|
+
"@bloklabs/core": "^1.10.0",
|
|
58
58
|
"react": "^18.0.0 || ^19.0.0",
|
|
59
59
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
60
60
|
}
|
package/src/BlokView.tsx
CHANGED
|
@@ -24,6 +24,8 @@ export interface BlokViewProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chi
|
|
|
24
24
|
blockIds?: BlocksToHtmlOptions['blockIds'];
|
|
25
25
|
/** Pure URL rewrite hook for block URLs + inline anchors, run before the unsafe-scheme strip. */
|
|
26
26
|
transformUrl?: BlocksToHtmlOptions['transformUrl'];
|
|
27
|
+
/** Custom renderers for inline elements, keyed by lowercase tag name (equations, mentions). */
|
|
28
|
+
inlineRenderers?: BlocksToHtmlOptions['inlineRenderers'];
|
|
27
29
|
/**
|
|
28
30
|
* Render with the editor's presentational classes and per-block scaffolding
|
|
29
31
|
* so the output matches a read-only editor render (default `true`). Needs
|
|
@@ -56,6 +58,7 @@ export const BlokView = ({
|
|
|
56
58
|
toolAttributes,
|
|
57
59
|
blockIds,
|
|
58
60
|
transformUrl,
|
|
61
|
+
inlineRenderers,
|
|
59
62
|
classes,
|
|
60
63
|
...divProps
|
|
61
64
|
}: BlokViewProps): ReactNode => {
|
|
@@ -73,6 +76,7 @@ export const BlokView = ({
|
|
|
73
76
|
toolAttributes,
|
|
74
77
|
blockIds,
|
|
75
78
|
transformUrl,
|
|
79
|
+
inlineRenderers,
|
|
76
80
|
classes: classes ?? true,
|
|
77
81
|
});
|
|
78
82
|
|
package/src/createReactBlock.tsx
CHANGED
|
@@ -2,7 +2,9 @@ import { isValidElement, useEffect, useRef, type ComponentType, type ReactElemen
|
|
|
2
2
|
import { flushSync } from 'react-dom';
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import {
|
|
5
|
+
applyChildDecoration,
|
|
5
6
|
BlockChildrenMounted,
|
|
7
|
+
createChildDecorationLedger,
|
|
6
8
|
DATA_ATTR,
|
|
7
9
|
deepEqual,
|
|
8
10
|
fillDefaults,
|
|
@@ -47,55 +49,6 @@ const INTERNAL_CONFIG_KEYS: readonly string[] = [
|
|
|
47
49
|
*/
|
|
48
50
|
export type BlockToolStatics = Omit<BlockToolConstructable, 'toolbox' | 'isReadOnlySupported'>;
|
|
49
51
|
|
|
50
|
-
/** What the previous decoration pass wrote for one child, so it can be undone. */
|
|
51
|
-
interface StampedChild {
|
|
52
|
-
holder: HTMLElement;
|
|
53
|
-
names: string[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Apply one pass of per-child holder attributes and clear the previous pass's
|
|
58
|
-
* leftovers — including on a child that has since left the container, which
|
|
59
|
-
* would otherwise keep a dead index forever.
|
|
60
|
-
* @param stamped - the slot's ledger of what the last pass wrote (mutated)
|
|
61
|
-
* @param children - the container's model children, in model order
|
|
62
|
-
* @param decorate - the authored per-child decorator, if any
|
|
63
|
-
*/
|
|
64
|
-
const applyChildAttributes = (
|
|
65
|
-
stamped: Map<string, StampedChild>,
|
|
66
|
-
children: BlockAPI[],
|
|
67
|
-
decorate: BlockChildrenProps['childAttributes']
|
|
68
|
-
): void => {
|
|
69
|
-
const next = new Map<string, StampedChild>();
|
|
70
|
-
|
|
71
|
-
children.forEach((child, index) => {
|
|
72
|
-
const names: string[] = [];
|
|
73
|
-
|
|
74
|
-
for (const [name, value] of Object.entries(decorate?.(child, index) ?? {})) {
|
|
75
|
-
if (value === null || value === undefined) {
|
|
76
|
-
child.holder.removeAttribute(name);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
child.holder.setAttribute(name, String(value));
|
|
81
|
-
names.push(name);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
next.set(child.id, { holder: child.holder, names });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
for (const [id, previous] of stamped) {
|
|
88
|
-
const current = next.get(id);
|
|
89
|
-
|
|
90
|
-
previous.names
|
|
91
|
-
.filter(name => current === undefined || !current.names.includes(name))
|
|
92
|
-
.forEach(name => previous.holder.removeAttribute(name));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
stamped.clear();
|
|
96
|
-
next.forEach((entry, id) => stamped.set(id, entry));
|
|
97
|
-
};
|
|
98
|
-
|
|
99
52
|
/**
|
|
100
53
|
* Announce that a container's child holders have settled in its slot — the
|
|
101
54
|
* signal a host waits on before putting the caret into a freshly inserted
|
|
@@ -187,6 +140,27 @@ export interface ReactBlockRenderProps<Data, Config = Record<string, unknown>> {
|
|
|
187
140
|
config: Readonly<Partial<Config>>;
|
|
188
141
|
/** Engine-owned child slot — render `<BlockChildren />` for a container block. */
|
|
189
142
|
BlockChildren: ComponentType<BlockChildrenProps>;
|
|
143
|
+
/**
|
|
144
|
+
* Attach this to the element the +/drag toolbar should vertically center on:
|
|
145
|
+
* `<div ref={toolbarAnchorRef}>`. The React-native way to answer core's
|
|
146
|
+
* `getToolbarAnchorElement`, which is otherwise a `(host, block) => Element`
|
|
147
|
+
* hook resolved OUTSIDE the component tree — so pointing at an element you
|
|
148
|
+
* render meant inventing a data attribute and `querySelector`-ing for it from
|
|
149
|
+
* {@link CreateReactBlockSpec.getToolbarAnchorElement}.
|
|
150
|
+
*
|
|
151
|
+
* A container block whose own chrome is not editable needs an anchor: with
|
|
152
|
+
* none, core centers the toolbar on the first `[contenteditable]` under the
|
|
153
|
+
* host, which for a container is its FIRST CHILD BLOCK — parking the +/drag
|
|
154
|
+
* handles halfway down, beside content that has a toolbar of its own.
|
|
155
|
+
*
|
|
156
|
+
* The ref is stable across renders and outranks
|
|
157
|
+
* {@link CreateReactBlockSpec.getToolbarAnchorElement} while it holds a
|
|
158
|
+
* MOUNTED element; when the element unmounts (React calls the ref with null,
|
|
159
|
+
* or it is detached) the declared hook takes over again, so the toolbar is
|
|
160
|
+
* never positioned against a stale node. Leave it unattached to keep core's
|
|
161
|
+
* default.
|
|
162
|
+
*/
|
|
163
|
+
toolbarAnchorRef: (element: HTMLElement | null) => void;
|
|
190
164
|
}
|
|
191
165
|
|
|
192
166
|
/**
|
|
@@ -222,6 +196,28 @@ export interface BlockChildrenProps {
|
|
|
222
196
|
* @param index - the child's position in the container's model children
|
|
223
197
|
*/
|
|
224
198
|
childAttributes?: (child: BlockAPI, index: number) => ChildAttributes;
|
|
199
|
+
/**
|
|
200
|
+
* The same per-child decoration, applied one level IN: on each child's
|
|
201
|
+
* `[data-blok-element-content]` wrapper instead of its holder. Core's
|
|
202
|
+
* child-holder decoration law blesses both, and a container needs both — the
|
|
203
|
+
* holder is the child's outer box (rails, indices, hover states), while the
|
|
204
|
+
* content wrapper is where the child's own text box begins, which is what a
|
|
205
|
+
* numbered rail or a connector line has to align to.
|
|
206
|
+
*
|
|
207
|
+
* Reach for it instead of walking core's wrapper chain from a holder hook
|
|
208
|
+
* (`[data-step] > [data-blok-element-content] > …`): those selectors encode
|
|
209
|
+
* engine DOM structure in host CSS and break the moment that structure changes.
|
|
210
|
+
* With a named hook the selector is one flat attribute.
|
|
211
|
+
*
|
|
212
|
+
* Same cleanup and same inertness as {@link BlockChildrenProps.childAttributes}
|
|
213
|
+
* — attributes the callback stops producing are removed on the next pass, and
|
|
214
|
+
* writes here are still not the child's edit. A child whose DOM has not
|
|
215
|
+
* committed yet (a portal-rendered block, on the first pass) has no wrapper to
|
|
216
|
+
* write to and is skipped, then stamped on the next pass.
|
|
217
|
+
* @param child - the child block's API
|
|
218
|
+
* @param index - the child's position in the container's model children
|
|
219
|
+
*/
|
|
220
|
+
childContentAttributes?: (child: BlockAPI, index: number) => ChildAttributes;
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
/**
|
|
@@ -694,6 +690,17 @@ export function createReactBlock<Data = BlockToolData, Config = Record<string, u
|
|
|
694
690
|
private hostEl: HTMLElement | null = null;
|
|
695
691
|
/** True while a deferred dispatch is waiting for a pointer drag to end. */
|
|
696
692
|
private pendingDispatch = false;
|
|
693
|
+
/** Element the component pointed `toolbarAnchorRef` at, if any. */
|
|
694
|
+
private anchorEl: HTMLElement | null = null;
|
|
695
|
+
/**
|
|
696
|
+
* The `toolbarAnchorRef` prop. A bound field so its identity is STABLE
|
|
697
|
+
* across renders — a fresh callback ref each render would make React detach
|
|
698
|
+
* (null) and re-attach it on every commit.
|
|
699
|
+
* @param element - the element React attached, or null on detach
|
|
700
|
+
*/
|
|
701
|
+
private readonly toolbarAnchorRef = (element: HTMLElement | null): void => {
|
|
702
|
+
this.anchorEl = element;
|
|
703
|
+
};
|
|
697
704
|
|
|
698
705
|
public constructor(options: BlockToolConstructorOptions) {
|
|
699
706
|
this.blockApi = options.block;
|
|
@@ -736,10 +743,11 @@ export function createReactBlock<Data = BlockToolData, Config = Record<string, u
|
|
|
736
743
|
// appends the real child holders imperatively (React never reconciles them).
|
|
737
744
|
this.childrenComponent = function BlockChildren({
|
|
738
745
|
childAttributes,
|
|
746
|
+
childContentAttributes,
|
|
739
747
|
}: BlockChildrenProps): ReactElement {
|
|
740
748
|
const slotRef = useRef<HTMLDivElement | null>(null);
|
|
741
749
|
// What the last decoration pass wrote, so a dropped key is cleaned up.
|
|
742
|
-
const
|
|
750
|
+
const ledgerRef = useRef(createChildDecorationLedger());
|
|
743
751
|
|
|
744
752
|
// No dep array: re-adopt children after EVERY render (mirrors the Vue
|
|
745
753
|
// adapter's onMounted + onUpdated pair).
|
|
@@ -751,7 +759,8 @@ export function createReactBlock<Data = BlockToolData, Config = Record<string, u
|
|
|
751
759
|
const children = blockApi.getChildren();
|
|
752
760
|
|
|
753
761
|
mountChildBlocks(slotRef.current, children);
|
|
754
|
-
|
|
762
|
+
applyChildDecoration(ledgerRef.current, children, { childAttributes,
|
|
763
|
+
childContentAttributes });
|
|
755
764
|
emitChildrenMounted(api, blockApi.id, children);
|
|
756
765
|
});
|
|
757
766
|
|
|
@@ -879,6 +888,19 @@ export function createReactBlock<Data = BlockToolData, Config = Record<string, u
|
|
|
879
888
|
* default positioning already assumes.
|
|
880
889
|
*/
|
|
881
890
|
public getToolbarAnchorElement(): HTMLElement | undefined {
|
|
891
|
+
/**
|
|
892
|
+
* A ref the component attached wins — it names the exact element, chosen
|
|
893
|
+
* from inside the tree. `isConnected` is the guard that matters: React
|
|
894
|
+
* nulls the ref on unmount, but a component that re-parents its anchor can
|
|
895
|
+
* leave a detached node here, and positioning the toolbar against one
|
|
896
|
+
* silently parks it at 0,0.
|
|
897
|
+
*/
|
|
898
|
+
const fromRef = this.anchorEl;
|
|
899
|
+
|
|
900
|
+
if (fromRef !== null && fromRef.isConnected) {
|
|
901
|
+
return fromRef;
|
|
902
|
+
}
|
|
903
|
+
|
|
882
904
|
const host = this.hostEl;
|
|
883
905
|
|
|
884
906
|
if (host === null || spec.getToolbarAnchorElement === undefined) {
|
|
@@ -916,6 +938,7 @@ export function createReactBlock<Data = BlockToolData, Config = Record<string, u
|
|
|
916
938
|
readOnly: this.readOnly,
|
|
917
939
|
config: this.toolConfig,
|
|
918
940
|
BlockChildren: this.childrenComponent,
|
|
941
|
+
toolbarAnchorRef: this.toolbarAnchorRef,
|
|
919
942
|
};
|
|
920
943
|
|
|
921
944
|
return props as unknown as Record<string, unknown>;
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { useBlokInstance } from './blok-instance-context';
|
|
|
12
12
|
export type { UseBlokConfig, BlokContentProps } from './types';
|
|
13
13
|
export type { BlokEditorProps } from './BlokEditor';
|
|
14
14
|
export { useBlocks } from './useBlocks';
|
|
15
|
+
export type { UseBlocksOptions } from './useBlocks';
|
|
15
16
|
export { useBlokReady } from './useBlokReady';
|
|
16
17
|
export type { UseBlokReadyOptions } from './useBlokReady';
|
|
17
18
|
export { BlokView } from './BlokView';
|
package/src/useBlocks.ts
CHANGED
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
3
3
|
|
|
4
4
|
import type { Blok } from '@/types';
|
|
5
|
-
import { createBlocksApiForEditor, EMPTY_API } from '@bloklabs/core/adapters';
|
|
5
|
+
import { changeTouchesSubtree, createBlocksApiForEditor, EMPTY_API } from '@bloklabs/core/adapters';
|
|
6
6
|
|
|
7
7
|
import type { UseBlocksApi } from './blocks-snapshot';
|
|
8
8
|
|
|
9
9
|
const BLOCK_CHANGED_EVENT = 'block changed';
|
|
10
10
|
|
|
11
|
+
/** Options for {@link useBlocks}. */
|
|
12
|
+
export interface UseBlocksOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Re-render only for changes inside the subtree rooted at this block id (the
|
|
15
|
+
* block itself or any descendant). Omit — or pass `null` — for the
|
|
16
|
+
* document-wide default.
|
|
17
|
+
*
|
|
18
|
+
* This bounds REACTIVITY, not reads: the returned API still sees the whole
|
|
19
|
+
* tree, so a scoped consumer can still `getById` anything. Reach for it in a
|
|
20
|
+
* container block that renders only its own children — unscoped, such a block
|
|
21
|
+
* re-renders on every keystroke anywhere in the document, and a page of N
|
|
22
|
+
* containers turns one keystroke into N re-renders.
|
|
23
|
+
*
|
|
24
|
+
* A change whose block cannot be placed in the tree (a removal that emits
|
|
25
|
+
* after the block is gone) counts as in-scope: skipping it would leave a
|
|
26
|
+
* container rendering a child that no longer exists.
|
|
27
|
+
*/
|
|
28
|
+
within?: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
/**
|
|
12
32
|
* React hook exposing an id/parentId-relative, reactive view of the block tree.
|
|
13
33
|
* Re-renders whenever the editor emits 'block changed' — including programmatic
|
|
@@ -42,8 +62,12 @@ const BLOCK_CHANGED_EVENT = 'block changed';
|
|
|
42
62
|
* result (or the api handle) in a `useMemo`/`useEffect` dependency array
|
|
43
63
|
* expecting it to change identity per mutation; depend on the node `id`s instead.
|
|
44
64
|
* @param editor - the Blok instance from useBlok, or null before it is ready
|
|
65
|
+
* @param options - reactivity options; see {@link UseBlocksOptions.within} to
|
|
66
|
+
* scope re-renders to one block's subtree
|
|
45
67
|
*/
|
|
46
|
-
export function useBlocks(editor: Blok | null): UseBlocksApi {
|
|
68
|
+
export function useBlocks(editor: Blok | null, options: UseBlocksOptions = {}): UseBlocksApi {
|
|
69
|
+
const within = options.within ?? null;
|
|
70
|
+
|
|
47
71
|
// Monotonic version bumped on every 'block changed'; drives useSyncExternalStore.
|
|
48
72
|
const versionRef = useRef(0);
|
|
49
73
|
|
|
@@ -65,7 +89,11 @@ export function useBlocks(editor: Blok | null): UseBlocksApi {
|
|
|
65
89
|
return () => undefined;
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
const handler = (): void => {
|
|
92
|
+
const handler = (payload?: unknown): void => {
|
|
93
|
+
if (within !== null && !changeTouchesSubtree(editor, payload, within)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
69
97
|
versionRef.current += 1;
|
|
70
98
|
onStoreChange();
|
|
71
99
|
};
|
|
@@ -74,7 +102,7 @@ export function useBlocks(editor: Blok | null): UseBlocksApi {
|
|
|
74
102
|
|
|
75
103
|
return () => editor.off(BLOCK_CHANGED_EVENT, handler);
|
|
76
104
|
},
|
|
77
|
-
[editor]
|
|
105
|
+
[editor, within]
|
|
78
106
|
);
|
|
79
107
|
|
|
80
108
|
const getSnapshot = useCallback((): number => versionRef.current, []);
|
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
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -631,8 +631,33 @@ export interface UseBlocksApi {
|
|
|
631
631
|
* expecting it to change identity per mutation; depend on the node `id`s instead.
|
|
632
632
|
*
|
|
633
633
|
* @param editor - the Blok instance from useBlok, or null before it is ready
|
|
634
|
+
* @param options - reactivity options; see {@link UseBlocksOptions.within}
|
|
634
635
|
*/
|
|
635
|
-
export declare function useBlocks(editor: Blok | null): UseBlocksApi;
|
|
636
|
+
export declare function useBlocks(editor: Blok | null, options?: UseBlocksOptions): UseBlocksApi;
|
|
637
|
+
|
|
638
|
+
/** Options for {@link useBlocks}. */
|
|
639
|
+
export interface UseBlocksOptions {
|
|
640
|
+
/**
|
|
641
|
+
* Re-render only for changes inside the subtree rooted at this block id (the
|
|
642
|
+
* block itself or any descendant). Omit — or pass `null` — for the
|
|
643
|
+
* document-wide default.
|
|
644
|
+
*
|
|
645
|
+
* This bounds REACTIVITY, not reads: the returned API still sees the whole
|
|
646
|
+
* tree, so a scoped consumer can still `getById` anything. Reach for it in a
|
|
647
|
+
* container block that renders only its own children — unscoped, such a block
|
|
648
|
+
* re-renders on every keystroke anywhere in the document, and a page of N
|
|
649
|
+
* containers turns one keystroke into N re-renders.
|
|
650
|
+
*
|
|
651
|
+
* A change whose block cannot be placed in the tree (a removal that emits
|
|
652
|
+
* after the block is gone) counts as in-scope: skipping it would leave a
|
|
653
|
+
* container rendering a child that no longer exists.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* // A container block re-rendering only for its own subtree.
|
|
657
|
+
* const blocks = useBlocks(useBlokInstance(), { within: block.id });
|
|
658
|
+
*/
|
|
659
|
+
within?: string | null;
|
|
660
|
+
}
|
|
636
661
|
|
|
637
662
|
// ---------------------------------------------------------------------------
|
|
638
663
|
// Scoped readiness (useBlokReady)
|
|
@@ -723,6 +748,25 @@ export interface ReactBlockRenderProps<Data, Config = Record<string, unknown>> {
|
|
|
723
748
|
config: Readonly<Partial<Config>>;
|
|
724
749
|
/** Engine-owned child slot — render `<BlockChildren />` for a container block. */
|
|
725
750
|
BlockChildren: React.ComponentType<BlockChildrenProps>;
|
|
751
|
+
/**
|
|
752
|
+
* Attach this to the element the +/drag toolbar should vertically center on:
|
|
753
|
+
* `<div ref={toolbarAnchorRef}>`. The React-native way to answer core's
|
|
754
|
+
* `getToolbarAnchorElement`, which is otherwise a `(host, block) => Element`
|
|
755
|
+
* hook resolved OUTSIDE the component tree — so pointing at an element you
|
|
756
|
+
* render meant inventing a data attribute and `querySelector`-ing for it.
|
|
757
|
+
*
|
|
758
|
+
* A container block whose own chrome is not editable needs an anchor: with
|
|
759
|
+
* none, core centers the toolbar on the first `[contenteditable]` under the
|
|
760
|
+
* host, which for a container is its FIRST CHILD BLOCK — parking the +/drag
|
|
761
|
+
* handles halfway down, beside content that has a toolbar of its own.
|
|
762
|
+
*
|
|
763
|
+
* The ref is stable across renders and outranks the spec's
|
|
764
|
+
* `getToolbarAnchorElement` while it holds a MOUNTED element; once the element
|
|
765
|
+
* unmounts or detaches, the declared hook takes over again, so the toolbar is
|
|
766
|
+
* never positioned against a stale node. Leave it unattached to keep core's
|
|
767
|
+
* default positioning.
|
|
768
|
+
*/
|
|
769
|
+
toolbarAnchorRef: (element: HTMLElement | null) => void;
|
|
726
770
|
}
|
|
727
771
|
|
|
728
772
|
/**
|
|
@@ -748,6 +792,24 @@ export interface BlockChildrenProps {
|
|
|
748
792
|
* child's tool root DOES score as that child's edit.
|
|
749
793
|
*/
|
|
750
794
|
childAttributes?: (child: BlockAPI, index: number) => ChildAttributes;
|
|
795
|
+
/**
|
|
796
|
+
* The same per-child decoration, applied one level IN: on each child's
|
|
797
|
+
* `[data-blok-element-content]` wrapper instead of its holder. Core's
|
|
798
|
+
* child-holder decoration law blesses both, and a container needs both — the
|
|
799
|
+
* holder is the child's outer box (rails, indices, hover states), while the
|
|
800
|
+
* content wrapper is where the child's own text box begins, which is what a
|
|
801
|
+
* numbered rail or a connector line has to align to.
|
|
802
|
+
*
|
|
803
|
+
* Reach for it instead of walking Blok's wrapper chain from a holder hook
|
|
804
|
+
* (`[data-step] > [data-blok-element-content] > …`): those selectors encode
|
|
805
|
+
* engine DOM structure in host CSS and break the moment that structure changes.
|
|
806
|
+
* With a named hook the selector is one flat attribute.
|
|
807
|
+
*
|
|
808
|
+
* Same cleanup and same inertness as `childAttributes`. A child whose DOM has
|
|
809
|
+
* not committed yet (a portal-rendered block, on the first pass) has no wrapper
|
|
810
|
+
* to write to and is stamped on the next pass.
|
|
811
|
+
*/
|
|
812
|
+
childContentAttributes?: (child: BlockAPI, index: number) => ChildAttributes;
|
|
751
813
|
}
|
|
752
814
|
|
|
753
815
|
/**
|
|
@@ -1141,6 +1203,15 @@ export interface BlokViewProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
1141
1203
|
blockIds?: BlocksToHtmlOptions['blockIds'];
|
|
1142
1204
|
/** Pure URL rewrite hook for block URLs + inline anchors, run before the unsafe-scheme strip. */
|
|
1143
1205
|
transformUrl?: BlocksToHtmlOptions['transformUrl'];
|
|
1206
|
+
/** Custom renderers for inline elements, keyed by lowercase tag name (equations, mentions). */
|
|
1207
|
+
inlineRenderers?: BlocksToHtmlOptions['inlineRenderers'];
|
|
1208
|
+
/**
|
|
1209
|
+
* Render with the editor's presentational classes and per-block scaffolding
|
|
1210
|
+
* so the output matches a read-only editor render (default `true`). Needs
|
|
1211
|
+
* `@bloklabs/core/view.css` imported to paint. Set `false` for unstyled
|
|
1212
|
+
* semantic markup.
|
|
1213
|
+
*/
|
|
1214
|
+
classes?: BlocksToHtmlOptions['classes'];
|
|
1144
1215
|
}
|
|
1145
1216
|
|
|
1146
1217
|
/**
|