@jackuait/blok 0.22.0 → 0.23.1
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 +12 -1
- package/dist/blok.cjs +1 -1
- package/dist/blok.iife.js +5 -5
- package/dist/blok.mjs +2 -2
- package/dist/blok.umd.js +5 -5
- package/dist/chunks/{blok-S6c68iVJ.mjs → blok-BPeM6wKv.mjs} +33 -1
- package/dist/chunks/{blok-Blnd71Ww.cjs → blok-Ci1zCOUg.cjs} +4 -4
- package/dist/chunks/{constants-BIqDMhRj.cjs → constants-Bkncy0y5.cjs} +1 -1
- package/dist/chunks/{constants-BvxJIgSQ.mjs → constants-DR3sya6U.mjs} +1 -1
- package/dist/chunks/{tools-B89FUeNz.mjs → tools-B72rczVw.mjs} +4 -1
- package/dist/chunks/{tools-DTjC0C5o.cjs → tools-DlOZpcVU.cjs} +1 -1
- package/dist/full.cjs +1 -1
- package/dist/full.mjs +3 -3
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +69 -42
- package/dist/tools.cjs +1 -1
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/blok.ts +43 -0
- package/src/components/block/index.ts +13 -0
- package/src/components/modules/blockManager/blockManager.ts +19 -0
- package/src/components/tools/base.ts +9 -0
- package/src/react/BlokEditor.tsx +88 -0
- package/src/react/config-keys.ts +51 -0
- package/src/react/index.ts +2 -0
- package/src/react/types.ts +14 -3
- package/src/react/useBlok.ts +27 -0
- package/src/tools/paragraph/index.ts +20 -0
- package/types/api/index.d.ts +1 -0
- package/types/api/placeholder.d.ts +18 -0
- package/types/index.d.ts +5 -0
- package/types/react.d.ts +40 -3
package/dist/full.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-Ci1zCOUg.cjs`),t=require(`./chunks/constants-Bkncy0y5.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-DlOZpcVU.cjs`);var i={paragraph:{class:r.k,inlineToolbar:!0,config:{preserveBlank:!0}},header:{class:r.O,inlineToolbar:!0},list:{class:r.D,inlineToolbar:!0}},a=n.t(n.t({},i),{},{bold:{class:r.l},italic:{class:r.c},marker:{class:r.o},underline:{class:r.a},strikethrough:{class:r.i},link:{class:r.s}});exports.Blok=e.t,exports.Bold=r.l,exports.DATA_ATTR=t.ja,exports.Header=r.O,exports.Italic=r.c,exports.Link=r.s,exports.List=r.D,exports.Marker=r.o,exports.Paragraph=r.k,exports.Strikethrough=r.i,exports.Toggle=r.T,exports.Underline=r.a,exports.allTools=a,exports.defaultBlockTools=r.t,exports.defaultInlineTools=r.n,exports.defaultTools=i,exports.version=e.n;
|
package/dist/full.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as e, t } from "./chunks/blok-
|
|
2
|
-
import { ja as n } from "./chunks/constants-
|
|
1
|
+
import { n as e, t } from "./chunks/blok-BPeM6wKv.mjs";
|
|
2
|
+
import { ja as n } from "./chunks/constants-DR3sya6U.mjs";
|
|
3
3
|
import { t as r } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
4
|
-
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-
|
|
4
|
+
import { D as i, O as a, T as o, a as s, c, i as l, k as u, l as d, n as f, o as p, s as m, t as h } from "./chunks/tools-B72rczVw.mjs";
|
|
5
5
|
//#region src/full.ts
|
|
6
6
|
var g = {
|
|
7
7
|
paragraph: {
|
package/dist/react.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-Ci1zCOUg.cjs`),t=require(`./chunks/objectSpread2-iNkE6aJh.cjs`);let n=require(`react`),r=require(`react/jsx-runtime`);var i=new WeakMap;function a(e,t){i.set(e,t)}function o(e){return i.get(e)}function s(e){i.delete(e)}function c(r,i){let[o,c]=(0,n.useState)(null),u=(0,n.useRef)(r);u.current=r;let d=(0,n.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,depsToken:null}),f=(0,n.useMemo)(()=>({}),i==null?[]:i);(0,n.useEffect)(()=>{if(typeof window>`u`)return;let n=d.current;if(n.destroyTimeout!==null&&(clearTimeout(n.destroyTimeout),n.destroyTimeout=null),n.editor!==null&&!n.isDestroyed&&n.depsToken===f)return c(n.editor),()=>{l(n,c)};if(n.editor!==null&&!n.isDestroyed){s(n.editor);try{n.editor.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,c(null)}let r=document.createElement(`div`);n.holder=r,n.isDestroyed=!1,n.depsToken=f;let i=u.current,o=new e.t(t.t(t.t({},i),{},{holder:r,onReady:()=>{var e,t;(e=(t=u.current).onReady)==null||e.call(t)},onChange:(...e)=>{var t,n;(t=(n=u.current).onChange)==null||t.call(n,...e)}}));return n.editor=o,a(o,r),o.isReady.then(()=>{n.editor===o&&!n.isDestroyed&&c(o)}).catch(()=>{if(n.editor===o&&!n.isDestroyed){s(o);try{o.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,c(null)}}),()=>{l(n,c)}},[f]);let{readOnly:p}=r;(0,n.useEffect)(()=>{o!==null&&o.readOnly.set(p==null?!1:p)},[o,p]);let{autofocus:m}=r;(0,n.useEffect)(()=>{o===null||!m||o.focus()},[o,m]);let{theme:h}=r;(0,n.useEffect)(()=>{o===null||h===void 0||o.theme.set(h)},[o,h]);let{width:g}=r;(0,n.useEffect)(()=>{o===null||g===void 0||o.width.set(g)},[o,g]);let{placeholder:_}=r;return(0,n.useEffect)(()=>{o===null||_===void 0||o.placeholder.set(_)},[o,_]),o}function l(e,t){e.destroyTimeout=setTimeout(()=>{if(e.editor!==null){s(e.editor);try{e.editor.destroy()}catch(e){}e.editor=null,e.holder=null,e.isDestroyed=!0,e.destroyTimeout=null,t(null)}},0)}var u=[`editor`],d=(0,n.forwardRef)(function(i,a){let{editor:s}=i,c=e.i(i,u),l=(0,n.useRef)(null);return(0,n.useEffect)(()=>{if(s===null||l.current===null)return;let e=o(s);if(e!==void 0)return l.current.appendChild(e),()=>{e.remove()}},[s]),(0,r.jsx)(`div`,t.t({ref:e=>{if(l.current=e,typeof a==`function`)a(e);else if(a!=null){let t=a;t.current=e}}},c))}),f=`autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.linkPaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width`.split(`.`),p=[`deps`,`onReady`],m=new Set(f),h=(0,n.forwardRef)(function(i,a){let{deps:o,onReady:s}=i,l=e.i(i,p),u={},f={};for(let e of Object.keys(l)){let t=l[e];m.has(e)?u[e]=t:f[e]=t}let h=c(u,o);(0,n.useImperativeHandle)(a,()=>h,[h]);let g=(0,n.useRef)(s);return g.current=s,(0,n.useEffect)(()=>{if(h!==null){var e;(e=g.current)==null||e.call(g,h)}},[h]),(0,r.jsx)(d,t.t({editor:h},f))});exports.BlokContent=d,exports.BlokEditor=h,exports.useBlok=c;
|
package/dist/react.mjs
CHANGED
|
@@ -1,83 +1,95 @@
|
|
|
1
|
-
import { i as e, t } from "./chunks/blok-
|
|
1
|
+
import { i as e, t } from "./chunks/blok-BPeM6wKv.mjs";
|
|
2
2
|
import { t as n } from "./chunks/objectSpread2-jlBcdYeb.mjs";
|
|
3
|
-
import { forwardRef as r, useEffect as i,
|
|
4
|
-
import { jsx as
|
|
3
|
+
import { forwardRef as r, useEffect as i, useImperativeHandle as a, useMemo as o, useRef as s, useState as c } from "react";
|
|
4
|
+
import { jsx as l } from "react/jsx-runtime";
|
|
5
5
|
//#region src/react/holder-map.ts
|
|
6
|
-
var
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
function d(e) {
|
|
11
|
-
return l.get(e);
|
|
6
|
+
var u = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function d(e, t) {
|
|
8
|
+
u.set(e, t);
|
|
12
9
|
}
|
|
13
10
|
function f(e) {
|
|
14
|
-
|
|
11
|
+
return u.get(e);
|
|
12
|
+
}
|
|
13
|
+
function p(e) {
|
|
14
|
+
u.delete(e);
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/react/useBlok.ts
|
|
18
|
-
function
|
|
19
|
-
let [
|
|
20
|
-
|
|
21
|
-
let
|
|
18
|
+
function m(e, r) {
|
|
19
|
+
let [a, l] = c(null), u = s(e);
|
|
20
|
+
u.current = e;
|
|
21
|
+
let f = s({
|
|
22
22
|
editor: null,
|
|
23
23
|
holder: null,
|
|
24
24
|
destroyTimeout: null,
|
|
25
25
|
isDestroyed: !1,
|
|
26
26
|
depsToken: null
|
|
27
|
-
}),
|
|
27
|
+
}), m = o(() => ({}), r == null ? [] : r);
|
|
28
28
|
i(() => {
|
|
29
29
|
if (typeof window > "u") return;
|
|
30
|
-
let e =
|
|
31
|
-
if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken ===
|
|
32
|
-
|
|
30
|
+
let e = f.current;
|
|
31
|
+
if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken === m) return l(e.editor), () => {
|
|
32
|
+
h(e, l);
|
|
33
33
|
};
|
|
34
34
|
if (e.editor !== null && !e.isDestroyed) {
|
|
35
|
-
|
|
35
|
+
p(e.editor);
|
|
36
36
|
try {
|
|
37
37
|
e.editor.destroy();
|
|
38
38
|
} catch (e) {}
|
|
39
39
|
e.editor = null, e.holder = null, e.isDestroyed = !0, l(null);
|
|
40
40
|
}
|
|
41
41
|
let r = document.createElement("div");
|
|
42
|
-
e.holder = r, e.isDestroyed = !1, e.depsToken =
|
|
43
|
-
let i =
|
|
42
|
+
e.holder = r, e.isDestroyed = !1, e.depsToken = m;
|
|
43
|
+
let i = u.current, a = new t(n(n({}, i), {}, {
|
|
44
44
|
holder: r,
|
|
45
45
|
onReady: () => {
|
|
46
46
|
var e, t;
|
|
47
|
-
(e = (t =
|
|
47
|
+
(e = (t = u.current).onReady) == null || e.call(t);
|
|
48
48
|
},
|
|
49
49
|
onChange: (...e) => {
|
|
50
50
|
var t, n;
|
|
51
|
-
(t = (n =
|
|
51
|
+
(t = (n = u.current).onChange) == null || t.call(n, ...e);
|
|
52
52
|
}
|
|
53
53
|
}));
|
|
54
|
-
return e.editor = a,
|
|
54
|
+
return e.editor = a, d(a, r), a.isReady.then(() => {
|
|
55
55
|
e.editor === a && !e.isDestroyed && l(a);
|
|
56
56
|
}).catch(() => {
|
|
57
57
|
if (e.editor === a && !e.isDestroyed) {
|
|
58
|
-
|
|
58
|
+
p(a);
|
|
59
59
|
try {
|
|
60
60
|
a.destroy();
|
|
61
61
|
} catch (e) {}
|
|
62
62
|
e.editor = null, e.holder = null, e.isDestroyed = !0, l(null);
|
|
63
63
|
}
|
|
64
64
|
}), () => {
|
|
65
|
-
|
|
65
|
+
h(e, l);
|
|
66
66
|
};
|
|
67
|
-
}, [
|
|
67
|
+
}, [m]);
|
|
68
68
|
let { readOnly: g } = e;
|
|
69
69
|
i(() => {
|
|
70
|
-
|
|
71
|
-
}, [
|
|
70
|
+
a !== null && a.readOnly.set(g == null ? !1 : g);
|
|
71
|
+
}, [a, g]);
|
|
72
72
|
let { autofocus: _ } = e;
|
|
73
|
+
i(() => {
|
|
74
|
+
a === null || !_ || a.focus();
|
|
75
|
+
}, [a, _]);
|
|
76
|
+
let { theme: v } = e;
|
|
77
|
+
i(() => {
|
|
78
|
+
a === null || v === void 0 || a.theme.set(v);
|
|
79
|
+
}, [a, v]);
|
|
80
|
+
let { width: y } = e;
|
|
81
|
+
i(() => {
|
|
82
|
+
a === null || y === void 0 || a.width.set(y);
|
|
83
|
+
}, [a, y]);
|
|
84
|
+
let { placeholder: b } = e;
|
|
73
85
|
return i(() => {
|
|
74
|
-
|
|
75
|
-
}, [
|
|
86
|
+
a === null || b === void 0 || a.placeholder.set(b);
|
|
87
|
+
}, [a, b]), a;
|
|
76
88
|
}
|
|
77
|
-
function
|
|
89
|
+
function h(e, t) {
|
|
78
90
|
e.destroyTimeout = setTimeout(() => {
|
|
79
91
|
if (e.editor !== null) {
|
|
80
|
-
|
|
92
|
+
p(e.editor);
|
|
81
93
|
try {
|
|
82
94
|
e.editor.destroy();
|
|
83
95
|
} catch (e) {}
|
|
@@ -87,21 +99,36 @@ function m(e, t) {
|
|
|
87
99
|
}
|
|
88
100
|
//#endregion
|
|
89
101
|
//#region src/react/BlokContent.tsx
|
|
90
|
-
var
|
|
91
|
-
let { editor: a } = t,
|
|
102
|
+
var g = ["editor"], _ = r(function(t, r) {
|
|
103
|
+
let { editor: a } = t, o = e(t, g), c = s(null);
|
|
92
104
|
return i(() => {
|
|
93
|
-
if (a === null ||
|
|
94
|
-
let e =
|
|
95
|
-
if (e !== void 0) return
|
|
105
|
+
if (a === null || c.current === null) return;
|
|
106
|
+
let e = f(a);
|
|
107
|
+
if (e !== void 0) return c.current.appendChild(e), () => {
|
|
96
108
|
e.remove();
|
|
97
109
|
};
|
|
98
|
-
}, [a]), /* @__PURE__ */
|
|
99
|
-
if (
|
|
110
|
+
}, [a]), /* @__PURE__ */ l("div", n({ ref: (e) => {
|
|
111
|
+
if (c.current = e, typeof r == "function") r(e);
|
|
100
112
|
else if (r != null) {
|
|
101
113
|
let t = r;
|
|
102
114
|
t.current = e;
|
|
103
115
|
}
|
|
104
|
-
} },
|
|
116
|
+
} }, o));
|
|
117
|
+
}), v = /* @__PURE__ */ "autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.linkPaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width".split("."), y = ["deps", "onReady"], b = new Set(v), x = r(function(t, r) {
|
|
118
|
+
let { deps: o, onReady: c } = t, u = e(t, y), d = {}, f = {};
|
|
119
|
+
for (let e of Object.keys(u)) {
|
|
120
|
+
let t = u[e];
|
|
121
|
+
b.has(e) ? d[e] = t : f[e] = t;
|
|
122
|
+
}
|
|
123
|
+
let p = m(d, o);
|
|
124
|
+
a(r, () => p, [p]);
|
|
125
|
+
let h = s(c);
|
|
126
|
+
return h.current = c, i(() => {
|
|
127
|
+
if (p !== null) {
|
|
128
|
+
var e;
|
|
129
|
+
(e = h.current) == null || e.call(h, p);
|
|
130
|
+
}
|
|
131
|
+
}, [p]), /* @__PURE__ */ l(_, n({ editor: p }, f));
|
|
105
132
|
});
|
|
106
133
|
//#endregion
|
|
107
|
-
export {
|
|
134
|
+
export { _ as BlokContent, x as BlokEditor, m as useBlok };
|
package/dist/tools.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-Bkncy0y5.cjs`),t=require(`./chunks/tools-DlOZpcVU.cjs`);exports.Audio=t.g,exports.Bold=t.l,exports.Bookmark=t.u,exports.Callout=t.w,exports.Code=t.y,exports.Column=t.p,exports.ColumnList=t.m,exports.Columns=t.f,exports.Convert=e.m,exports.Database=t.C,exports.DatabaseRow=t.S,exports.Divider=t.x,exports.Embed=t.d,exports.File=t._,exports.Header=t.O,exports.Image=t.v,exports.InlineCode=t.r,exports.Italic=t.c,exports.Link=t.s,exports.List=t.D,exports.Marker=t.o,exports.Paragraph=t.k,exports.Quote=t.b,exports.Strikethrough=t.i,exports.Table=t.E,exports.Toggle=t.T,exports.Underline=t.a,exports.Video=t.h,exports.defaultBlockTools=t.t,exports.defaultInlineTools=t.n;
|
package/dist/tools.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as e } from "./chunks/constants-
|
|
2
|
-
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-
|
|
1
|
+
import { m as e } from "./chunks/constants-DR3sya6U.mjs";
|
|
2
|
+
import { C as t, D as n, E as r, O as i, S as a, T as o, _ as s, a as c, b as l, c as u, d, f, g as p, h as m, i as h, k as g, l as _, m as v, n as y, o as b, p as x, r as S, s as C, t as w, u as T, v as E, w as D, x as O, y as k } from "./chunks/tools-B72rczVw.mjs";
|
|
3
3
|
export { p as Audio, _ as Bold, T as Bookmark, D as Callout, k as Code, x as Column, v as ColumnList, f as Columns, e as Convert, t as Database, a as DatabaseRow, O as Divider, d as Embed, s as File, i as Header, E as Image, S as InlineCode, u as Italic, C as Link, n as List, b as Marker, g as Paragraph, l as Quote, h as Strikethrough, r as Table, o as Toggle, c as Underline, m as Video, w as defaultBlockTools, y as defaultInlineTools };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackuait/blok",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
|
|
5
5
|
"module": "dist/blok.mjs",
|
|
6
6
|
"main": "./dist/blok.cjs",
|
package/src/blok.ts
CHANGED
|
@@ -175,6 +175,39 @@ class Blok {
|
|
|
175
175
|
toggle: (): void => applyWidthMode(readWidthMode() === 'full' ? 'narrow' : 'full'),
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Placeholder API — exposed immediately (mirrors theme/width) so host apps
|
|
180
|
+
* can call instance.placeholder.set(...) before isReady without a silent
|
|
181
|
+
* no-op. The value is buffered and replayed once BlockManager is ready.
|
|
182
|
+
*/
|
|
183
|
+
const placeholderBuffer = { pending: null as string | false | null };
|
|
184
|
+
|
|
185
|
+
const getBlockManager = (): BlokModules['BlockManager'] | undefined =>
|
|
186
|
+
(blok.moduleInstances as Partial<BlokModules>).BlockManager;
|
|
187
|
+
|
|
188
|
+
const applyPlaceholder = (value: string | false): void => {
|
|
189
|
+
placeholderBuffer.pending = value;
|
|
190
|
+
|
|
191
|
+
const bm = getBlockManager();
|
|
192
|
+
|
|
193
|
+
if (bm !== undefined) {
|
|
194
|
+
bm.setPlaceholder(value);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
(this as Record<string, unknown>).placeholder = {
|
|
199
|
+
get: (): string | false => {
|
|
200
|
+
if (placeholderBuffer.pending !== null) {
|
|
201
|
+
return placeholderBuffer.pending;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const cfg = this.initialConfiguration;
|
|
205
|
+
|
|
206
|
+
return isObject(cfg) ? ((cfg as BlokConfig).placeholder ?? false) : false;
|
|
207
|
+
},
|
|
208
|
+
set: (value: string | false): void => applyPlaceholder(value),
|
|
209
|
+
};
|
|
210
|
+
|
|
178
211
|
/**
|
|
179
212
|
* We need to export isReady promise in the constructor
|
|
180
213
|
* as it can be used before other API methods are exported
|
|
@@ -247,6 +280,16 @@ class Blok {
|
|
|
247
280
|
widthBuffer.pendingMode = null;
|
|
248
281
|
}
|
|
249
282
|
|
|
283
|
+
// Apply any placeholder buffered before isReady resolved.
|
|
284
|
+
if (placeholderBuffer.pending !== null) {
|
|
285
|
+
const bm = (blok.moduleInstances as Partial<BlokModules>).BlockManager;
|
|
286
|
+
|
|
287
|
+
if (bm !== undefined) {
|
|
288
|
+
bm.setPlaceholder(placeholderBuffer.pending);
|
|
289
|
+
}
|
|
290
|
+
placeholderBuffer.pending = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
250
293
|
// Scroll to the block referenced by the URL hash, if present.
|
|
251
294
|
// isReady resolves only after all blocks are in the DOM (requestIdleCallback fence in Renderer),
|
|
252
295
|
// so no extra polling is needed even on slow connections.
|
|
@@ -610,6 +610,19 @@ export class Block extends EventsDispatcher<BlockEvents> {
|
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
/**
|
|
614
|
+
* Update the block's placeholder in place (reactive editor.placeholder API).
|
|
615
|
+
* Delegates to the tool instance if it implements `setPlaceholder`. No-op otherwise.
|
|
616
|
+
* @param value - new placeholder text, or false to clear it
|
|
617
|
+
*/
|
|
618
|
+
public setPlaceholder(value: string | false): void {
|
|
619
|
+
const tool = this.toolInstance as unknown as { setPlaceholder?: (v: string | false) => void };
|
|
620
|
+
|
|
621
|
+
if (typeof tool.setPlaceholder === 'function') {
|
|
622
|
+
tool.setPlaceholder(value);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
613
626
|
/**
|
|
614
627
|
* Tool could specify several entries to be displayed at the Toolbox (for example, "Heading 1", "Heading 2", "Heading 3")
|
|
615
628
|
* This method returns the entry that is related to the Block (depended on the Block data)
|
|
@@ -153,6 +153,25 @@ export class BlockManager extends Module {
|
|
|
153
153
|
return this.repository.blocks;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Apply a new editor-level placeholder to existing blocks and future ones.
|
|
158
|
+
* Backs the reactive `editor.placeholder` API: mutates the shared config,
|
|
159
|
+
* updates the default tool adapter (so blocks created afterward use it), and
|
|
160
|
+
* sweeps existing blocks for an in-place update.
|
|
161
|
+
* @param value - new placeholder text, or false to clear it
|
|
162
|
+
*/
|
|
163
|
+
public setPlaceholder(value: string | false): void {
|
|
164
|
+
this.config.placeholder = value;
|
|
165
|
+
|
|
166
|
+
const defaultToolName = this.config.defaultBlock ?? 'paragraph';
|
|
167
|
+
|
|
168
|
+
this.Blok.Tools.blockTools.get(defaultToolName)?.setDefaultPlaceholder(value);
|
|
169
|
+
|
|
170
|
+
for (const block of this.blocks) {
|
|
171
|
+
block.setPlaceholder(value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
/**
|
|
157
176
|
* Check if each Block is empty
|
|
158
177
|
* @returns {boolean}
|
|
@@ -191,6 +191,15 @@ export abstract class BaseToolAdapter<Type extends ToolType = ToolType, ToolClas
|
|
|
191
191
|
this.defaultPlaceholder = defaultPlaceholder;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Update the editor-level default placeholder. Used by the reactive
|
|
196
|
+
* `editor.placeholder` API so blocks created AFTER the change use the new value.
|
|
197
|
+
* @param value - new default placeholder, or false to disable it
|
|
198
|
+
*/
|
|
199
|
+
public setDefaultPlaceholder(value: string | false): void {
|
|
200
|
+
this.defaultPlaceholder = value;
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
/**
|
|
195
204
|
* Returns Tool user configuration.
|
|
196
205
|
* Extracts tool-specific options from flat config and merges with nested config.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
type DependencyList,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { useBlok } from './useBlok';
|
|
10
|
+
import { BlokContent } from './BlokContent';
|
|
11
|
+
import { USE_BLOK_CONFIG_KEYS } from './config-keys';
|
|
12
|
+
import type { Blok } from '@/types';
|
|
13
|
+
import type { UseBlokConfig } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for the all-in-one BlokEditor component.
|
|
17
|
+
* Accepts every useBlok config prop (except `onReady`, re-typed below to receive
|
|
18
|
+
* the ready instance) AND every standard HTML div attribute, which is forwarded
|
|
19
|
+
* to the editor container element (mirrors `BlokContent`). `style` and `onChange`
|
|
20
|
+
* keep their editor-config meaning (they collide with div attributes of the same
|
|
21
|
+
* name), so the container is styled via `className` rather than an inline `style`.
|
|
22
|
+
*
|
|
23
|
+
* `data` is uncontrolled (seed-only): it sets the INITIAL content. After mount the
|
|
24
|
+
* editor owns the document — passing a new `data` reference does NOT reload it.
|
|
25
|
+
* Read content via `onChange` or the ref (`ref.current.save()`); replace it
|
|
26
|
+
* imperatively via `ref.current.render(newData)`.
|
|
27
|
+
*/
|
|
28
|
+
export interface BlokEditorProps
|
|
29
|
+
extends Omit<UseBlokConfig, 'onReady'>,
|
|
30
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'onChange'> {
|
|
31
|
+
/** When any value changes, the editor is destroyed and recreated. */
|
|
32
|
+
deps?: DependencyList;
|
|
33
|
+
/** Test id forwarded to the editor container element (via data-testid). */
|
|
34
|
+
'data-testid'?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Called once the editor is ready, with the live Blok instance. Fires after the
|
|
37
|
+
* forwarded ref is committed, so `ref.current` is also populated at this point.
|
|
38
|
+
*/
|
|
39
|
+
onReady?: (editor: Blok) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Fast membership test for partitioning props into editor config vs. div attributes. */
|
|
43
|
+
const CONFIG_KEY_SET = new Set<string>(USE_BLOK_CONFIG_KEYS);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The recommended way to embed Blok in React. Internally wires `useBlok` and
|
|
47
|
+
* `BlokContent`, and forwards a ref to the live `Blok` instance.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* const ref = useRef<Blok | null>(null);
|
|
52
|
+
* <BlokEditor ref={ref} id="editor" tools={tools} data={data} theme={theme} onReady={(e) => e.focus()} />;
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const BlokEditor = forwardRef<Blok | null, BlokEditorProps>(
|
|
56
|
+
function BlokEditor({ deps, onReady, ...rest }, ref) {
|
|
57
|
+
const config: Record<string, unknown> = {};
|
|
58
|
+
const divProps: Record<string, unknown> = {};
|
|
59
|
+
|
|
60
|
+
for (const key of Object.keys(rest)) {
|
|
61
|
+
const value = (rest as Record<string, unknown>)[key];
|
|
62
|
+
|
|
63
|
+
if (CONFIG_KEY_SET.has(key)) {
|
|
64
|
+
config[key] = value;
|
|
65
|
+
} else {
|
|
66
|
+
divProps[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const editor = useBlok(config as UseBlokConfig, deps);
|
|
71
|
+
|
|
72
|
+
useImperativeHandle<Blok | null, Blok | null>(ref, () => editor, [editor]);
|
|
73
|
+
|
|
74
|
+
// Own onReady here instead of forwarding it to the core config: the core
|
|
75
|
+
// onReady fires before useImperativeHandle commits the ref. This passive
|
|
76
|
+
// effect runs AFTER the commit, so both the instance argument and ref.current
|
|
77
|
+
// are reliably populated.
|
|
78
|
+
const onReadyRef = useRef(onReady);
|
|
79
|
+
onReadyRef.current = onReady;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (editor !== null) {
|
|
82
|
+
onReadyRef.current?.(editor);
|
|
83
|
+
}
|
|
84
|
+
}, [editor]);
|
|
85
|
+
|
|
86
|
+
return <BlokContent editor={editor} {...(divProps as HTMLAttributes<HTMLDivElement>)} />;
|
|
87
|
+
}
|
|
88
|
+
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { UseBlokConfig } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keys that belong to the editor config (consumed by `useBlok`). Any prop on
|
|
5
|
+
* `BlokEditor` whose key is NOT in this set is treated as a container div
|
|
6
|
+
* attribute and spread onto the underlying element (mirrors `BlokContent`).
|
|
7
|
+
*
|
|
8
|
+
* Keep in sync with `UseBlokConfig`. The two compile-time guards below fail the
|
|
9
|
+
* `yarn lint:types` typecheck if this list drifts.
|
|
10
|
+
*/
|
|
11
|
+
export const USE_BLOK_CONFIG_KEYS = [
|
|
12
|
+
'autofocus',
|
|
13
|
+
'defaultBlock',
|
|
14
|
+
'dataModel',
|
|
15
|
+
'placeholder',
|
|
16
|
+
'sanitizer',
|
|
17
|
+
'hideToolbar',
|
|
18
|
+
'maxHistoryLength',
|
|
19
|
+
'historyDebounceTime',
|
|
20
|
+
'newGroupDelay',
|
|
21
|
+
'globalUndoRedo',
|
|
22
|
+
'tools',
|
|
23
|
+
'data',
|
|
24
|
+
'minHeight',
|
|
25
|
+
'logLevel',
|
|
26
|
+
'readOnly',
|
|
27
|
+
'i18n',
|
|
28
|
+
'linkPaste',
|
|
29
|
+
'onReady',
|
|
30
|
+
'onChange',
|
|
31
|
+
'inlineToolbar',
|
|
32
|
+
'tunes',
|
|
33
|
+
'style',
|
|
34
|
+
'theme',
|
|
35
|
+
'onThemeChange',
|
|
36
|
+
'scrollToBlock',
|
|
37
|
+
'user',
|
|
38
|
+
'resolveUser',
|
|
39
|
+
'notifierPosition',
|
|
40
|
+
'notifier',
|
|
41
|
+
'width',
|
|
42
|
+
] as const satisfies readonly (keyof UseBlokConfig)[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compile error if a `UseBlokConfig` key is missing from the list above.
|
|
46
|
+
* (`satisfies` only proves each entry is a valid key; this proves coverage.)
|
|
47
|
+
*/
|
|
48
|
+
type _MissingConfigKey = Exclude<keyof UseBlokConfig, (typeof USE_BLOK_CONFIG_KEYS)[number]>;
|
|
49
|
+
const _exhaustive: _MissingConfigKey extends never ? true : never = true;
|
|
50
|
+
|
|
51
|
+
void _exhaustive;
|
package/src/react/index.ts
CHANGED
package/src/react/types.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import type { BlokConfig, Blok } from '@/types';
|
|
1
|
+
import type { BlokConfig, Blok, EditorWidth } from '@/types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Configuration for useBlok hook.
|
|
5
|
-
* Same as BlokConfig but without `holder`
|
|
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)`
|
|
10
|
+
* - `autofocus` — calls `editor.focus()` when changed to true
|
|
11
|
+
* - `theme` — calls `editor.theme.set(value)`
|
|
12
|
+
* - `width` — calls `editor.width.set(value)`
|
|
13
|
+
* - `placeholder` — calls `editor.placeholder.set(value)`
|
|
6
14
|
*/
|
|
7
|
-
export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
|
|
15
|
+
export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
|
|
16
|
+
/** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */
|
|
17
|
+
width?: EditorWidth;
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
/**
|
|
10
21
|
* Props for the BlokContent component.
|
package/src/react/useBlok.ts
CHANGED
|
@@ -146,6 +146,33 @@ export function useBlok(config: UseBlokConfig, deps?: DependencyList): Blok | nu
|
|
|
146
146
|
editor.focus();
|
|
147
147
|
}, [editor, autofocus]);
|
|
148
148
|
|
|
149
|
+
// Reactive: theme
|
|
150
|
+
const { theme } = config;
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (editor === null || theme === undefined) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
editor.theme.set(theme);
|
|
156
|
+
}, [editor, theme]);
|
|
157
|
+
|
|
158
|
+
// Reactive: width
|
|
159
|
+
const { width } = config;
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (editor === null || width === undefined) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
editor.width.set(width);
|
|
165
|
+
}, [editor, width]);
|
|
166
|
+
|
|
167
|
+
// Reactive: placeholder
|
|
168
|
+
const { placeholder } = config;
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (editor === null || placeholder === undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
editor.placeholder.set(placeholder);
|
|
174
|
+
}, [editor, placeholder]);
|
|
175
|
+
|
|
149
176
|
return editor;
|
|
150
177
|
}
|
|
151
178
|
|
|
@@ -321,6 +321,26 @@ export class Paragraph implements BlockTool {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Update the block's placeholder text in place (reactive editor.placeholder API).
|
|
326
|
+
* Updates internal state always; writes the live attribute only when editable
|
|
327
|
+
* and rendered. A read-only block picks up the new text on the next edit toggle.
|
|
328
|
+
*
|
|
329
|
+
* @param value - new placeholder text, or false to clear it
|
|
330
|
+
*/
|
|
331
|
+
public setPlaceholder(value: string | false): void {
|
|
332
|
+
this._placeholder = value === false ? '' : value;
|
|
333
|
+
|
|
334
|
+
if (!this._element || this.readOnly) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this._element.setAttribute(
|
|
339
|
+
'data-blok-placeholder-active',
|
|
340
|
+
value === false ? '' : this.api.i18n.t(this._placeholder)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
324
344
|
/**
|
|
325
345
|
* Method that specified how to merge two Text blocks.
|
|
326
346
|
* Called by Editor by backspace at the beginning of the Block
|
package/types/api/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes Blok's placeholder API for controlling the editor-level placeholder
|
|
3
|
+
* (the hint shown on the empty default block) at runtime.
|
|
4
|
+
*/
|
|
5
|
+
export interface Placeholder {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current editor placeholder.
|
|
8
|
+
* @returns the placeholder text, or `false` when disabled.
|
|
9
|
+
*/
|
|
10
|
+
get(): string | false;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sets the editor placeholder. Updates existing blocks in place and applies to
|
|
14
|
+
* blocks created afterward. Pass `false` to clear it.
|
|
15
|
+
* @param value - new placeholder text, or false to disable it
|
|
16
|
+
*/
|
|
17
|
+
set(value: string | false): void;
|
|
18
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
ThemeMode,
|
|
34
34
|
ResolvedTheme,
|
|
35
35
|
Width,
|
|
36
|
+
Placeholder,
|
|
36
37
|
} from './api';
|
|
37
38
|
|
|
38
39
|
import { OutputData } from './data-formats';
|
|
@@ -129,6 +130,7 @@ export {
|
|
|
129
130
|
ResolvedTheme,
|
|
130
131
|
Width,
|
|
131
132
|
EditorWidth,
|
|
133
|
+
Placeholder,
|
|
132
134
|
} from './api';
|
|
133
135
|
export {
|
|
134
136
|
BlockMutationType,
|
|
@@ -228,6 +230,8 @@ export interface PendingBlok {
|
|
|
228
230
|
theme: Theme;
|
|
229
231
|
/** Width API, exposed immediately after construction. */
|
|
230
232
|
width: Width;
|
|
233
|
+
/** Placeholder API, exposed immediately after construction. */
|
|
234
|
+
placeholder: Placeholder;
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/**
|
|
@@ -249,6 +253,7 @@ export class Blok {
|
|
|
249
253
|
public readOnly: ReadOnly;
|
|
250
254
|
public theme: Theme;
|
|
251
255
|
public width: Width;
|
|
256
|
+
public placeholder: Placeholder;
|
|
252
257
|
constructor(configuration?: BlokConfig|string);
|
|
253
258
|
|
|
254
259
|
/**
|