@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/dist/full.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-Blnd71Ww.cjs`),t=require(`./chunks/constants-BIqDMhRj.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-DTjC0C5o.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;
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-S6c68iVJ.mjs";
2
- import { ja as n } from "./chunks/constants-BvxJIgSQ.mjs";
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-B89FUeNz.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-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-Blnd71Ww.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;return(0,n.useEffect)(()=>{o===null||!m||o.focus()},[o,m]),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))});exports.BlokContent=d,exports.useBlok=c;
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-S6c68iVJ.mjs";
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, useMemo as a, useRef as o, useState as s } from "react";
4
- import { jsx as c } from "react/jsx-runtime";
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 l = /* @__PURE__ */ new WeakMap();
7
- function u(e, t) {
8
- l.set(e, t);
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
- l.delete(e);
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 p(e, r) {
19
- let [c, l] = s(null), d = o(e);
20
- d.current = e;
21
- let p = o({
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
- }), h = a(() => ({}), r == null ? [] : r);
27
+ }), m = o(() => ({}), r == null ? [] : r);
28
28
  i(() => {
29
29
  if (typeof window > "u") return;
30
- let e = p.current;
31
- if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken === h) return l(e.editor), () => {
32
- m(e, l);
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
- f(e.editor);
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 = h;
43
- let i = d.current, a = new t(n(n({}, 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 = d.current).onReady) == null || e.call(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 = d.current).onChange) == null || t.call(n, ...e);
51
+ (t = (n = u.current).onChange) == null || t.call(n, ...e);
52
52
  }
53
53
  }));
54
- return e.editor = a, u(a, r), a.isReady.then(() => {
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
- f(a);
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
- m(e, l);
65
+ h(e, l);
66
66
  };
67
- }, [h]);
67
+ }, [m]);
68
68
  let { readOnly: g } = e;
69
69
  i(() => {
70
- c !== null && c.readOnly.set(g == null ? !1 : g);
71
- }, [c, g]);
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
- c === null || !_ || c.focus();
75
- }, [c, _]), c;
86
+ a === null || b === void 0 || a.placeholder.set(b);
87
+ }, [a, b]), a;
76
88
  }
77
- function m(e, t) {
89
+ function h(e, t) {
78
90
  e.destroyTimeout = setTimeout(() => {
79
91
  if (e.editor !== null) {
80
- f(e.editor);
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 h = ["editor"], g = r(function(t, r) {
91
- let { editor: a } = t, s = e(t, h), l = o(null);
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 || l.current === null) return;
94
- let e = d(a);
95
- if (e !== void 0) return l.current.appendChild(e), () => {
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__ */ c("div", n({ ref: (e) => {
99
- if (l.current = e, typeof r == "function") r(e);
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
- } }, s));
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 { g as BlokContent, p as useBlok };
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-BIqDMhRj.cjs`),t=require(`./chunks/tools-DTjC0C5o.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;
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-BvxJIgSQ.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-B89FUeNz.mjs";
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.22.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;
@@ -1,3 +1,5 @@
1
1
  export { useBlok } from './useBlok';
2
2
  export { BlokContent } from './BlokContent';
3
+ export { BlokEditor } from './BlokEditor';
3
4
  export type { UseBlokConfig, BlokContentProps } from './types';
5
+ export type { BlokEditorProps } from './BlokEditor';
@@ -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` — the holder is managed by BlokContent.
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.
@@ -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
@@ -19,3 +19,4 @@ export * from './tools';
19
19
 
20
20
  export * from './theme';
21
21
  export * from './width';
22
+ export * from './placeholder';
@@ -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
  /**