@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.
@@ -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
- }
@@ -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
- }