@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/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BlokConfig, BlockToolData, OutputBlockData, OutputData, LooseOutputData } from '@bloklabs/core';
|
|
2
2
|
import type { BlocksToHtmlOptions } from '@bloklabs/core/view';
|
|
3
|
-
import type { BlockAPI, BlockToolConstructable, ToolboxConfigEntry } from '@bloklabs/core';
|
|
3
|
+
import type { BlockAPI, BlockOrigin, BlockToolConstructable, ToolboxConfigEntry } from '@bloklabs/core';
|
|
4
4
|
import type { InlineToolConstructable, SanitizerConfig } from '@bloklabs/core';
|
|
5
5
|
import type { API, MarkSnapshot, MarkSpec } from '@bloklabs/core';
|
|
6
6
|
import type { Blok, EditorWidth, BlockRenderedPayload, BlocksRenderedPayload } from '@bloklabs/core';
|
|
@@ -35,6 +35,13 @@ export { getDirection, normalizeLocale } from '@bloklabs/core/locales';
|
|
|
35
35
|
* forwarded — it only affects the initial locale resolution.
|
|
36
36
|
* - `data` — re-renders via `editor.render(value)` when the content changes
|
|
37
37
|
* (deep-equal–deduped and serialized; seeds the initial content at creation)
|
|
38
|
+
* - `onChange`, `onSave`, `onEnter`, `onSubmit`, `onBeforeRender`,
|
|
39
|
+
* `onAfterRender` — calls `editor.handlers.set({ ... })` when a callback
|
|
40
|
+
* APPEARS or DISAPPEARS. Callback presence is load-bearing in core (an
|
|
41
|
+
* `onSubmit` turns Enter into serialize-and-submit; an `onSave` arms the
|
|
42
|
+
* change pipeline), so dropping the prop UNSETS the handler rather than
|
|
43
|
+
* leaving a stale one behind. Callback IDENTITY is always live — the adapter
|
|
44
|
+
* forwards to the latest render, so you never need to memoize them.
|
|
38
45
|
*
|
|
39
46
|
* All other config is consumed once at editor creation.
|
|
40
47
|
*/
|
|
@@ -162,11 +169,21 @@ export interface BlokEditorHandle {
|
|
|
162
169
|
readonly isReady: boolean;
|
|
163
170
|
/** Focus the editor. Returns whether focus landed; `false` (no-op) before ready. */
|
|
164
171
|
focus(atEnd?: boolean): boolean;
|
|
165
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Clear all content. Resolves immediately (no-op) before ready. Safe to mix
|
|
174
|
+
* with a controlled `data` prop: the adapter's content baseline is updated
|
|
175
|
+
* once the clear lands, so setting `data` back to a document the editor itself
|
|
176
|
+
* emitted re-renders it instead of being dismissed as an echo.
|
|
177
|
+
*/
|
|
166
178
|
clear(): Promise<void>;
|
|
167
179
|
/** Serialize the current content, or resolve to `null` before ready. */
|
|
168
180
|
save(): Promise<OutputData | null>;
|
|
169
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* Render new content in place. Resolves immediately (no-op) before ready. Safe
|
|
183
|
+
* to mix with a controlled `data` prop: the rendered document becomes the
|
|
184
|
+
* adapter's content baseline, so a later `data` change back to the previous
|
|
185
|
+
* document still re-renders.
|
|
186
|
+
*/
|
|
170
187
|
render(data: OutputData | LooseOutputData): Promise<void>;
|
|
171
188
|
/** Set or toggle read-only mode. Resolves to the resulting state; `false` (no-op) before ready. */
|
|
172
189
|
setReadOnly(state?: boolean): Promise<boolean>;
|
|
@@ -205,6 +222,17 @@ export declare function BlokProvider(props: {
|
|
|
205
222
|
/** Reads the app-wide Blok defaults from the nearest `BlokProvider` (or `{}`). */
|
|
206
223
|
export declare function useBlokDefaults(): Partial<UseBlokConfig>;
|
|
207
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The live Blok instance a component is rendered inside, or null before it
|
|
227
|
+
* exists. Inside a `createReactBlock` component this is the block's OWN editor —
|
|
228
|
+
* `createPortal` preserves the component tree's context — so
|
|
229
|
+
* `useBlocks(useBlokInstance())` works with no host plumbing, and makes a
|
|
230
|
+
* container block reactive to its own child tree (`useBlocks` re-renders on the
|
|
231
|
+
* editor's `block changed` event). Outside a `BlokContent`/`BlokEditor` subtree
|
|
232
|
+
* it returns null.
|
|
233
|
+
*/
|
|
234
|
+
export declare function useBlokInstance(): Blok | null;
|
|
235
|
+
|
|
208
236
|
/**
|
|
209
237
|
* A plain, serializable view of one block in the tree.
|
|
210
238
|
*
|
|
@@ -671,6 +699,14 @@ export interface ReactBlockRenderProps<Data, Config = Record<string, unknown>> {
|
|
|
671
699
|
commit: (patch: Partial<Data>) => void;
|
|
672
700
|
/** This block's per-block API. */
|
|
673
701
|
block: BlockAPI;
|
|
702
|
+
/**
|
|
703
|
+
* The EDITOR-level API this block belongs to (`api.blocks`, `api.caret`,
|
|
704
|
+
* `api.toolbar`…) — the same object a vanilla tool receives in its
|
|
705
|
+
* constructor. For the reactive, id/parentId-relative view of the tree (and
|
|
706
|
+
* to re-render when your own children change), pair `useBlocks` with
|
|
707
|
+
* `useBlokInstance()` instead; the api handle itself is not reactive.
|
|
708
|
+
*/
|
|
709
|
+
api: API;
|
|
674
710
|
/**
|
|
675
711
|
* Read-only flag. Toggled IN PLACE by core's read-only switch (no remount,
|
|
676
712
|
* ephemeral state survives). Honor it — a block that ignores it stays
|
|
@@ -686,9 +722,41 @@ export interface ReactBlockRenderProps<Data, Config = Record<string, unknown>> {
|
|
|
686
722
|
*/
|
|
687
723
|
config: Readonly<Partial<Config>>;
|
|
688
724
|
/** Engine-owned child slot — render `<BlockChildren />` for a container block. */
|
|
689
|
-
BlockChildren: React.ComponentType
|
|
725
|
+
BlockChildren: React.ComponentType<BlockChildrenProps>;
|
|
690
726
|
}
|
|
691
727
|
|
|
728
|
+
/**
|
|
729
|
+
* One child's decoration: attribute name -> value. `null`/`undefined` removes
|
|
730
|
+
* the attribute; a boolean or number is stringified (so `false` writes
|
|
731
|
+
* `data-active="false"`, which CSS can select, rather than dropping the hook).
|
|
732
|
+
*/
|
|
733
|
+
export type ChildAttributes = Record<string, string | number | boolean | null | undefined>;
|
|
734
|
+
|
|
735
|
+
/** Props of the engine-owned {@link ReactBlockRenderProps.BlockChildren} slot. */
|
|
736
|
+
export interface BlockChildrenProps {
|
|
737
|
+
/**
|
|
738
|
+
* Per-child decoration, applied to each child's HOLDER after the holders are
|
|
739
|
+
* mounted. Named hooks (`data-step-index`, `data-active`…) replace positional
|
|
740
|
+
* `:nth-child()` CSS over Blok's holders, which breaks the moment a child is
|
|
741
|
+
* inserted, removed or reordered. The holders stay DIRECT children of the slot
|
|
742
|
+
* (core requires that for hierarchy reparenting and caret navigation), so
|
|
743
|
+
* decoration is attributes, never wrapper elements. Attributes the callback
|
|
744
|
+
* stops producing are removed on the next pass.
|
|
745
|
+
*
|
|
746
|
+
* Writing on a child's holder is inert by design; the guarantee stops at the
|
|
747
|
+
* holder and its `[data-blok-element-content]` wrapper — writing AT or BELOW a
|
|
748
|
+
* child's tool root DOES score as that child's edit.
|
|
749
|
+
*/
|
|
750
|
+
childAttributes?: (child: BlockAPI, index: number) => ChildAttributes;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Every STATIC member of core's block-tool contract a React block may declare
|
|
755
|
+
* for itself. `toolbox` and `isReadOnlySupported` are excluded — the factory
|
|
756
|
+
* owns them.
|
|
757
|
+
*/
|
|
758
|
+
export type BlockToolStatics = Omit<BlockToolConstructable, 'toolbox' | 'isReadOnlySupported'>;
|
|
759
|
+
|
|
692
760
|
/**
|
|
693
761
|
* Props handed to a React block's `viewComponent` (the read-only renderer):
|
|
694
762
|
* the entry props minus `commit` (no write path) and `readOnly` (always true).
|
|
@@ -739,11 +807,92 @@ export interface CreateReactBlockSpec<
|
|
|
739
807
|
* omit it to keep the single-component in-place toggle.
|
|
740
808
|
*/
|
|
741
809
|
viewComponent?: React.ComponentType<ReactBlockViewProps<Data, Config>>;
|
|
810
|
+
/**
|
|
811
|
+
* Static members of core's tool contract, forwarded verbatim onto the
|
|
812
|
+
* generated tool class (`ownsChildren`, `keepsChildrenOnEnter`,
|
|
813
|
+
* `conversionConfig`, `pasteConfig`, `sanitize`, `shortcut`, `upgradeData`…)
|
|
814
|
+
* — the single channel for everything core reads off the CLASS rather than
|
|
815
|
+
* the instance.
|
|
816
|
+
*
|
|
817
|
+
* `keepsChildrenOnEnter` is the per-tool Enter policy: declare it and Enter on
|
|
818
|
+
* this container's empty LAST child creates the new line INSIDE it instead of
|
|
819
|
+
* escaping to the container's parent (Blok's default, which is Notion's
|
|
820
|
+
* callout behaviour). It is policy the DOM cannot express — a callout renders
|
|
821
|
+
* the same nested-blocks slot as a column yet wants the escape — so a layout
|
|
822
|
+
* container (a card, a `steps` block) had to hijack the editor-global
|
|
823
|
+
* `config.onEnter` before it existed.
|
|
824
|
+
*/
|
|
825
|
+
statics?: BlockToolStatics;
|
|
826
|
+
/**
|
|
827
|
+
* The element the +/drag toolbar should vertically center on — core's
|
|
828
|
+
* `getToolbarAnchorElement` hook, resolved against this block's host element
|
|
829
|
+
* on every call. A container block whose own chrome is not editable needs it:
|
|
830
|
+
* with no anchor, core centers the toolbar on the first `[contenteditable]`
|
|
831
|
+
* under the host, which for a container is its FIRST CHILD BLOCK. Return
|
|
832
|
+
* `null`/`undefined` (or omit) to keep core's default.
|
|
833
|
+
*/
|
|
834
|
+
getToolbarAnchorElement?: (host: HTMLElement, block: BlockAPI) => HTMLElement | null | undefined;
|
|
742
835
|
onRendered?: (block: BlockAPI) => void;
|
|
836
|
+
/**
|
|
837
|
+
* Fired ONCE per block instance, after the portal's FIRST commit — the first
|
|
838
|
+
* moment this block's rendered DOM exists (and, for a container, the moment
|
|
839
|
+
* `<BlockChildren />` has adopted the child holders). `onRendered` cannot mean
|
|
840
|
+
* that: core calls `rendered()` in the same tick as `render()`, before React
|
|
841
|
+
* has drawn anything.
|
|
842
|
+
*
|
|
843
|
+
* It is also the create-vs-restore signal: `context.origin` says whether the
|
|
844
|
+
* author just made this block (`user`/`api`/`convert`) or the document is
|
|
845
|
+
* being re-materialised (`load`/`replay`/`paste`), so a container can seed its
|
|
846
|
+
* default children here exactly once.
|
|
847
|
+
*
|
|
848
|
+
* To observe OTHER blocks settling — e.g. before placing the caret in a child
|
|
849
|
+
* you just inserted — listen for the `block:childrenMounted` editor event
|
|
850
|
+
* instead: `api.events.on('block:childrenMounted', ({ blockId, childIds }) => …)`.
|
|
851
|
+
*/
|
|
852
|
+
onMounted?: (block: BlockAPI, context: ReactBlockMountedContext) => void;
|
|
853
|
+
/**
|
|
854
|
+
* The SEEDING hook: `onMounted`, narrowed to a genuine creation. Fired ONCE
|
|
855
|
+
* per block instance, after the portal's first commit, and only when the
|
|
856
|
+
* author just made this block (`origin` of `user`, `api` or `convert`) —
|
|
857
|
+
* never for a `load`/`replay`/`paste` restore, and never for the off-tree
|
|
858
|
+
* `probe` instance core builds to read a tool's default data.
|
|
859
|
+
*
|
|
860
|
+
* Prefer it over hand-testing `context.origin` in `onMounted`: the intuitive
|
|
861
|
+
* `origin === 'user'` check is wrong — it drops `api.blocks.insert(...)` and
|
|
862
|
+
* turn-into, so the container comes up empty for every path except a
|
|
863
|
+
* keystroke.
|
|
864
|
+
* @example
|
|
865
|
+
* ```tsx
|
|
866
|
+
* onCreated: (block, { api }) => {
|
|
867
|
+
* if (block.getChildren().length === 0) {
|
|
868
|
+
* api.blocks.insertInsideParent(block.id);
|
|
869
|
+
* }
|
|
870
|
+
* }
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
onCreated?: (block: BlockAPI, context: ReactBlockMountedContext) => void;
|
|
743
874
|
onMoved?: (block: BlockAPI) => void;
|
|
744
875
|
onRemoved?: (block: BlockAPI) => void;
|
|
745
876
|
}
|
|
746
877
|
|
|
878
|
+
/**
|
|
879
|
+
* Second argument of {@link CreateReactBlockSpec.onMounted} and
|
|
880
|
+
* {@link CreateReactBlockSpec.onCreated}.
|
|
881
|
+
*/
|
|
882
|
+
export interface ReactBlockMountedContext {
|
|
883
|
+
/**
|
|
884
|
+
* Why this block instance was constructed. CREATION origins (`user`, `api`,
|
|
885
|
+
* `convert`) mean the author just made it, so seeding default children is
|
|
886
|
+
* correct; RESTORE origins (`load`, `replay`, `paste`) mean the document
|
|
887
|
+
* already says what the children are. `probe` is an off-tree instance built
|
|
888
|
+
* only to read a tool's default data and must not touch the block tree.
|
|
889
|
+
* Defaults to `'api'`.
|
|
890
|
+
*/
|
|
891
|
+
origin: BlockOrigin;
|
|
892
|
+
/** The editor-level API (`api.blocks`, `api.caret`, `api.events`…). */
|
|
893
|
+
api: API;
|
|
894
|
+
}
|
|
895
|
+
|
|
747
896
|
/**
|
|
748
897
|
* Author a first-party React block: returns a `BlockToolConstructable`
|
|
749
898
|
* registered exactly like a vanilla tool (`tools: { type: createReactBlock(...) }`).
|
|
@@ -940,7 +1089,13 @@ export interface BlockPortalEntry {
|
|
|
940
1089
|
*/
|
|
941
1090
|
export interface BlockPortalRegistry {
|
|
942
1091
|
register(id: string, entry: BlockPortalEntry): void;
|
|
943
|
-
|
|
1092
|
+
/**
|
|
1093
|
+
* Remove the entry for `id`. Pass the caller's own `hostEl` to make the
|
|
1094
|
+
* removal ownership-checked — the entry is deleted only when it is still the
|
|
1095
|
+
* one that host registered, so a superseded owner's late teardown can never
|
|
1096
|
+
* clobber a same-id re-register.
|
|
1097
|
+
*/
|
|
1098
|
+
unregister(id: string, hostEl?: HTMLElement): void;
|
|
944
1099
|
setProps(id: string, props: Record<string, unknown>): void;
|
|
945
1100
|
/**
|
|
946
1101
|
* Replace the `config` prop of every entry registered under `toolName`, and
|
|
@@ -986,6 +1141,15 @@ export interface BlokViewProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
986
1141
|
blockIds?: BlocksToHtmlOptions['blockIds'];
|
|
987
1142
|
/** Pure URL rewrite hook for block URLs + inline anchors, run before the unsafe-scheme strip. */
|
|
988
1143
|
transformUrl?: BlocksToHtmlOptions['transformUrl'];
|
|
1144
|
+
/** Custom renderers for inline elements, keyed by lowercase tag name (equations, mentions). */
|
|
1145
|
+
inlineRenderers?: BlocksToHtmlOptions['inlineRenderers'];
|
|
1146
|
+
/**
|
|
1147
|
+
* Render with the editor's presentational classes and per-block scaffolding
|
|
1148
|
+
* so the output matches a read-only editor render (default `true`). Needs
|
|
1149
|
+
* `@bloklabs/core/view.css` imported to paint. Set `false` for unstyled
|
|
1150
|
+
* semantic markup.
|
|
1151
|
+
*/
|
|
1152
|
+
classes?: BlocksToHtmlOptions['classes'];
|
|
989
1153
|
}
|
|
990
1154
|
|
|
991
1155
|
/**
|