@bloklabs/react 1.16.0 → 2.0.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 +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +365 -353
- package/package.json +3 -4
- package/types/index.d.ts +63 -4
- package/src/BlockPortalHost.tsx +0 -58
- package/src/BlokContent.tsx +0 -112
- package/src/BlokEditor.tsx +0 -201
- package/src/BlokView.tsx +0 -106
- package/src/block-portal-registry.ts +0 -161
- package/src/blocks-snapshot.ts +0 -27
- package/src/blok-instance-context.ts +0 -35
- package/src/config-keys.ts +0 -72
- package/src/content-baseline-map.ts +0 -38
- package/src/createReactBlock.tsx +0 -1062
- package/src/createReactInlineTool.tsx +0 -406
- package/src/deep-equal.ts +0 -7
- package/src/holder-map.ts +0 -17
- package/src/index.ts +0 -62
- package/src/live-tool-config.ts +0 -154
- package/src/provide-blok.tsx +0 -70
- package/src/registry-map.ts +0 -21
- package/src/types.ts +0 -38
- package/src/useBlocks.ts +0 -119
- package/src/useBlok.ts +0 -908
- package/src/useBlokHandle.ts +0 -141
- package/src/useBlokReady.ts +0 -60
- package/src/useBlokView.ts +0 -70
- package/src/view-nodes-to-react.ts +0 -153
package/src/provide-blok.tsx
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, type ReactNode } from 'react';
|
|
2
|
-
import type { UseBlokConfig } from './types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Context holding app-wide Blok defaults (shared tools registry, default theme,
|
|
6
|
-
* i18n). Merged UNDER per-instance config by `useBlok` / `<BlokEditor>`. Mirrors
|
|
7
|
-
* Vue's and Angular's `provideBlok`.
|
|
8
|
-
*/
|
|
9
|
-
export const BlokDefaultsContext = createContext<Partial<UseBlokConfig>>({});
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Registers app-wide Blok defaults for every `useBlok` / `<BlokEditor>` rendered
|
|
13
|
-
* beneath it. Per-instance config overrides these, and the `tools` registry is
|
|
14
|
-
* merged (shared registry composes with per-instance additions) rather than
|
|
15
|
-
* replaced. Mirrors Vue's and Angular's `provideBlok`.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <BlokProvider defaults={{ theme: 'dark', tools: sharedTools }}>
|
|
20
|
-
* <App />
|
|
21
|
-
* </BlokProvider>
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export function BlokProvider({
|
|
25
|
-
defaults,
|
|
26
|
-
children,
|
|
27
|
-
}: {
|
|
28
|
-
defaults: Partial<UseBlokConfig>;
|
|
29
|
-
children?: ReactNode;
|
|
30
|
-
}): React.ReactElement {
|
|
31
|
-
return <BlokDefaultsContext.Provider value={defaults}>{children}</BlokDefaultsContext.Provider>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Reads the app-wide Blok defaults from the nearest `BlokProvider` (or `{}`). */
|
|
35
|
-
export function useBlokDefaults(): Partial<UseBlokConfig> {
|
|
36
|
-
return useContext(BlokDefaultsContext);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Merge app-wide `defaults` UNDER a per-instance `config`: a defined config value
|
|
41
|
-
* overrides the default, and the `tools` registry is merged across both layers.
|
|
42
|
-
* Returns the original `config` reference when there are no defaults, so the
|
|
43
|
-
* no-provider path stays identity-stable (no spurious editor recreation).
|
|
44
|
-
*/
|
|
45
|
-
export function mergeBlokDefaults(
|
|
46
|
-
defaults: Partial<UseBlokConfig>,
|
|
47
|
-
config: UseBlokConfig
|
|
48
|
-
): UseBlokConfig {
|
|
49
|
-
if (Object.keys(defaults).length === 0) {
|
|
50
|
-
return config;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const merged: Record<string, unknown> = { ...defaults };
|
|
54
|
-
|
|
55
|
-
for (const key of Object.keys(config)) {
|
|
56
|
-
const value = (config as Record<string, unknown>)[key];
|
|
57
|
-
|
|
58
|
-
if (value !== undefined) {
|
|
59
|
-
merged[key] = value;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// tools registries MERGE across layers (a shared registry composes with
|
|
64
|
-
// per-instance additions) rather than the instance replacing the default.
|
|
65
|
-
if (defaults.tools !== undefined || config.tools !== undefined) {
|
|
66
|
-
merged.tools = { ...defaults.tools, ...config.tools };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return merged;
|
|
70
|
-
}
|
package/src/registry-map.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { BlockPortalRegistry } from './block-portal-registry';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Associates each live Blok instance with its portal registry, keyed by the
|
|
5
|
-
* editor (mirrors the holder WeakMap). `useBlok` sets it at creation;
|
|
6
|
-
* `BlokContent` reads it to mount the shared `BlockPortalHost`. WeakMap → no
|
|
7
|
-
* leak on destroy.
|
|
8
|
-
*/
|
|
9
|
-
const registries = new WeakMap<WeakKey, BlockPortalRegistry>();
|
|
10
|
-
|
|
11
|
-
export function setRegistry(editor: WeakKey, registry: BlockPortalRegistry): void {
|
|
12
|
-
registries.set(editor, registry);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getRegistry(editor: WeakKey): BlockPortalRegistry | undefined {
|
|
16
|
-
return registries.get(editor);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function removeRegistry(editor: WeakKey): void {
|
|
20
|
-
registries.delete(editor);
|
|
21
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { BlokConfig, Blok, EditorWidth } from '@/types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Configuration for useBlok hook.
|
|
5
|
-
* Same as BlokConfig but without `holder` (managed by BlokContent), plus a
|
|
6
|
-
* React-only reactive `width` prop.
|
|
7
|
-
*
|
|
8
|
-
* Reactive props (sync after mount without recreation):
|
|
9
|
-
* - `readOnly` — calls `editor.readOnly.set(value)`; the object form also
|
|
10
|
-
* syncs `hideControls` via `editor.readOnly.set(enabled, { hideControls })`
|
|
11
|
-
* - `hideToolbar` — calls `editor.toolbar.setHidden(value)`
|
|
12
|
-
* - `toolbarPosition` — calls `editor.toolbar.setPosition(value)`
|
|
13
|
-
* - `inlineToolbar` — calls `editor.tools.setInlineToolbar(value)` (arrays
|
|
14
|
-
* are compared by content, so a re-created literal is a no-op)
|
|
15
|
-
* - `autofocus` — calls `editor.focus()` when changed to true
|
|
16
|
-
* - `theme` — calls `editor.theme.set(value)`
|
|
17
|
-
* - `width` — calls `editor.width.set(value)`
|
|
18
|
-
* - `placeholder` — calls `editor.placeholder.set(value)`
|
|
19
|
-
* - `data` — re-renders via `editor.render(value)` when content changes
|
|
20
|
-
* (deep-equal–deduped and serialized; seeds the initial content at creation)
|
|
21
|
-
*
|
|
22
|
-
* The `onSave` config (inherited from `BlokConfig`) is the controlled output
|
|
23
|
-
* half: it fires with the full serialized `OutputData` on every content change,
|
|
24
|
-
* so `data` + `onSave` form a controlled component without manual `save()` calls.
|
|
25
|
-
*/
|
|
26
|
-
export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
|
|
27
|
-
/** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */
|
|
28
|
-
width?: EditorWidth;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Props for the BlokContent component.
|
|
33
|
-
* Extends standard div HTML attributes so className, style, id, etc. pass through.
|
|
34
|
-
*/
|
|
35
|
-
export interface BlokContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
36
|
-
/** The Blok editor instance returned by useBlok. Pass null if editor is not ready yet. */
|
|
37
|
-
editor: Blok | null;
|
|
38
|
-
}
|
package/src/useBlocks.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// packages/react/src/useBlocks.ts
|
|
2
|
-
import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
3
|
-
|
|
4
|
-
import type { Blok } from '@/types';
|
|
5
|
-
import { changeTouchesSubtree, createBlocksApiForEditor, EMPTY_API } from '@bloklabs/core/adapters';
|
|
6
|
-
|
|
7
|
-
import type { UseBlocksApi } from './blocks-snapshot';
|
|
8
|
-
|
|
9
|
-
const BLOCK_CHANGED_EVENT = 'block changed';
|
|
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
|
-
|
|
31
|
-
/**
|
|
32
|
-
* React hook exposing an id/parentId-relative, reactive view of the block tree.
|
|
33
|
-
* Re-renders whenever the editor emits 'block changed' — including programmatic
|
|
34
|
-
* `nest`/`unnest` reparents, which Blok surfaces as a structural mutation.
|
|
35
|
-
*
|
|
36
|
-
* The block-tree logic itself is framework-agnostic and lives in the shared
|
|
37
|
-
* {@link createBlocksApiForEditor} core (one implementation behind both the
|
|
38
|
-
* React and Vue `useBlocks`, so the two adapters cannot drift). This hook only
|
|
39
|
-
* supplies React's reactivity wrapper: `useSyncExternalStore` re-renders the
|
|
40
|
-
* component on 'block changed', and the shared API's reads run live during that
|
|
41
|
-
* render — so React needs no per-read dependency tracking (the default no-op
|
|
42
|
-
* `onRead`).
|
|
43
|
-
*
|
|
44
|
-
* Reactivity contract: reads refresh ONLY in response to the editor's
|
|
45
|
-
* 'block changed' event. A mutation that advances the editor state WITHOUT
|
|
46
|
-
* emitting it (a tool that mutates its own data without a sync, say) would leave
|
|
47
|
-
* these reads frozen on the previous snapshot until the next emission. The
|
|
48
|
-
* built-in mutators here all route through paths that emit, so this only bites
|
|
49
|
-
* out-of-band tool writes.
|
|
50
|
-
*
|
|
51
|
-
* Pre-ready contract: while `editor` is null (before `useBlok` resolves) the
|
|
52
|
-
* returned API is the stable {@link EMPTY_API} — every MUTATOR is a no-op,
|
|
53
|
-
* `insert`/`getById` return `null`, and `getChildren` returns `[]`. The one
|
|
54
|
-
* exception is `transact`, which still invokes its callback even pre-ready.
|
|
55
|
-
* Mutator calls made before the editor is ready are silently dropped, so guard
|
|
56
|
-
* on a non-null editor (or render-gate on it) when an insert must not be lost.
|
|
57
|
-
*
|
|
58
|
-
* Referential stability: the returned API object is stable across renders (it
|
|
59
|
-
* only changes when `editor` does), but each `getById`/`getChildren` call
|
|
60
|
-
* allocates fresh `BlockNode` objects/arrays from a live snapshot. Read them in
|
|
61
|
-
* render and re-read after a change — do NOT put a `getById`/`getChildren`
|
|
62
|
-
* result (or the api handle) in a `useMemo`/`useEffect` dependency array
|
|
63
|
-
* expecting it to change identity per mutation; depend on the node `id`s instead.
|
|
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
|
|
67
|
-
*/
|
|
68
|
-
export function useBlocks(editor: Blok | null, options: UseBlocksOptions = {}): UseBlocksApi {
|
|
69
|
-
const within = options.within ?? null;
|
|
70
|
-
|
|
71
|
-
// Monotonic version bumped on every 'block changed'; drives useSyncExternalStore.
|
|
72
|
-
const versionRef = useRef(0);
|
|
73
|
-
|
|
74
|
-
// Reset the monotonic version when the editor INSTANCE changes so a fresh
|
|
75
|
-
// editor doesn't inherit a stale version from a previous one. Done in render
|
|
76
|
-
// (idempotent: editorRef is updated in the same pass, so a re-render with the
|
|
77
|
-
// same editor won't reset again) — safe for useSyncExternalStore, which just
|
|
78
|
-
// re-reads getSnapshot and re-renders if the value differs.
|
|
79
|
-
const editorRef = useRef(editor);
|
|
80
|
-
|
|
81
|
-
if (editorRef.current !== editor) {
|
|
82
|
-
editorRef.current = editor;
|
|
83
|
-
versionRef.current = 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const subscribe = useCallback(
|
|
87
|
-
(onStoreChange: () => void): (() => void) => {
|
|
88
|
-
if (editor === null) {
|
|
89
|
-
return () => undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const handler = (payload?: unknown): void => {
|
|
93
|
-
if (within !== null && !changeTouchesSubtree(editor, payload, within)) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
versionRef.current += 1;
|
|
98
|
-
onStoreChange();
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
editor.on(BLOCK_CHANGED_EVENT, handler);
|
|
102
|
-
|
|
103
|
-
return () => editor.off(BLOCK_CHANGED_EVENT, handler);
|
|
104
|
-
},
|
|
105
|
-
[editor, within]
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const getSnapshot = useCallback((): number => versionRef.current, []);
|
|
109
|
-
|
|
110
|
-
useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
111
|
-
|
|
112
|
-
// React re-renders the whole component on 'block changed' (above), and the
|
|
113
|
-
// shared API's reads run live during that render, so no per-read dependency
|
|
114
|
-
// tracking is needed — the default no-op `onRead` is correct here.
|
|
115
|
-
return useMemo<UseBlocksApi>(
|
|
116
|
-
() => (editor === null ? EMPTY_API : createBlocksApiForEditor(editor)),
|
|
117
|
-
[editor]
|
|
118
|
-
);
|
|
119
|
-
}
|