@bloklabs/react 1.1.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/index.cjs +1 -0
- package/dist/index.mjs +313 -0
- package/package.json +40 -0
- package/src/BlokContent.tsx +46 -0
- package/src/BlokEditor.tsx +157 -0
- package/src/blocks-snapshot.ts +27 -0
- package/src/config-keys.ts +56 -0
- package/src/deep-equal.ts +7 -0
- package/src/holder-map.ts +17 -0
- package/src/index.ts +16 -0
- package/src/provide-blok.tsx +70 -0
- package/src/types.ts +33 -0
- package/src/useBlocks.ts +91 -0
- package/src/useBlok.ts +365 -0
- package/types/index.d.ts +538 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`),t=require(`@bloklabs/core`),n=require(`@bloklabs/core/adapters`),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(e){"@babel/helpers - typeof";return c=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},c(e)}function l(e,t){if(c(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(c(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function u(e){var t=l(e,`string`);return c(t)==`symbol`?t:t+``}function d(e,t,n){return(t=u(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function f(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function p(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:arguments[t];t%2?f(Object(n),!0).forEach(function(t){d(e,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):f(Object(n)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})}return e}var m=(0,e.createContext)({});function h({defaults:e,children:t}){return(0,r.jsx)(m.Provider,{value:e,children:t})}function g(){return(0,e.useContext)(m)}function _(e,t){if(Object.keys(e).length===0)return t;let n=p({},e);for(let e of Object.keys(t)){let r=t[e];r!==void 0&&(n[e]=r)}return(e.tools!==void 0||t.tools!==void 0)&&(n.tools=p(p({},e.tools),t.tools)),n}function v(r,i){let o=_((0,e.useContext)(m),r),[c,l]=(0,e.useState)(null),u=(0,e.useRef)(o);u.current=o;let d=(0,e.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,isEditorReady:!1,depsToken:null}),f=(0,e.useMemo)(()=>({}),i==null?[]:i),h=(0,e.useRef)(o.data),g=(0,e.useRef)(null),v=(0,e.useRef)(o.data),b=(0,e.useRef)(Promise.resolve()),x=(0,e.useRef)(void 0);(0,e.useEffect)(()=>{if(typeof window>`u`)return;let e=d.current;if(e.destroyTimeout!==null&&(clearTimeout(e.destroyTimeout),e.destroyTimeout=null),e.editor!==null&&!e.isDestroyed&&e.depsToken===f)return e.isEditorReady&&l(e.editor),()=>{y(e,l)};if(e.editor!==null&&!e.isDestroyed){s(e.editor);try{e.editor.destroy()}catch(e){}e.editor=null,e.holder=null,e.isDestroyed=!0,e.isEditorReady=!1,l(null)}let n=document.createElement(`div`);e.holder=n,e.isDestroyed=!1,e.isEditorReady=!1,e.depsToken=f;let r=u.current,i=p(p({},r),{},{holder:n,onReady:(...e)=>{var t,n;(t=(n=u.current).onReady)==null||t.call(n,...e)},onChange:(...e)=>{var t,n;(t=(n=u.current).onChange)==null||t.call(n,...e)}});r.onSave&&(i.onSave=(...e)=>{var t,n;h.current=e[0],(t=(n=u.current).onSave)==null||t.call(n,...e)}),r.onBeforeRender&&(i.onBeforeRender=(...e)=>{var t,n,r;return(t=(n=(r=u.current).onBeforeRender)==null?void 0:n.call(r,...e))==null?e[0]:t}),r.onAfterRender&&(i.onAfterRender=(...e)=>{var t,n;(t=(n=u.current).onAfterRender)==null||t.call(n,...e)});let o=new t.Blok(i);return e.editor=o,v.current=r.data,a(o,n),o.isReady.then(()=>{e.editor===o&&!e.isDestroyed&&(e.isEditorReady=!0,l(o))}).catch(()=>{if(e.editor===o&&!e.isDestroyed){s(o);try{o.destroy()}catch(e){}e.editor=null,e.holder=null,e.isDestroyed=!0,e.isEditorReady=!1,l(null)}}),()=>{y(e,l)}},[f]);let S=(0,n.normalizeReadOnlyConfig)(o.readOnly).enabled;(0,e.useEffect)(()=>{c!==null&&c.readOnly.set(S)},[c,S]);let{autofocus:C}=o;(0,e.useEffect)(()=>{c===null||!C||c.focus()},[c,C]);let{theme:w}=o;(0,e.useEffect)(()=>{c===null||w===void 0||c.theme.set(w)},[c,w]);let{width:T}=o;(0,e.useEffect)(()=>{c===null||T===void 0||c.width.set(T)},[c,T]);let{placeholder:E}=o;(0,e.useEffect)(()=>{c===null||E===void 0||c.placeholder.set(E)},[c,E]);let{data:D}=o;return(0,e.useEffect)(()=>{if(c===null||D===void 0)return;let e=d.current;if(!(e.editor!==c||e.isDestroyed)){if(g.current!==c&&(g.current=c,x.current=void 0,(0,n.deepEqual)(D,v.current))){h.current=D;return}(0,n.deepEqual)(D,h.current)||(0,n.deepEqual)(D,x.current)||(x.current=D,b.current=b.current.catch(()=>void 0).then(()=>c.render(D)).then(()=>{h.current=D},()=>{}).then(()=>{x.current===D&&(x.current=void 0)}))}},[c,D]),c}function y(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.isEditorReady=!1,e.destroyTimeout=null,t(null)}},0)}function b(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.includes(r))continue;n[r]=e[r]}return n}function x(e,t){if(e==null)return{};var n,r,i=b(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.includes(n)||{}.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var S=[`editor`],C=(0,e.forwardRef)(function(t,n){let{editor:i}=t,a=x(t,S),s=(0,e.useRef)(null);return(0,e.useEffect)(()=>{if(i===null||s.current===null)return;let e=o(i);if(e!==void 0)return s.current.appendChild(e),()=>{e.remove()}},[i]),(0,r.jsx)(`div`,p({ref:e=>{if(s.current=e,typeof n==`function`)n(e);else if(n!=null){let t=n;t.current=e}}},a))}),w=`autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.onSave.onBeforeRender.onAfterRender.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width`.split(`.`),T=[`deps`,`onReady`,`onBlocksRendered`,`onBlockRendered`],E=new Set(w),D=(0,e.forwardRef)(function(t,n){let{deps:i,onReady:a,onBlocksRendered:o,onBlockRendered:s}=t,c=x(t,T),l={},u={};for(let e of Object.keys(c)){let t=c[e];E.has(e)?l[e]=t:u[e]=t}let d=v(l,i);(0,e.useImperativeHandle)(n,()=>d,[d]);let f=(0,e.useRef)(a);f.current=a,(0,e.useEffect)(()=>{if(d!==null){var e;(e=f.current)==null||e.call(f,d)}},[d]);let m=(0,e.useRef)(o);m.current=o;let h=(0,e.useRef)(s);h.current=s;let g=!!o,_=!!s;return(0,e.useEffect)(()=>{if(d===null)return;let e=[];if(g){let t=e=>{var t;return(t=m.current)==null?void 0:t.call(m,e)};d.on(`blocks:rendered`,t),e.push([`blocks:rendered`,t])}if(_){let t=e=>{var t;return(t=h.current)==null?void 0:t.call(h,e)};d.on(`block:rendered`,t),e.push([`block:rendered`,t])}return()=>{for(let[t,n]of e)d.off(t,n)}},[d,g,_]),(0,r.jsx)(C,p({editor:d},u))}),O=`block changed`;function k(t){let r=(0,e.useRef)(0),i=(0,e.useRef)(t);i.current!==t&&(i.current=t,r.current=0);let a=(0,e.useCallback)(e=>{if(t===null)return()=>void 0;let n=()=>{r.current+=1,e()};return t.on(O,n),()=>t.off(O,n)},[t]),o=(0,e.useCallback)(()=>r.current,[]);return(0,e.useSyncExternalStore)(a,o,o),(0,e.useMemo)(()=>t===null?n.EMPTY_API:(0,n.createBlocksApiForEditor)(t),[t])}exports.BlokContent=C,exports.BlokEditor=D,exports.BlokProvider=h,exports.useBlocks=k,exports.useBlok=v,exports.useBlokDefaults=g;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { createContext as e, forwardRef as t, useCallback as n, useContext as r, useEffect as i, useImperativeHandle as a, useMemo as o, useRef as s, useState as c, useSyncExternalStore as l } from "react";
|
|
2
|
+
import { Blok as u } from "@bloklabs/core";
|
|
3
|
+
import { EMPTY_API as d, createBlocksApiForEditor as f, deepEqual as p, normalizeReadOnlyConfig as m } from "@bloklabs/core/adapters";
|
|
4
|
+
import { jsx as h } from "react/jsx-runtime";
|
|
5
|
+
//#region src/holder-map.ts
|
|
6
|
+
var g = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function _(e, t) {
|
|
8
|
+
g.set(e, t);
|
|
9
|
+
}
|
|
10
|
+
function v(e) {
|
|
11
|
+
return g.get(e);
|
|
12
|
+
}
|
|
13
|
+
function y(e) {
|
|
14
|
+
g.delete(e);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/typeof.js
|
|
18
|
+
function b(e) {
|
|
19
|
+
"@babel/helpers - typeof";
|
|
20
|
+
return b = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(e) {
|
|
21
|
+
return typeof e;
|
|
22
|
+
} : function(e) {
|
|
23
|
+
return e && typeof Symbol == "function" && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e;
|
|
24
|
+
}, b(e);
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/toPrimitive.js
|
|
28
|
+
function x(e, t) {
|
|
29
|
+
if (b(e) != "object" || !e) return e;
|
|
30
|
+
var n = e[Symbol.toPrimitive];
|
|
31
|
+
if (n !== void 0) {
|
|
32
|
+
var r = n.call(e, t || "default");
|
|
33
|
+
if (b(r) != "object") return r;
|
|
34
|
+
throw TypeError("@@toPrimitive must return a primitive value.");
|
|
35
|
+
}
|
|
36
|
+
return (t === "string" ? String : Number)(e);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/toPropertyKey.js
|
|
40
|
+
function S(e) {
|
|
41
|
+
var t = x(e, "string");
|
|
42
|
+
return b(t) == "symbol" ? t : t + "";
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/defineProperty.js
|
|
46
|
+
function C(e, t, n) {
|
|
47
|
+
return (t = S(t)) in e ? Object.defineProperty(e, t, {
|
|
48
|
+
value: n,
|
|
49
|
+
enumerable: !0,
|
|
50
|
+
configurable: !0,
|
|
51
|
+
writable: !0
|
|
52
|
+
}) : e[t] = n, e;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/objectSpread2.js
|
|
56
|
+
function w(e, t) {
|
|
57
|
+
var n = Object.keys(e);
|
|
58
|
+
if (Object.getOwnPropertySymbols) {
|
|
59
|
+
var r = Object.getOwnPropertySymbols(e);
|
|
60
|
+
t && (r = r.filter(function(t) {
|
|
61
|
+
return Object.getOwnPropertyDescriptor(e, t).enumerable;
|
|
62
|
+
})), n.push.apply(n, r);
|
|
63
|
+
}
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
function T(e) {
|
|
67
|
+
for (var t = 1; t < arguments.length; t++) {
|
|
68
|
+
var n = arguments[t] == null ? {} : arguments[t];
|
|
69
|
+
t % 2 ? w(Object(n), !0).forEach(function(t) {
|
|
70
|
+
C(e, t, n[t]);
|
|
71
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : w(Object(n)).forEach(function(t) {
|
|
72
|
+
Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return e;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/provide-blok.tsx
|
|
79
|
+
var E = e({});
|
|
80
|
+
function D({ defaults: e, children: t }) {
|
|
81
|
+
return /* @__PURE__ */ h(E.Provider, {
|
|
82
|
+
value: e,
|
|
83
|
+
children: t
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function O() {
|
|
87
|
+
return r(E);
|
|
88
|
+
}
|
|
89
|
+
function k(e, t) {
|
|
90
|
+
if (Object.keys(e).length === 0) return t;
|
|
91
|
+
let n = T({}, e);
|
|
92
|
+
for (let e of Object.keys(t)) {
|
|
93
|
+
let r = t[e];
|
|
94
|
+
r !== void 0 && (n[e] = r);
|
|
95
|
+
}
|
|
96
|
+
return (e.tools !== void 0 || t.tools !== void 0) && (n.tools = T(T({}, e.tools), t.tools)), n;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/useBlok.ts
|
|
100
|
+
function A(e, t) {
|
|
101
|
+
let n = k(r(E), e), [a, l] = c(null), d = s(n);
|
|
102
|
+
d.current = n;
|
|
103
|
+
let f = s({
|
|
104
|
+
editor: null,
|
|
105
|
+
holder: null,
|
|
106
|
+
destroyTimeout: null,
|
|
107
|
+
isDestroyed: !1,
|
|
108
|
+
isEditorReady: !1,
|
|
109
|
+
depsToken: null
|
|
110
|
+
}), h = o(() => ({}), t == null ? [] : t), g = s(n.data), v = s(null), b = s(n.data), x = s(Promise.resolve()), S = s(void 0);
|
|
111
|
+
i(() => {
|
|
112
|
+
if (typeof window > "u") return;
|
|
113
|
+
let e = f.current;
|
|
114
|
+
if (e.destroyTimeout !== null && (clearTimeout(e.destroyTimeout), e.destroyTimeout = null), e.editor !== null && !e.isDestroyed && e.depsToken === h) return e.isEditorReady && l(e.editor), () => {
|
|
115
|
+
j(e, l);
|
|
116
|
+
};
|
|
117
|
+
if (e.editor !== null && !e.isDestroyed) {
|
|
118
|
+
y(e.editor);
|
|
119
|
+
try {
|
|
120
|
+
e.editor.destroy();
|
|
121
|
+
} catch (e) {}
|
|
122
|
+
e.editor = null, e.holder = null, e.isDestroyed = !0, e.isEditorReady = !1, l(null);
|
|
123
|
+
}
|
|
124
|
+
let t = document.createElement("div");
|
|
125
|
+
e.holder = t, e.isDestroyed = !1, e.isEditorReady = !1, e.depsToken = h;
|
|
126
|
+
let n = d.current, r = T(T({}, n), {}, {
|
|
127
|
+
holder: t,
|
|
128
|
+
onReady: (...e) => {
|
|
129
|
+
var t, n;
|
|
130
|
+
(t = (n = d.current).onReady) == null || t.call(n, ...e);
|
|
131
|
+
},
|
|
132
|
+
onChange: (...e) => {
|
|
133
|
+
var t, n;
|
|
134
|
+
(t = (n = d.current).onChange) == null || t.call(n, ...e);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
n.onSave && (r.onSave = (...e) => {
|
|
138
|
+
var t, n;
|
|
139
|
+
g.current = e[0], (t = (n = d.current).onSave) == null || t.call(n, ...e);
|
|
140
|
+
}), n.onBeforeRender && (r.onBeforeRender = (...e) => {
|
|
141
|
+
var t, n, r;
|
|
142
|
+
return (t = (n = (r = d.current).onBeforeRender) == null ? void 0 : n.call(r, ...e)) == null ? e[0] : t;
|
|
143
|
+
}), n.onAfterRender && (r.onAfterRender = (...e) => {
|
|
144
|
+
var t, n;
|
|
145
|
+
(t = (n = d.current).onAfterRender) == null || t.call(n, ...e);
|
|
146
|
+
});
|
|
147
|
+
let i = new u(r);
|
|
148
|
+
return e.editor = i, b.current = n.data, _(i, t), i.isReady.then(() => {
|
|
149
|
+
e.editor === i && !e.isDestroyed && (e.isEditorReady = !0, l(i));
|
|
150
|
+
}).catch(() => {
|
|
151
|
+
if (e.editor === i && !e.isDestroyed) {
|
|
152
|
+
y(i);
|
|
153
|
+
try {
|
|
154
|
+
i.destroy();
|
|
155
|
+
} catch (e) {}
|
|
156
|
+
e.editor = null, e.holder = null, e.isDestroyed = !0, e.isEditorReady = !1, l(null);
|
|
157
|
+
}
|
|
158
|
+
}), () => {
|
|
159
|
+
j(e, l);
|
|
160
|
+
};
|
|
161
|
+
}, [h]);
|
|
162
|
+
let C = m(n.readOnly).enabled;
|
|
163
|
+
i(() => {
|
|
164
|
+
a !== null && a.readOnly.set(C);
|
|
165
|
+
}, [a, C]);
|
|
166
|
+
let { autofocus: w } = n;
|
|
167
|
+
i(() => {
|
|
168
|
+
a === null || !w || a.focus();
|
|
169
|
+
}, [a, w]);
|
|
170
|
+
let { theme: D } = n;
|
|
171
|
+
i(() => {
|
|
172
|
+
a === null || D === void 0 || a.theme.set(D);
|
|
173
|
+
}, [a, D]);
|
|
174
|
+
let { width: O } = n;
|
|
175
|
+
i(() => {
|
|
176
|
+
a === null || O === void 0 || a.width.set(O);
|
|
177
|
+
}, [a, O]);
|
|
178
|
+
let { placeholder: A } = n;
|
|
179
|
+
i(() => {
|
|
180
|
+
a === null || A === void 0 || a.placeholder.set(A);
|
|
181
|
+
}, [a, A]);
|
|
182
|
+
let { data: M } = n;
|
|
183
|
+
return i(() => {
|
|
184
|
+
if (a === null || M === void 0) return;
|
|
185
|
+
let e = f.current;
|
|
186
|
+
if (!(e.editor !== a || e.isDestroyed)) {
|
|
187
|
+
if (v.current !== a && (v.current = a, S.current = void 0, p(M, b.current))) {
|
|
188
|
+
g.current = M;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
p(M, g.current) || p(M, S.current) || (S.current = M, x.current = x.current.catch(() => void 0).then(() => a.render(M)).then(() => {
|
|
192
|
+
g.current = M;
|
|
193
|
+
}, () => {}).then(() => {
|
|
194
|
+
S.current === M && (S.current = void 0);
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
}, [a, M]), a;
|
|
198
|
+
}
|
|
199
|
+
function j(e, t) {
|
|
200
|
+
e.destroyTimeout = setTimeout(() => {
|
|
201
|
+
if (e.editor !== null) {
|
|
202
|
+
y(e.editor);
|
|
203
|
+
try {
|
|
204
|
+
e.editor.destroy();
|
|
205
|
+
} catch (e) {}
|
|
206
|
+
e.editor = null, e.holder = null, e.isDestroyed = !0, e.isEditorReady = !1, e.destroyTimeout = null, t(null);
|
|
207
|
+
}
|
|
208
|
+
}, 0);
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/objectWithoutPropertiesLoose.js
|
|
212
|
+
function M(e, t) {
|
|
213
|
+
if (e == null) return {};
|
|
214
|
+
var n = {};
|
|
215
|
+
for (var r in e) if ({}.hasOwnProperty.call(e, r)) {
|
|
216
|
+
if (t.includes(r)) continue;
|
|
217
|
+
n[r] = e[r];
|
|
218
|
+
}
|
|
219
|
+
return n;
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/objectWithoutProperties.js
|
|
223
|
+
function N(e, t) {
|
|
224
|
+
if (e == null) return {};
|
|
225
|
+
var n, r, i = M(e, t);
|
|
226
|
+
if (Object.getOwnPropertySymbols) {
|
|
227
|
+
var a = Object.getOwnPropertySymbols(e);
|
|
228
|
+
for (r = 0; r < a.length; r++) n = a[r], t.includes(n) || {}.propertyIsEnumerable.call(e, n) && (i[n] = e[n]);
|
|
229
|
+
}
|
|
230
|
+
return i;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/BlokContent.tsx
|
|
234
|
+
var P = ["editor"], F = t(function(e, t) {
|
|
235
|
+
let { editor: n } = e, r = N(e, P), a = s(null);
|
|
236
|
+
return i(() => {
|
|
237
|
+
if (n === null || a.current === null) return;
|
|
238
|
+
let e = v(n);
|
|
239
|
+
if (e !== void 0) return a.current.appendChild(e), () => {
|
|
240
|
+
e.remove();
|
|
241
|
+
};
|
|
242
|
+
}, [n]), /* @__PURE__ */ h("div", T({ ref: (e) => {
|
|
243
|
+
if (a.current = e, typeof t == "function") t(e);
|
|
244
|
+
else if (t != null) {
|
|
245
|
+
let n = t;
|
|
246
|
+
n.current = e;
|
|
247
|
+
}
|
|
248
|
+
} }, r));
|
|
249
|
+
}), I = /* @__PURE__ */ "autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.onSave.onBeforeRender.onAfterRender.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width".split("."), L = [
|
|
250
|
+
"deps",
|
|
251
|
+
"onReady",
|
|
252
|
+
"onBlocksRendered",
|
|
253
|
+
"onBlockRendered"
|
|
254
|
+
], R = new Set(I), z = t(function(e, t) {
|
|
255
|
+
let { deps: n, onReady: r, onBlocksRendered: o, onBlockRendered: c } = e, l = N(e, L), u = {}, d = {};
|
|
256
|
+
for (let e of Object.keys(l)) {
|
|
257
|
+
let t = l[e];
|
|
258
|
+
R.has(e) ? u[e] = t : d[e] = t;
|
|
259
|
+
}
|
|
260
|
+
let f = A(u, n);
|
|
261
|
+
a(t, () => f, [f]);
|
|
262
|
+
let p = s(r);
|
|
263
|
+
p.current = r, i(() => {
|
|
264
|
+
if (f !== null) {
|
|
265
|
+
var e;
|
|
266
|
+
(e = p.current) == null || e.call(p, f);
|
|
267
|
+
}
|
|
268
|
+
}, [f]);
|
|
269
|
+
let m = s(o);
|
|
270
|
+
m.current = o;
|
|
271
|
+
let g = s(c);
|
|
272
|
+
g.current = c;
|
|
273
|
+
let _ = !!o, v = !!c;
|
|
274
|
+
return i(() => {
|
|
275
|
+
if (f === null) return;
|
|
276
|
+
let e = [];
|
|
277
|
+
if (_) {
|
|
278
|
+
let t = (e) => {
|
|
279
|
+
var t;
|
|
280
|
+
return (t = m.current) == null ? void 0 : t.call(m, e);
|
|
281
|
+
};
|
|
282
|
+
f.on("blocks:rendered", t), e.push(["blocks:rendered", t]);
|
|
283
|
+
}
|
|
284
|
+
if (v) {
|
|
285
|
+
let t = (e) => {
|
|
286
|
+
var t;
|
|
287
|
+
return (t = g.current) == null ? void 0 : t.call(g, e);
|
|
288
|
+
};
|
|
289
|
+
f.on("block:rendered", t), e.push(["block:rendered", t]);
|
|
290
|
+
}
|
|
291
|
+
return () => {
|
|
292
|
+
for (let [t, n] of e) f.off(t, n);
|
|
293
|
+
};
|
|
294
|
+
}, [
|
|
295
|
+
f,
|
|
296
|
+
_,
|
|
297
|
+
v
|
|
298
|
+
]), /* @__PURE__ */ h(F, T({ editor: f }, d));
|
|
299
|
+
}), B = "block changed";
|
|
300
|
+
function V(e) {
|
|
301
|
+
let t = s(0), r = s(e);
|
|
302
|
+
r.current !== e && (r.current = e, t.current = 0);
|
|
303
|
+
let i = n((n) => {
|
|
304
|
+
if (e === null) return () => void 0;
|
|
305
|
+
let r = () => {
|
|
306
|
+
t.current += 1, n();
|
|
307
|
+
};
|
|
308
|
+
return e.on(B, r), () => e.off(B, r);
|
|
309
|
+
}, [e]), a = n(() => t.current, []);
|
|
310
|
+
return l(i, a, a), o(() => e === null ? d : f(e), [e]);
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
export { F as BlokContent, z as BlokEditor, D as BlokProvider, V as useBlocks, A as useBlok, O as useBlokDefaults };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bloklabs/react",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "React bindings for the Blok editor",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "JackUait",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/JackUait/blok.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.mjs",
|
|
16
|
+
"types": "./types/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./types/index.d.ts",
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"types",
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "yarn run -T vite build --mode production"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@bloklabs/core": "^1.1.1",
|
|
37
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
38
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useRef, forwardRef } from 'react';
|
|
2
|
+
import { getHolder } from './holder-map';
|
|
3
|
+
import type { BlokContentProps } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Component that provides the DOM mount point for a Blok editor.
|
|
7
|
+
* Renders a <div> and adopts the editor's detached holder DOM into it.
|
|
8
|
+
* When editor is null, renders an empty div (for layout stability).
|
|
9
|
+
* Passes through all standard HTML div attributes (className, style, etc.).
|
|
10
|
+
*/
|
|
11
|
+
export const BlokContent = forwardRef<HTMLDivElement, BlokContentProps>(
|
|
12
|
+
function BlokContent({ editor, ...divProps }, ref) {
|
|
13
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
14
|
+
|
|
15
|
+
const setRefs = (node: HTMLDivElement | null): void => {
|
|
16
|
+
containerRef.current = node;
|
|
17
|
+
if (typeof ref === 'function') {
|
|
18
|
+
ref(node);
|
|
19
|
+
} else if (ref !== null && ref !== undefined) {
|
|
20
|
+
const mutableRef = ref;
|
|
21
|
+
|
|
22
|
+
mutableRef.current = node;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (editor === null || containerRef.current === null) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const holder = getHolder(editor);
|
|
32
|
+
|
|
33
|
+
if (holder === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
containerRef.current.appendChild(holder);
|
|
38
|
+
|
|
39
|
+
return (): void => {
|
|
40
|
+
holder.remove();
|
|
41
|
+
};
|
|
42
|
+
}, [editor]);
|
|
43
|
+
|
|
44
|
+
return <div ref={setRefs} {...divProps} />;
|
|
45
|
+
}
|
|
46
|
+
);
|
|
@@ -0,0 +1,157 @@
|
|
|
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, BlockRenderedPayload, BlocksRenderedPayload } 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 reactive (controlled-ish): it seeds the INITIAL content and, after
|
|
24
|
+
* mount, changing it to new *content* re-renders the editor via `render()` — no
|
|
25
|
+
* recreation. Updates are deep-equal–deduped (a new reference with identical
|
|
26
|
+
* content is a no-op, so it won't clobber the caret) and serialized (rapid
|
|
27
|
+
* changes can't overlap). For ad-hoc reloads you can still call
|
|
28
|
+
* `ref.current.render(newData)`.
|
|
29
|
+
*
|
|
30
|
+
* Pair `data` with `onSave` for a true controlled component: `onSave` is the
|
|
31
|
+
* output half, firing (debounced) with the full serialized `OutputData` on every
|
|
32
|
+
* content change — no manual `ref.current.save()` polling. Echoing that payload
|
|
33
|
+
* straight back via `onSave={setData}` is safe and caret-stable: the adapter
|
|
34
|
+
* records the editor's own emitted output as the content baseline, so the
|
|
35
|
+
* `onSave → setState → data` round-trip deep-equals that baseline and is deduped
|
|
36
|
+
* to a no-op (no re-render, no caret reset, no recursion). Genuine external `data`
|
|
37
|
+
* changes still re-render in place.
|
|
38
|
+
*/
|
|
39
|
+
export interface BlokEditorProps
|
|
40
|
+
extends Omit<UseBlokConfig, 'onReady'>,
|
|
41
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'onChange'> {
|
|
42
|
+
/**
|
|
43
|
+
* When any value changes, the editor is destroyed and recreated. Keep each
|
|
44
|
+
* value referentially stable (primitives or useMemo-stable objects) — a dep
|
|
45
|
+
* whose identity changes every render recreates the editor each time.
|
|
46
|
+
*/
|
|
47
|
+
deps?: DependencyList;
|
|
48
|
+
/** Test id forwarded to the editor container element (via data-testid). */
|
|
49
|
+
'data-testid'?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Called once the editor is ready, with the live Blok instance. Fires after the
|
|
52
|
+
* forwarded ref is committed, so `ref.current` is also populated at this point.
|
|
53
|
+
*/
|
|
54
|
+
onReady?: (editor: Blok) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Called after a batch render completes (core `blocks:rendered` event). The
|
|
57
|
+
* declarative analog of `ref.current.on('blocks:rendered', …)` — mirrors the
|
|
58
|
+
* Vue/Angular adapters' rendered-lifecycle outputs.
|
|
59
|
+
*/
|
|
60
|
+
onBlocksRendered?: (payload: BlocksRenderedPayload) => void;
|
|
61
|
+
/** Called for each block rendered into the DOM (core `block:rendered` event). */
|
|
62
|
+
onBlockRendered?: (payload: BlockRenderedPayload) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Fast membership test for partitioning props into editor config vs. div attributes. */
|
|
66
|
+
const CONFIG_KEY_SET = new Set<string>(USE_BLOK_CONFIG_KEYS);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The recommended way to embed Blok in React. Internally wires `useBlok` and
|
|
70
|
+
* `BlokContent`, and forwards a ref to the live `Blok` instance.
|
|
71
|
+
*
|
|
72
|
+
* Don't wrap this component in `styled()` or any HOC that reserves the `theme`
|
|
73
|
+
* prop — styled-components claims `theme` for its own `ThemeProvider`, so it
|
|
74
|
+
* never reaches the editor and theme sync silently breaks. Render it directly
|
|
75
|
+
* and style the container via `className`.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const ref = useRef<Blok | null>(null);
|
|
80
|
+
* <BlokEditor ref={ref} id="editor" tools={tools} data={data} theme={theme} onReady={(e) => e.focus()} />;
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const BlokEditor = forwardRef<Blok | null, BlokEditorProps>(
|
|
84
|
+
function BlokEditor({ deps, onReady, onBlocksRendered, onBlockRendered, ...rest }, ref) {
|
|
85
|
+
const config: Record<string, unknown> = {};
|
|
86
|
+
const divProps: Record<string, unknown> = {};
|
|
87
|
+
|
|
88
|
+
for (const key of Object.keys(rest)) {
|
|
89
|
+
const value = (rest as Record<string, unknown>)[key];
|
|
90
|
+
|
|
91
|
+
if (CONFIG_KEY_SET.has(key)) {
|
|
92
|
+
config[key] = value;
|
|
93
|
+
} else {
|
|
94
|
+
divProps[key] = value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const editor = useBlok(config as UseBlokConfig, deps);
|
|
99
|
+
|
|
100
|
+
useImperativeHandle<Blok | null, Blok | null>(ref, () => editor, [editor]);
|
|
101
|
+
|
|
102
|
+
// Own onReady here instead of forwarding it to the core config: the core
|
|
103
|
+
// onReady fires before useImperativeHandle commits the ref. This passive
|
|
104
|
+
// effect runs AFTER the commit, so both the instance argument and ref.current
|
|
105
|
+
// are reliably populated.
|
|
106
|
+
const onReadyRef = useRef(onReady);
|
|
107
|
+
onReadyRef.current = onReady;
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (editor !== null) {
|
|
110
|
+
onReadyRef.current?.(editor);
|
|
111
|
+
}
|
|
112
|
+
}, [editor]);
|
|
113
|
+
|
|
114
|
+
// Subscribe to the rendered-lifecycle events (the declarative analog of the
|
|
115
|
+
// Vue/Angular outputs). Latest handlers are read through refs so a new
|
|
116
|
+
// callback identity never resubscribes; presence booleans in the deps add or
|
|
117
|
+
// drop subscriptions when a handler appears/disappears.
|
|
118
|
+
const onBlocksRenderedRef = useRef(onBlocksRendered);
|
|
119
|
+
onBlocksRenderedRef.current = onBlocksRendered;
|
|
120
|
+
const onBlockRenderedRef = useRef(onBlockRendered);
|
|
121
|
+
onBlockRenderedRef.current = onBlockRendered;
|
|
122
|
+
|
|
123
|
+
const hasBlocksRendered = Boolean(onBlocksRendered);
|
|
124
|
+
const hasBlockRendered = Boolean(onBlockRendered);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (editor === null) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const subscriptions: Array<[string, (payload?: unknown) => void]> = [];
|
|
131
|
+
|
|
132
|
+
if (hasBlocksRendered) {
|
|
133
|
+
const handler = (payload?: unknown): void =>
|
|
134
|
+
onBlocksRenderedRef.current?.(payload as BlocksRenderedPayload);
|
|
135
|
+
|
|
136
|
+
editor.on('blocks:rendered', handler);
|
|
137
|
+
subscriptions.push(['blocks:rendered', handler]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (hasBlockRendered) {
|
|
141
|
+
const handler = (payload?: unknown): void =>
|
|
142
|
+
onBlockRenderedRef.current?.(payload as BlockRenderedPayload);
|
|
143
|
+
|
|
144
|
+
editor.on('block:rendered', handler);
|
|
145
|
+
subscriptions.push(['block:rendered', handler]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return (): void => {
|
|
149
|
+
for (const [name, handler] of subscriptions) {
|
|
150
|
+
editor.off(name, handler);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}, [editor, hasBlocksRendered, hasBlockRendered]);
|
|
154
|
+
|
|
155
|
+
return <BlokContent editor={editor} {...(divProps as HTMLAttributes<HTMLDivElement>)} />;
|
|
156
|
+
}
|
|
157
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// packages/react/src/blocks-snapshot.ts
|
|
2
|
+
//
|
|
3
|
+
// The block-tree connection types and pure resolution helpers are
|
|
4
|
+
// framework-agnostic and now live in a shared core module so the React, Vue,
|
|
5
|
+
// and Angular adapters — and core's own per-block `BlockAPI` — resolve flat
|
|
6
|
+
// insert/move indices through ONE battle-tested implementation (the parity
|
|
7
|
+
// requirement: author once, reuse everywhere). This file re-exports them so
|
|
8
|
+
// existing React imports (`./blocks-snapshot`) keep working unchanged.
|
|
9
|
+
export {
|
|
10
|
+
snapshotNodes,
|
|
11
|
+
resolveInsertIndex,
|
|
12
|
+
resolveMoveIndex,
|
|
13
|
+
parentMap,
|
|
14
|
+
isDescendantOf,
|
|
15
|
+
} from '@bloklabs/core/adapters';
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
BlockNode,
|
|
19
|
+
CaretTarget,
|
|
20
|
+
InsertPosition,
|
|
21
|
+
InsertSpec,
|
|
22
|
+
TreeInsertSpec,
|
|
23
|
+
MoveTarget,
|
|
24
|
+
BlocksReader,
|
|
25
|
+
IndexReader,
|
|
26
|
+
UseBlocksApi,
|
|
27
|
+
} from '@bloklabs/core/adapters';
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
'link',
|
|
29
|
+
'linkPaste',
|
|
30
|
+
'onBeforePaste',
|
|
31
|
+
'onReady',
|
|
32
|
+
'onChange',
|
|
33
|
+
'onSave',
|
|
34
|
+
'onBeforeRender',
|
|
35
|
+
'onAfterRender',
|
|
36
|
+
'inlineToolbar',
|
|
37
|
+
'tunes',
|
|
38
|
+
'style',
|
|
39
|
+
'theme',
|
|
40
|
+
'onThemeChange',
|
|
41
|
+
'scrollToBlock',
|
|
42
|
+
'user',
|
|
43
|
+
'resolveUser',
|
|
44
|
+
'notifierPosition',
|
|
45
|
+
'notifier',
|
|
46
|
+
'width',
|
|
47
|
+
] as const satisfies readonly (keyof UseBlokConfig)[];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compile error if a `UseBlokConfig` key is missing from the list above.
|
|
51
|
+
* (`satisfies` only proves each entry is a valid key; this proves coverage.)
|
|
52
|
+
*/
|
|
53
|
+
type _MissingConfigKey = Exclude<keyof UseBlokConfig, (typeof USE_BLOK_CONFIG_KEYS)[number]>;
|
|
54
|
+
const _exhaustive: _MissingConfigKey extends never ? true : never = true;
|
|
55
|
+
|
|
56
|
+
void _exhaustive;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export of the framework-agnostic deep equality used to dedupe reactive
|
|
3
|
+
* `data` updates. The implementation now lives in `src/shared/deep-equal.ts`
|
|
4
|
+
* (shared with the Angular adapter); this module is kept for back-compat with
|
|
5
|
+
* existing `./deep-equal` importers in the React adapter.
|
|
6
|
+
*/
|
|
7
|
+
export { deepEqual } from '@bloklabs/core/adapters';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level WeakMap associating editor instances with their detached holder divs.
|
|
3
|
+
* WeakMap ensures no memory leaks — when the editor is garbage collected, the holder reference is released.
|
|
4
|
+
*/
|
|
5
|
+
const holders = new WeakMap<WeakKey, HTMLDivElement>();
|
|
6
|
+
|
|
7
|
+
export function setHolder(editor: WeakKey, holder: HTMLDivElement): void {
|
|
8
|
+
holders.set(editor, holder);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getHolder(editor: WeakKey): HTMLDivElement | undefined {
|
|
12
|
+
return holders.get(editor);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function removeHolder(editor: WeakKey): void {
|
|
16
|
+
holders.delete(editor);
|
|
17
|
+
}
|