@bloklabs/vue 1.16.1 → 2.1.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/src/holder-map.ts DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * Module-level WeakMap associating editor instances with their detached holder
3
- * divs. WeakMap ensures no memory leaks — when the editor is garbage collected,
4
- * the holder reference is released. `useBlok` registers the holder at creation;
5
- * `BlokContent` reads it to adopt the editor's DOM into its container.
6
- */
7
- const holders = new WeakMap<WeakKey, HTMLDivElement>();
8
-
9
- export function setHolder(editor: WeakKey, holder: HTMLDivElement): void {
10
- holders.set(editor, holder);
11
- }
12
-
13
- export function getHolder(editor: WeakKey): HTMLDivElement | undefined {
14
- return holders.get(editor);
15
- }
16
-
17
- export function removeHolder(editor: WeakKey): void {
18
- holders.delete(editor);
19
- }
package/src/index.ts DELETED
@@ -1,30 +0,0 @@
1
- export { useBlok } from './useBlok';
2
- export { useBlocks } from './useBlocks';
3
- export type { UseBlocksOptions } from './useBlocks';
4
- export { useBlokReady } from './useBlokReady';
5
- export { BlokContent } from './BlokContent';
6
- export { BlokEditor } from './BlokEditor';
7
- export { provideBlok, useBlokDefaults, BLOK_DEFAULT_CONFIG } from './provide-blok';
8
- export { useBlokInstance, BLOK_EDITOR_INSTANCE } from './blok-instance';
9
- export { createVueBlock } from './createVueBlock';
10
- export type { UseBlokConfig, BlokContentProps } from './types';
11
- export type { UseBlokReadyOptions } from './useBlokReady';
12
- export type {
13
- UseBlocksApi,
14
- BlockNode,
15
- CaretTarget,
16
- InsertPosition,
17
- InsertSpec,
18
- TreeInsertSpec,
19
- MoveTarget,
20
- } from './blocks-snapshot';
21
- export type {
22
- BlockToolStatics,
23
- ChildAttributes,
24
- ChildAttributesFn,
25
- CreateVueBlockSpec,
26
- PropSchema,
27
- PropSchemaEntry,
28
- VueBlockMountedContext,
29
- VueBlockRenderProps,
30
- } from './createVueBlock';
@@ -1,65 +0,0 @@
1
- import { inject, provide, type InjectionKey } from 'vue';
2
- import type { UseBlokConfig } from './types';
3
-
4
- /**
5
- * Injection key holding app-wide Blok defaults (shared tools registry, default
6
- * theme, i18n). Merged UNDER per-instance config by `useBlok` (and therefore by
7
- * `<BlokEditor>`, which wires `useBlok`).
8
- */
9
- export const BLOK_DEFAULT_CONFIG: InjectionKey<Partial<UseBlokConfig>> = Symbol('BLOK_DEFAULT_CONFIG');
10
-
11
- /**
12
- * Registers app-wide Blok defaults in the current component's provide scope
13
- * (call inside a parent component's `setup`). Mirrors React's `BlokProvider` and
14
- * Angular's `provideBlok`.
15
- *
16
- * @example
17
- * ```ts
18
- * setup() {
19
- * provideBlok({ theme: 'dark', tools: sharedTools });
20
- * }
21
- * ```
22
- */
23
- export function provideBlok(defaults: Partial<UseBlokConfig>): void {
24
- provide(BLOK_DEFAULT_CONFIG, defaults);
25
- }
26
-
27
- /**
28
- * Reads the app-wide Blok defaults from the nearest `provideBlok` (or `{}`).
29
- * Mirrors React's `useBlokDefaults`. Call inside `setup`.
30
- */
31
- export function useBlokDefaults(): Partial<UseBlokConfig> {
32
- return inject(BLOK_DEFAULT_CONFIG, {});
33
- }
34
-
35
- /**
36
- * Merge app-wide `defaults` UNDER a per-instance `config`: a defined config value
37
- * overrides the default, and the `tools` registry is merged across both layers
38
- * (a shared registry composes with per-instance additions) rather than replaced.
39
- * Returns the original `config` reference when there are no defaults, so the
40
- * no-provider path stays identity-stable. Mirrors React's `mergeBlokDefaults`.
41
- */
42
- export function mergeBlokDefaults(
43
- defaults: Partial<UseBlokConfig>,
44
- config: UseBlokConfig
45
- ): UseBlokConfig {
46
- if (Object.keys(defaults).length === 0) {
47
- return config;
48
- }
49
-
50
- const merged: Record<string, unknown> = { ...defaults };
51
-
52
- for (const key of Object.keys(config)) {
53
- const value = (config as Record<string, unknown>)[key];
54
-
55
- if (value !== undefined) {
56
- merged[key] = value;
57
- }
58
- }
59
-
60
- if (defaults.tools !== undefined || config.tools !== undefined) {
61
- merged.tools = { ...defaults.tools, ...config.tools };
62
- }
63
-
64
- return merged;
65
- }
@@ -1,21 +0,0 @@
1
- // src/vue/registry-map.ts
2
- import type { BlockPortalRegistry } from './block-portal-registry';
3
-
4
- /**
5
- * Associates each live Blok instance with its portal registry, keyed by the RAW
6
- * editor (mirrors the holder WeakMap). `useBlok` sets it at creation; `BlokContent`
7
- * reads it to mount the shared `BlockPortalHost`. WeakMap → no 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,36 +0,0 @@
1
- import type { BlokConfig, Blok, EditorWidth } from '@/types';
2
-
3
- /**
4
- * Configuration for the `useBlok` composable and the `<BlokEditor>` component.
5
- * Same as `BlokConfig` but without `holder` (managed by the adapter), plus a
6
- * reactive `width` prop.
7
- *
8
- * Reactive props (sync after mount without recreation):
9
- * - `readOnly` — calls `editor.readOnly.set(enabled, { hideControls })`; the
10
- * object form's `hideControls` is live too (toggles in place — see
11
- * `editor.readOnly.togglesInPlace`)
12
- * - `hideToolbar` — calls `editor.toolbar.setHidden(value)` (undefined → false)
13
- * - `toolbarPosition` — calls `editor.toolbar.setPosition(value)`
14
- * - `inlineToolbar` — calls `editor.tools.setInlineToolbar(value)`
15
- * (content-compared, so a fresh array with the same names is a no-op)
16
- * - `autofocus` — calls `editor.focus()` when changed to true
17
- * - `theme` — calls `editor.theme.set(value)`
18
- * - `width` — calls `editor.width.set(value)`
19
- * - `placeholder` — calls `editor.placeholder.set(value)`
20
- * - `data` — re-renders via `editor.render(value)` when content changes
21
- * (deep-equal–deduped and serialized; seeds the initial content at creation)
22
- *
23
- * The `onSave` config (inherited from `BlokConfig`) is the controlled output
24
- * half: it fires with the full serialized `OutputData` on every content change,
25
- * so `data` + `onSave` (or `v-model:data`) form a controlled component.
26
- */
27
- export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
28
- /** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */
29
- width?: EditorWidth;
30
- }
31
-
32
- /** Props for the `BlokContent` component. */
33
- export interface BlokContentProps {
34
- /** The Blok editor instance returned by `useBlok`. Pass null before it is ready. */
35
- editor: Blok | null;
36
- }
package/src/useBlocks.ts DELETED
@@ -1,182 +0,0 @@
1
- // src/vue/useBlocks.ts
2
- import {
3
- shallowRef,
4
- watch,
5
- toValue,
6
- toRaw,
7
- onScopeDispose,
8
- type MaybeRefOrGetter,
9
- } from 'vue';
10
-
11
- import type { Blok } from '@/types';
12
- import { changeTouchesSubtree, createBlocksApiForEditor, EMPTY_API } from '@bloklabs/core/adapters';
13
-
14
- import type { UseBlocksApi } from './blocks-snapshot';
15
-
16
- const BLOCK_CHANGED_EVENT = 'block changed';
17
-
18
- /** Options for {@link useBlocks}. */
19
- export interface UseBlocksOptions {
20
- /**
21
- * Re-render only for changes inside the subtree rooted at this block id (the
22
- * block itself or any descendant). Omit — or pass `null` — for the
23
- * document-wide default.
24
- *
25
- * This bounds REACTIVITY, not reads: the returned API still sees the whole
26
- * tree, so a scoped consumer can still `getById` anything. Reach for it in a
27
- * container block that renders only its own children — unscoped, such a block
28
- * invalidates on every keystroke anywhere in the document, and a page of N
29
- * containers turns one keystroke into N re-renders.
30
- *
31
- * Accepted as a value, ref or getter (like `editor`) and read at EMIT time, so
32
- * changing it takes effect immediately with no re-subscription.
33
- *
34
- * A change whose block cannot be placed in the tree (a removal that emits
35
- * after the block is gone) counts as in-scope: skipping it would leave a
36
- * container rendering a child that no longer exists.
37
- */
38
- within?: MaybeRefOrGetter<string | null>;
39
- }
40
-
41
- /**
42
- * Vue composable exposing an id/parentId-relative, reactive view of the block
43
- * tree. Reads refresh whenever the editor emits `block changed`; mutators route
44
- * through the editor-level `blocks` API (core's chokepoints), so undo/redo and
45
- * Yjs sync are inherited rather than re-implemented.
46
- *
47
- * The block-tree logic itself is framework-agnostic and lives in the shared
48
- * {@link createBlocksApiForEditor} core — the SAME implementation behind React's
49
- * `useBlocks`, so the two adapters expose the identical 28-method surface and
50
- * cannot drift. This composable supplies only Vue's reactivity wrapper:
51
- *
52
- * - A private `version` ref is bumped on every `block changed`. The shared API
53
- * is built with an `onRead` seam (`() => { void version.value }`) that every
54
- * read method calls, so reading `getChildren`/`getById`/… inside a `computed`
55
- * or template tracks `version` and re-runs on each structural mutation
56
- * (including programmatic nest/unnest, which core surfaces as a change).
57
- * - The bound API is rebuilt when the editor IDENTITY changes (held in a
58
- * `shallowRef`), and the returned facade is stable across that swap.
59
- *
60
- * The editor is accepted as a value, ref, or getter (so the `shallowRef` that
61
- * `useBlok` returns can be passed directly) and is `toRaw`-unwrapped before any
62
- * read or core handoff, never letting a Vue reactive proxy reach core (Risk R0).
63
- *
64
- * Pre-ready: while the editor is null the bound API is the shared
65
- * {@link EMPTY_API} — every mutator a no-op, reads empty/null — except
66
- * `transact`/`transactWithoutCapture`, which still run their callback.
67
- *
68
- * @param editor - the Blok instance (or ref/getter of it), or null pre-ready
69
- * @param options - reactivity options; see {@link UseBlocksOptions.within} to
70
- * scope invalidation to one block's subtree
71
- */
72
- export function useBlocks(
73
- editor: MaybeRefOrGetter<Blok | null>,
74
- options: UseBlocksOptions = {}
75
- ): UseBlocksApi {
76
- // Bumped on every `block changed`; the shared API's read methods touch it
77
- // (via the onRead seam below) so template/computed reads stay reactive.
78
- const version = shallowRef(0);
79
-
80
- const resolve = (): Blok | null => {
81
- const ed = toValue(editor);
82
-
83
- return ed === null ? null : toRaw(ed);
84
- };
85
-
86
- // The reactivity seam handed to the shared core: every read method calls it,
87
- // so a read inside a Vue effect tracks `version` and re-runs on mutation.
88
- const touch = (): void => {
89
- version.value;
90
- };
91
-
92
- // The API bound to the CURRENT editor. Rebuilt by the watch below when the
93
- // editor identity changes; EMPTY_API while null. A shallowRef so swapping the
94
- // bound API (on editor change) is itself a tracked reactive read.
95
- const bound = shallowRef<UseBlocksApi>(EMPTY_API);
96
-
97
- // Manage the `block changed` subscription, re-binding when the editor changes.
98
- // Held in one object (no `let` reassignment) — the same pattern useBlok uses.
99
- const sub: { editor: Blok | null; handler: ((payload?: unknown) => void) | null } = { editor: null, handler: null };
100
-
101
- const unsubscribe = (): void => {
102
- if (sub.editor !== null && sub.handler !== null) {
103
- sub.editor.off(BLOCK_CHANGED_EVENT, sub.handler);
104
- }
105
- sub.editor = null;
106
- sub.handler = null;
107
- };
108
-
109
- watch(
110
- () => resolve(),
111
- (ed) => {
112
- unsubscribe();
113
- // A changed editor identity is itself a reason to re-read.
114
- version.value += 1;
115
-
116
- if (ed === null) {
117
- bound.value = EMPTY_API;
118
-
119
- return;
120
- }
121
-
122
- const handler = (payload?: unknown): void => {
123
- // Resolved per emission, so a reactive `within` needs no re-subscription.
124
- const within = toValue(options.within) ?? null;
125
-
126
- if (within !== null && !changeTouchesSubtree(ed, payload, within)) {
127
- return;
128
- }
129
-
130
- version.value += 1;
131
- };
132
-
133
- ed.on(BLOCK_CHANGED_EVENT, handler);
134
- sub.editor = ed;
135
- sub.handler = handler;
136
- bound.value = createBlocksApiForEditor(ed, touch);
137
- },
138
- { immediate: true }
139
- );
140
-
141
- onScopeDispose(unsubscribe);
142
-
143
- // A stable facade whose methods delegate to the currently-bound API. Reading
144
- // `bound.value` tracks the editor-identity swap; the shared reads track
145
- // `version` via the onRead seam — so both kinds of change re-run a consumer's
146
- // computed/template. Every key is listed EXPLICITLY (no spread) so a forgotten
147
- // delegation is a COMPILE error against the UseBlocksApi return type rather
148
- // than a silently-missing method.
149
- return {
150
- getById: (id) => bound.value.getById(id),
151
- getChildren: (parentId) => bound.value.getChildren(parentId),
152
- insert: (spec) => bound.value.insert(spec),
153
- insertMany: (specs) => bound.value.insertMany(specs),
154
- insertTree: (spec) => bound.value.insertTree(spec),
155
- insertMarkdown: (markdown, options) => bound.value.insertMarkdown(markdown, options),
156
- exportMarkdown: () => bound.value.exportMarkdown(),
157
- move: (id, target) => bound.value.move(id, target),
158
- nest: (id, parentId) => bound.value.nest(id, parentId),
159
- unnest: (id) => bound.value.unnest(id),
160
- remove: (id) => bound.value.remove(id),
161
- update: (id, data, tunes) => bound.value.update(id, data, tunes),
162
- convert: (id, newType, dataOverrides, options) => bound.value.convert(id, newType, dataOverrides, options),
163
- transact: (fn) => bound.value.transact(fn),
164
- transactWithoutCapture: (fn, options) => bound.value.transactWithoutCapture(fn, options),
165
- getBlocksCount: () => bound.value.getBlocksCount(),
166
- getCurrentBlockIndex: () => bound.value.getCurrentBlockIndex(),
167
- getBlockByIndex: (index) => bound.value.getBlockByIndex(index),
168
- getBlockByElement: (element) => bound.value.getBlockByElement(element),
169
- getBlockData: (id) => bound.value.getBlockData(id),
170
- getBlockIndex: (id) => bound.value.getBlockIndex(id),
171
- composeBlockData: (toolName) => bound.value.composeBlockData(toolName),
172
- renderFromHTML: (html) => bound.value.renderFromHTML(html),
173
- insertOutputData: (blocks, options) => bound.value.insertOutputData(blocks, options),
174
- splitBlock: (currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex) =>
175
- bound.value.splitBlock(currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex),
176
- insertInsideParent: (parentId, insertIndex, childData) =>
177
- bound.value.insertInsideParent(parentId, insertIndex, childData),
178
- render: (data) => bound.value.render(data),
179
- clear: () => bound.value.clear(),
180
- isSyncingFromYjs: () => bound.value.isSyncingFromYjs(),
181
- };
182
- }