@bloklabs/vue 1.16.1 → 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 +20 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +146 -104
- package/package.json +3 -4
- package/types/index.d.ts +57 -3
- package/src/BlockPortalHost.ts +0 -46
- package/src/BlokContent.ts +0 -103
- package/src/BlokEditor.ts +0 -326
- package/src/block-portal-registry.ts +0 -93
- package/src/blocks-snapshot.ts +0 -17
- package/src/blok-instance.ts +0 -40
- package/src/config-keys.ts +0 -82
- package/src/content-baseline-map.ts +0 -38
- package/src/createVueBlock.ts +0 -740
- package/src/holder-map.ts +0 -19
- package/src/index.ts +0 -30
- package/src/provide-blok.ts +0 -65
- package/src/registry-map.ts +0 -21
- package/src/types.ts +0 -36
- package/src/useBlocks.ts +0 -182
- package/src/useBlok.ts +0 -686
- package/src/useBlokReady.ts +0 -70
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';
|
package/src/provide-blok.ts
DELETED
|
@@ -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
|
-
}
|
package/src/registry-map.ts
DELETED
|
@@ -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
|
-
}
|