@jackuait/blok 0.23.3 → 0.23.5

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.
Files changed (34) hide show
  1. package/README.md +3 -3
  2. package/dist/blok.cjs +1 -1
  3. package/dist/blok.iife.js +14 -14
  4. package/dist/blok.mjs +2 -2
  5. package/dist/blok.umd.js +6 -6
  6. package/dist/chunks/{blok-J3Lfa_bK.cjs → blok-DixLFA0K.cjs} +5 -5
  7. package/dist/chunks/{blok-1H9roBGf.mjs → blok-TvOrZOjF.mjs} +2195 -2170
  8. package/dist/chunks/{constants-BZUc1kAY.cjs → constants-Bze_TXgc.cjs} +1 -1
  9. package/dist/chunks/{constants-BJ79cqMa.mjs → constants-DpIX_GOn.mjs} +2 -1
  10. package/dist/chunks/{tools-OHDv2C7w.cjs → tools-CRAPWuhj.cjs} +1 -1
  11. package/dist/chunks/{tools-ByOjWADT.mjs → tools-DBb88o-I.mjs} +1 -1
  12. package/dist/full.cjs +1 -1
  13. package/dist/full.mjs +3 -3
  14. package/dist/react.cjs +1 -1
  15. package/dist/react.mjs +42 -31
  16. package/dist/tools.cjs +1 -1
  17. package/dist/tools.mjs +2 -2
  18. package/package.json +1 -1
  19. package/src/components/block/index.ts +3 -2
  20. package/src/components/block/tunes-manager.ts +7 -2
  21. package/src/components/constants/data-attributes.ts +4 -0
  22. package/src/components/modules/modificationsObserver.ts +40 -2
  23. package/src/components/modules/paste/handlers/html-handler.ts +14 -1
  24. package/src/components/modules/paste/index.ts +1 -1
  25. package/src/components/modules/renderer.ts +32 -4
  26. package/src/components/modules/toolbar/blockSettings.ts +15 -2
  27. package/src/components/utils/apply-link-config.ts +40 -0
  28. package/src/react/BlokEditor.tsx +10 -2
  29. package/src/react/config-keys.ts +3 -0
  30. package/src/react/types.ts +4 -0
  31. package/src/react/useBlok.ts +45 -6
  32. package/types/block-tunes/block-tune.d.ts +21 -1
  33. package/types/configs/blok-config.d.ts +62 -5
  34. package/types/index.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { i as e, t } from "./chunk-5O_1xk2F.mjs";
2
- import { $ as n, $n as r, $r as i, $t as a, Ai as o, An as s, Ar as c, At as l, B as u, Bn as d, Br as f, Bt as p, C as m, Ci as h, Cn as g, Cr as _, Ct as v, Dn as y, Dr as b, Dt as x, Ea as S, Ei as C, En as w, Er as T, Et as E, F as D, Fn as ee, Fr as O, Ft as k, G as A, Gn as te, Gr as j, Gt as M, H as N, Hn as ne, Hr as P, Ht as re, I as F, In as ie, Ir as ae, It as I, J as oe, Jn as L, Jr as se, Jt as ce, Kn as le, Kr as ue, Kt as de, L as R, Ln as fe, Lr as pe, Lt as me, Ma as z, Mn as he, Mr as ge, Mt as _e, Na as ve, Ni as ye, Nn as be, Nr as xe, Nt as Se, On as Ce, Or as we, Ot as Te, P as Ee, Pi as De, Pn as Oe, Pr as ke, Pt as Ae, Q as je, Qn as Me, Qr as Ne, Qt as Pe, R as Fe, Rn as Ie, Rr as Le, Rt as Re, S as ze, Si as Be, Sn as Ve, Sr as He, St as Ue, T as We, Ti as Ge, Tn as Ke, Tr as qe, Tt as Je, U as Ye, Un as Xe, Ur as Ze, Ut as Qe, Vn as $e, Vr as et, Vt as tt, W as B, Wn as nt, Wr as rt, Wt as it, X as at, Xn as ot, Xr as st, Xt as ct, Y as lt, Yn as ut, Yr as dt, Yt as ft, Z as pt, Zi as mt, Zn as ht, Zt as gt, _ as _t, _n as vt, _r as yt, _t as bt, a as xt, ai as St, ar as Ct, at as wt, b as Tt, bi as V, bn as Et, br as Dt, bt as Ot, c as kt, cn as At, cr as jt, ct as Mt, d as Nt, di as Pt, dn as Ft, dr as It, dt as Lt, ei as Rt, en as zt, er as Bt, et as Vt, f as Ht, fn as Ut, fr as Wt, ft as Gt, g as Kt, gi as qt, gn as Jt, gr as Yt, gt as Xt, h as Zt, hn as Qt, hr as $t, ht as en, i as tn, ia as nn, ii as rn, in as an, ir as on, it as sn, jn as cn, jr as ln, jt as un, ki as H, kn as dn, kr as fn, kt as pn, l as mn, li as hn, ln as gn, lr as _n, lt as vn, mn as yn, mr as bn, mt as xn, n as Sn, ni as Cn, nn as wn, nr as Tn, nt as En, o as Dn, oa as On, oi as kn, on as An, or as jn, ot as Mn, p as Nn, pi as Pn, pn as Fn, pr as In, pt as Ln, q as Rn, qr as zn, qt as Bn, r as Vn, ri as Hn, rr as Un, rt as Wn, s as Gn, si as Kn, sn as qn, sr as Jn, st as Yn, t as Xn, ti as Zn, tr as Qn, u as U, ui as $n, un as er, ur as tr, ut as nr, v as rr, vn as ir, vr as ar, vt as or, wi as sr, wn as cr, wr as lr, wt as ur, x as dr, xi as fr, xn as pr, xr as mr, xt as hr, y as gr, yn as _r, yr as vr, yt as yr, z as br, zn as xr, zr as Sr, zt as Cr } from "./constants-BJ79cqMa.mjs";
2
+ import { $ as n, $n as r, $r as i, $t as a, Ai as o, An as s, Ar as c, At as l, B as u, Bn as d, Br as f, Bt as p, C as m, Ci as h, Cn as g, Cr as _, Ct as v, Dn as y, Dr as b, Dt as x, Ea as S, Ei as C, En as w, Er as T, Et as E, F as D, Fn as ee, Fr as O, Ft as k, G as A, Gn as te, Gr as j, Gt as M, H as N, Hn as ne, Hr as P, Ht as re, I as F, In as ie, Ir as ae, It as I, J as oe, Jn as L, Jr as se, Jt as ce, Kn as le, Kr as ue, Kt as de, L as R, Ln as fe, Lr as pe, Lt as me, Ma as z, Mn as he, Mr as ge, Mt as _e, Na as ve, Ni as ye, Nn as be, Nr as xe, Nt as Se, On as Ce, Or as we, Ot as Te, P as Ee, Pi as De, Pn as Oe, Pr as ke, Pt as Ae, Q as je, Qn as Me, Qr as Ne, Qt as Pe, R as Fe, Rn as Ie, Rr as Le, Rt as Re, S as ze, Si as Be, Sn as Ve, Sr as He, St as Ue, T as We, Ti as Ge, Tn as Ke, Tr as qe, Tt as Je, U as Ye, Un as Xe, Ur as Ze, Ut as Qe, Vn as $e, Vr as et, Vt as tt, W as B, Wn as nt, Wr as rt, Wt as it, X as at, Xn as ot, Xr as st, Xt as ct, Y as lt, Yn as ut, Yr as dt, Yt as ft, Z as pt, Zi as mt, Zn as ht, Zt as gt, _ as _t, _n as vt, _r as yt, _t as bt, a as xt, ai as St, ar as Ct, at as wt, b as Tt, bi as V, bn as Et, br as Dt, bt as Ot, c as kt, cn as At, cr as jt, ct as Mt, d as Nt, di as Pt, dn as Ft, dr as It, dt as Lt, ei as Rt, en as zt, er as Bt, et as Vt, f as Ht, fn as Ut, fr as Wt, ft as Gt, g as Kt, gi as qt, gn as Jt, gr as Yt, gt as Xt, h as Zt, hn as Qt, hr as $t, ht as en, i as tn, ia as nn, ii as rn, in as an, ir as on, it as sn, jn as cn, jr as ln, jt as un, ki as H, kn as dn, kr as fn, kt as pn, l as mn, li as hn, ln as gn, lr as _n, lt as vn, mn as yn, mr as bn, mt as xn, n as Sn, ni as Cn, nn as wn, nr as Tn, nt as En, o as Dn, oa as On, oi as kn, on as An, or as jn, ot as Mn, p as Nn, pi as Pn, pn as Fn, pr as In, pt as Ln, q as Rn, qr as zn, qt as Bn, r as Vn, ri as Hn, rr as Un, rt as Wn, s as Gn, si as Kn, sn as qn, sr as Jn, st as Yn, t as Xn, ti as Zn, tr as Qn, u as U, ui as $n, un as er, ur as tr, ut as nr, v as rr, vn as ir, vr as ar, vt as or, wi as sr, wn as cr, wr as lr, wt as ur, x as dr, xi as fr, xn as pr, xr as mr, xt as hr, y as gr, yn as _r, yr as vr, yt as yr, z as br, zn as xr, zr as Sr, zt as Cr } from "./constants-DpIX_GOn.mjs";
3
3
  import { t as W } from "./objectSpread2-jlBcdYeb.mjs";
4
4
  import { n as G } from "./tw-DmW6-pCY.mjs";
5
5
  import { n as wr } from "./lightweight-i18n-Cj88c8gl.mjs";
package/dist/full.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/blok-J3Lfa_bK.cjs`),t=require(`./chunks/constants-BZUc1kAY.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-OHDv2C7w.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.Ma,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-DixLFA0K.cjs`),t=require(`./chunks/constants-Bze_TXgc.cjs`),n=require(`./chunks/objectSpread2-iNkE6aJh.cjs`),r=require(`./chunks/tools-CRAPWuhj.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.Ma,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-1H9roBGf.mjs";
2
- import { Ma as n } from "./chunks/constants-BJ79cqMa.mjs";
1
+ import { n as e, t } from "./chunks/blok-TvOrZOjF.mjs";
2
+ import { Ma as n } from "./chunks/constants-DpIX_GOn.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-ByOjWADT.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-DBb88o-I.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-J3Lfa_bK.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(e,t){if(e===t)return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;let n=Array.isArray(e),r=Array.isArray(t);if(n!==r)return!1;if(n&&r)return e.length===t.length?e.every((e,n)=>c(e,t[n])):!1;let i=e,a=t,o=Object.keys(i),s=Object.keys(a);return o.length===s.length?o.every(e=>Object.prototype.hasOwnProperty.call(a,e)&&c(i[e],a[e])):!1}function l(r,i){let[o,l]=(0,n.useState)(null),d=(0,n.useRef)(r);d.current=r;let f=(0,n.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,depsToken:null}),p=(0,n.useMemo)(()=>({}),i==null?[]:i);(0,n.useEffect)(()=>{if(typeof window>`u`)return;let n=f.current;if(n.destroyTimeout!==null&&(clearTimeout(n.destroyTimeout),n.destroyTimeout=null),n.editor!==null&&!n.isDestroyed&&n.depsToken===p)return l(n.editor),()=>{u(n,l)};if(n.editor!==null&&!n.isDestroyed){s(n.editor);try{n.editor.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}let r=document.createElement(`div`);n.holder=r,n.isDestroyed=!1,n.depsToken=p;let i=d.current,o=new e.t(t.t(t.t({},i),{},{holder:r,onReady:()=>{var e,t;(e=(t=d.current).onReady)==null||e.call(t)},onChange:(...e)=>{var t,n;(t=(n=d.current).onChange)==null||t.call(n,...e)}}));return n.editor=o,a(o,r),o.isReady.then(()=>{n.editor===o&&!n.isDestroyed&&l(o)}).catch(()=>{if(n.editor===o&&!n.isDestroyed){s(o);try{o.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}}),()=>{u(n,l)}},[p]);let{readOnly:m}=r;(0,n.useEffect)(()=>{o!==null&&o.readOnly.set(m==null?!1:m)},[o,m]);let{autofocus:h}=r;(0,n.useEffect)(()=>{o===null||!h||o.focus()},[o,h]);let{theme:g}=r;(0,n.useEffect)(()=>{o===null||g===void 0||o.theme.set(g)},[o,g]);let{width:_}=r;(0,n.useEffect)(()=>{o===null||_===void 0||o.width.set(_)},[o,_]);let{placeholder:v}=r;(0,n.useEffect)(()=>{o===null||v===void 0||o.placeholder.set(v)},[o,v]);let{data:y}=r,b=(0,n.useRef)(r.data),x=(0,n.useRef)(null),S=(0,n.useRef)(Promise.resolve());return(0,n.useEffect)(()=>{if(!(o===null||y===void 0)){if(x.current!==o){x.current=o,b.current=y;return}c(y,b.current)||(b.current=y,S.current=S.current.catch(()=>void 0).then(()=>o.render(y)))}},[o,y]),o}function u(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 d=[`editor`],f=(0,n.forwardRef)(function(i,a){let{editor:s}=i,c=e.i(i,d),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))}),p=`autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width`.split(`.`),m=[`deps`,`onReady`],h=new Set(p),g=(0,n.forwardRef)(function(i,a){let{deps:o,onReady:s}=i,c=e.i(i,m),u={},d={};for(let e of Object.keys(c)){let t=c[e];h.has(e)?u[e]=t:d[e]=t}let p=l(u,o);(0,n.useImperativeHandle)(a,()=>p,[p]);let g=(0,n.useRef)(s);return g.current=s,(0,n.useEffect)(()=>{if(p!==null){var e;(e=g.current)==null||e.call(g,p)}},[p]),(0,r.jsx)(f,t.t({editor:p},d))});exports.BlokContent=f,exports.BlokEditor=g,exports.useBlok=l;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunks/chunk-BL1yB9zm.cjs`);const e=require(`./chunks/blok-DixLFA0K.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(e,t){if(e===t)return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;let n=Array.isArray(e),r=Array.isArray(t);if(n!==r)return!1;if(n&&r)return e.length===t.length?e.every((e,n)=>c(e,t[n])):!1;let i=e,a=t,o=Object.keys(i),s=Object.keys(a);return o.length===s.length?o.every(e=>Object.prototype.hasOwnProperty.call(a,e)&&c(i[e],a[e])):!1}function l(r,i){let[o,l]=(0,n.useState)(null),d=(0,n.useRef)(r);d.current=r;let f=(0,n.useRef)({editor:null,holder:null,destroyTimeout:null,isDestroyed:!1,depsToken:null}),p=(0,n.useMemo)(()=>({}),i==null?[]:i),m=(0,n.useRef)(r.data),h=(0,n.useRef)(null),g=(0,n.useRef)(Promise.resolve());(0,n.useEffect)(()=>{if(typeof window>`u`)return;let n=f.current;if(n.destroyTimeout!==null&&(clearTimeout(n.destroyTimeout),n.destroyTimeout=null),n.editor!==null&&!n.isDestroyed&&n.depsToken===p)return l(n.editor),()=>{u(n,l)};if(n.editor!==null&&!n.isDestroyed){s(n.editor);try{n.editor.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}let r=document.createElement(`div`);n.holder=r,n.isDestroyed=!1,n.depsToken=p;let i=d.current,o=t.t(t.t({},i),{},{holder:r,onReady:()=>{var e,t;(e=(t=d.current).onReady)==null||e.call(t)},onChange:(...e)=>{var t,n;(t=(n=d.current).onChange)==null||t.call(n,...e)}});i.onSave&&(o.onSave=(...e)=>{var t,n;m.current=e[0],(t=(n=d.current).onSave)==null||t.call(n,...e)}),i.onBeforeRender&&(o.onBeforeRender=(...e)=>{var t,n,r;return(t=(n=(r=d.current).onBeforeRender)==null?void 0:n.call(r,...e))==null?e[0]:t}),i.onAfterRender&&(o.onAfterRender=(...e)=>{var t,n;(t=(n=d.current).onAfterRender)==null||t.call(n,...e)});let c=new e.t(o);return n.editor=c,a(c,r),c.isReady.then(()=>{n.editor===c&&!n.isDestroyed&&l(c)}).catch(()=>{if(n.editor===c&&!n.isDestroyed){s(c);try{c.destroy()}catch(e){}n.editor=null,n.holder=null,n.isDestroyed=!0,l(null)}}),()=>{u(n,l)}},[p]);let{readOnly:_}=r;(0,n.useEffect)(()=>{o!==null&&o.readOnly.set(_==null?!1:_)},[o,_]);let{autofocus:v}=r;(0,n.useEffect)(()=>{o===null||!v||o.focus()},[o,v]);let{theme:y}=r;(0,n.useEffect)(()=>{o===null||y===void 0||o.theme.set(y)},[o,y]);let{width:b}=r;(0,n.useEffect)(()=>{o===null||b===void 0||o.width.set(b)},[o,b]);let{placeholder:x}=r;(0,n.useEffect)(()=>{o===null||x===void 0||o.placeholder.set(x)},[o,x]);let{data:S}=r;return(0,n.useEffect)(()=>{if(!(o===null||S===void 0)){if(h.current!==o){h.current=o,m.current=S;return}c(S,m.current)||(m.current=S,g.current=g.current.catch(()=>void 0).then(()=>o.render(S)))}},[o,S]),o}function u(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 d=[`editor`],f=(0,n.forwardRef)(function(i,a){let{editor:s}=i,c=e.i(i,d),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))}),p=`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(`.`),m=[`deps`,`onReady`],h=new Set(p),g=(0,n.forwardRef)(function(i,a){let{deps:o,onReady:s}=i,c=e.i(i,m),u={},d={};for(let e of Object.keys(c)){let t=c[e];h.has(e)?u[e]=t:d[e]=t}let p=l(u,o);(0,n.useImperativeHandle)(a,()=>p,[p]);let g=(0,n.useRef)(s);return g.current=s,(0,n.useEffect)(()=>{if(p!==null){var e;(e=g.current)==null||e.call(g,p)}},[p]),(0,r.jsx)(f,t.t({editor:p},d))});exports.BlokContent=f,exports.BlokEditor=g,exports.useBlok=l;
package/dist/react.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as e, t } from "./chunks/blok-1H9roBGf.mjs";
1
+ import { i as e, t } from "./chunks/blok-TvOrZOjF.mjs";
2
2
  import { t as n } from "./chunks/objectSpread2-jlBcdYeb.mjs";
3
3
  import { forwardRef as r, useEffect as i, useImperativeHandle as a, useMemo as o, useRef as s, useState as c } from "react";
4
4
  import { jsx as l } from "react/jsx-runtime";
@@ -35,7 +35,7 @@ function h(e, r) {
35
35
  destroyTimeout: null,
36
36
  isDestroyed: !1,
37
37
  depsToken: null
38
- }), h = o(() => ({}), r == null ? [] : r);
38
+ }), h = o(() => ({}), r == null ? [] : r), _ = s(e.data), v = s(null), y = s(Promise.resolve());
39
39
  i(() => {
40
40
  if (typeof window > "u") return;
41
41
  let e = f.current;
@@ -51,7 +51,7 @@ function h(e, r) {
51
51
  }
52
52
  let r = document.createElement("div");
53
53
  e.holder = r, e.isDestroyed = !1, e.depsToken = h;
54
- let i = u.current, a = new t(n(n({}, i), {}, {
54
+ let i = u.current, a = n(n({}, i), {}, {
55
55
  holder: r,
56
56
  onReady: () => {
57
57
  var e, t;
@@ -61,14 +61,25 @@ function h(e, r) {
61
61
  var t, n;
62
62
  (t = (n = u.current).onChange) == null || t.call(n, ...e);
63
63
  }
64
- }));
65
- return e.editor = a, d(a, r), a.isReady.then(() => {
66
- e.editor === a && !e.isDestroyed && l(a);
64
+ });
65
+ i.onSave && (a.onSave = (...e) => {
66
+ var t, n;
67
+ _.current = e[0], (t = (n = u.current).onSave) == null || t.call(n, ...e);
68
+ }), i.onBeforeRender && (a.onBeforeRender = (...e) => {
69
+ var t, n, r;
70
+ return (t = (n = (r = u.current).onBeforeRender) == null ? void 0 : n.call(r, ...e)) == null ? e[0] : t;
71
+ }), i.onAfterRender && (a.onAfterRender = (...e) => {
72
+ var t, n;
73
+ (t = (n = u.current).onAfterRender) == null || t.call(n, ...e);
74
+ });
75
+ let o = new t(a);
76
+ return e.editor = o, d(o, r), o.isReady.then(() => {
77
+ e.editor === o && !e.isDestroyed && l(o);
67
78
  }).catch(() => {
68
- if (e.editor === a && !e.isDestroyed) {
69
- p(a);
79
+ if (e.editor === o && !e.isDestroyed) {
80
+ p(o);
70
81
  try {
71
- a.destroy();
82
+ o.destroy();
72
83
  } catch (e) {}
73
84
  e.editor = null, e.holder = null, e.isDestroyed = !0, l(null);
74
85
  }
@@ -76,36 +87,36 @@ function h(e, r) {
76
87
  g(e, l);
77
88
  };
78
89
  }, [h]);
79
- let { readOnly: _ } = e;
90
+ let { readOnly: b } = e;
80
91
  i(() => {
81
- a !== null && a.readOnly.set(_ == null ? !1 : _);
82
- }, [a, _]);
83
- let { autofocus: v } = e;
92
+ a !== null && a.readOnly.set(b == null ? !1 : b);
93
+ }, [a, b]);
94
+ let { autofocus: x } = e;
84
95
  i(() => {
85
- a === null || !v || a.focus();
86
- }, [a, v]);
87
- let { theme: y } = e;
96
+ a === null || !x || a.focus();
97
+ }, [a, x]);
98
+ let { theme: S } = e;
88
99
  i(() => {
89
- a === null || y === void 0 || a.theme.set(y);
90
- }, [a, y]);
91
- let { width: b } = e;
100
+ a === null || S === void 0 || a.theme.set(S);
101
+ }, [a, S]);
102
+ let { width: C } = e;
92
103
  i(() => {
93
- a === null || b === void 0 || a.width.set(b);
94
- }, [a, b]);
95
- let { placeholder: x } = e;
104
+ a === null || C === void 0 || a.width.set(C);
105
+ }, [a, C]);
106
+ let { placeholder: w } = e;
96
107
  i(() => {
97
- a === null || x === void 0 || a.placeholder.set(x);
98
- }, [a, x]);
99
- let { data: S } = e, C = s(e.data), w = s(null), T = s(Promise.resolve());
108
+ a === null || w === void 0 || a.placeholder.set(w);
109
+ }, [a, w]);
110
+ let { data: T } = e;
100
111
  return i(() => {
101
- if (!(a === null || S === void 0)) {
102
- if (w.current !== a) {
103
- w.current = a, C.current = S;
112
+ if (!(a === null || T === void 0)) {
113
+ if (v.current !== a) {
114
+ v.current = a, _.current = T;
104
115
  return;
105
116
  }
106
- m(S, C.current) || (C.current = S, T.current = T.current.catch(() => void 0).then(() => a.render(S)));
117
+ m(T, _.current) || (_.current = T, y.current = y.current.catch(() => void 0).then(() => a.render(T)));
107
118
  }
108
- }, [a, S]), a;
119
+ }, [a, T]), a;
109
120
  }
110
121
  function g(e, t) {
111
122
  e.destroyTimeout = setTimeout(() => {
@@ -135,7 +146,7 @@ var _ = ["editor"], v = r(function(t, r) {
135
146
  t.current = e;
136
147
  }
137
148
  } }, o));
138
- }), y = /* @__PURE__ */ "autofocus.defaultBlock.dataModel.placeholder.sanitizer.hideToolbar.maxHistoryLength.historyDebounceTime.newGroupDelay.globalUndoRedo.tools.data.minHeight.logLevel.readOnly.i18n.link.linkPaste.onBeforePaste.onReady.onChange.inlineToolbar.tunes.style.theme.onThemeChange.scrollToBlock.user.resolveUser.notifierPosition.notifier.width".split("."), b = ["deps", "onReady"], x = new Set(y), S = r(function(t, r) {
149
+ }), y = /* @__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("."), b = ["deps", "onReady"], x = new Set(y), S = r(function(t, r) {
139
150
  let { deps: o, onReady: c } = t, u = e(t, b), d = {}, f = {};
140
151
  for (let e of Object.keys(u)) {
141
152
  let t = u[e];
package/dist/tools.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunks/constants-BZUc1kAY.cjs`),t=require(`./chunks/tools-OHDv2C7w.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-Bze_TXgc.cjs`),t=require(`./chunks/tools-CRAPWuhj.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-BJ79cqMa.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-ByOjWADT.mjs";
1
+ import { m as e } from "./chunks/constants-DpIX_GOn.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-DBb88o-I.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.23.3",
3
+ "version": "0.23.5",
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",
@@ -8,6 +8,7 @@ import type {
8
8
  PopoverItemParams
9
9
  } from '../../../types';
10
10
  import type { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
11
+ import type { BlockTuneRenderContext } from '../../../types/block-tunes/block-tune';
11
12
  import type { SavedData } from '../../../types/data-formats';
12
13
  import { Dom as $, toggleEmptyMark } from '../dom';
13
14
  import type { BlokEventMap } from '../events';
@@ -501,14 +502,14 @@ export class Block extends EventsDispatcher<BlockEvents> {
501
502
  * Returns data to render in Block Tunes menu.
502
503
  * Splits block tunes into 2 groups: block specific tunes and common tunes
503
504
  */
504
- public getTunes(): {
505
+ public getTunes(renderContext?: BlockTuneRenderContext): {
505
506
  toolTunes: PopoverItemParams[];
506
507
  commonTunes: PopoverItemParams[];
507
508
  } {
508
509
  /** Tool's tunes: may be defined as return value of optional renderSettings method */
509
510
  const tunesDefinedInTool = typeof this.toolInstance.renderSettings === 'function' ? this.toolInstance.renderSettings() : [];
510
511
 
511
- return this.tunesManager.getMenuConfig(tunesDefinedInTool);
512
+ return this.tunesManager.getMenuConfig(tunesDefinedInTool, renderContext);
512
513
  }
513
514
 
514
515
  /**
@@ -1,4 +1,5 @@
1
1
  import type { BlockTune as IBlockTune } from '../../../types';
2
+ import type { BlockTuneRenderContext } from '../../../types/block-tunes/block-tune';
2
3
  import type { BlockAPI as BlockAPIInterface } from '../../../types/api';
3
4
  import type { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
4
5
  import type { MenuConfigItem } from '../../../types/tools';
@@ -52,9 +53,13 @@ export class TunesManager {
52
53
  * Splits tunes into tool-specific (from tool's renderSettings) and common tunes.
53
54
  *
54
55
  * @param toolRenderSettings - Optional render settings from the tool
56
+ * @param renderContext - Optional context exposing the host tune popover element
55
57
  * @returns Object with toolTunes and commonTunes arrays
56
58
  */
57
- public getMenuConfig(toolRenderSettings?: MenuConfigItem | MenuConfigItem[] | HTMLElement): {
59
+ public getMenuConfig(
60
+ toolRenderSettings?: MenuConfigItem | MenuConfigItem[] | HTMLElement,
61
+ renderContext?: BlockTuneRenderContext
62
+ ): {
58
63
  toolTunes: PopoverItemParams[];
59
64
  commonTunes: PopoverItemParams[];
60
65
  } {
@@ -96,7 +101,7 @@ export class TunesManager {
96
101
  const commonTunes = [
97
102
  ...this.tunesInstances.values(),
98
103
  ...this.defaultTunesInstances.values(),
99
- ].map(tuneInstance => tuneInstance.render());
104
+ ].map(tuneInstance => tuneInstance.render(renderContext));
100
105
 
101
106
  /** Separate custom html from Popover items params for common tunes */
102
107
  commonTunes.forEach(tuneConfig => {
@@ -24,6 +24,10 @@ export const DATA_ATTR = {
24
24
  editor: 'data-blok-editor',
25
25
  /** Redactor zone */
26
26
  redactor: 'data-blok-redactor',
27
+ /** Present on the editor wrapper once a `blocks.render()` batch has finished
28
+ * inserting blocks into the DOM; removed while a re-render is in flight.
29
+ * Acts as a stable render-readiness gate for consumers (e.g. E2E waits). */
30
+ rendered: 'data-blok-rendered',
27
31
 
28
32
  // ============================================
29
33
  // Block Identifiers
@@ -42,6 +42,12 @@ export class ModificationsObserver extends Module {
42
42
  */
43
43
  private readonly batchTime = modificationsObserverBatchTimeout;
44
44
 
45
+ /**
46
+ * Set once the module is destroyed so an in-flight serialization (onSave)
47
+ * doesn't call back into a torn-down editor after its promise resolves.
48
+ */
49
+ private destroyed = false;
50
+
45
51
  /**
46
52
  * Prepare the module
47
53
  * @param options - options used by the modification observer module
@@ -103,7 +109,7 @@ export class ModificationsObserver extends Module {
103
109
  * @param event - some of our custom change events
104
110
  */
105
111
  private particularBlockChanged(event: BlockMutationEvent): void {
106
- if (this.disabled || !isFunction(this.config.onChange)) {
112
+ if (this.disabled || (!isFunction(this.config.onChange) && !isFunction(this.config.onSave))) {
107
113
  return;
108
114
  }
109
115
 
@@ -127,14 +133,45 @@ export class ModificationsObserver extends Module {
127
133
  ? queuedEvents[0]
128
134
  : queuedEvents;
129
135
 
130
- if (this.config.onChange) {
136
+ if (isFunction(this.config.onChange)) {
131
137
  this.config.onChange(this.Blok.API.methods, eventsToEmit);
132
138
  }
133
139
 
140
+ if (isFunction(this.config.onSave)) {
141
+ this.emitOnSave();
142
+ }
143
+
134
144
  this.batchingOnChangeQueue.clear();
135
145
  }, this.batchTime);
136
146
  }
137
147
 
148
+ /**
149
+ * Serializes the editor and delivers the full OutputData to the consumer's
150
+ * `onSave` callback. Invoked once per batched change window, so a burst of
151
+ * edits results in a single serialization. Skips delivery if the module was
152
+ * destroyed while the (async) serialization was in flight.
153
+ */
154
+ private emitOnSave(): void {
155
+ void this.Blok.Saver.save()
156
+ .then((data) => {
157
+ if (this.destroyed || data === undefined) {
158
+ return;
159
+ }
160
+
161
+ const { onSave } = this.config;
162
+
163
+ if (isFunction(onSave)) {
164
+ onSave(data, this.Blok.API.methods);
165
+ }
166
+ })
167
+ .catch(() => {
168
+ /**
169
+ * Serialization failed — the Saver already surfaces the error via its
170
+ * own channel, so swallow here to avoid an unhandled rejection.
171
+ */
172
+ });
173
+ }
174
+
138
175
  /**
139
176
  * Cleans up the module: disconnects the MutationObserver and cancels any
140
177
  * pending batching timeout. Called by the editor's destroy() chain so that
@@ -144,6 +181,7 @@ export class ModificationsObserver extends Module {
144
181
  */
145
182
  public destroy(): void {
146
183
  this.disabled = true;
184
+ this.destroyed = true;
147
185
  this.mutationObserver.disconnect();
148
186
 
149
187
  if (this.batchingTimeout !== null) {
@@ -1,8 +1,10 @@
1
+ import type { BlokConfig } from '../../../../../types/configs/blok-config';
1
2
  import type { SanitizerConfig } from '../../../../../types/configs/sanitizer-config';
2
3
  import type { BlokModules } from '../../../../types-internal/blok-modules';
3
4
  import { Dom as dom$ } from '../../../dom';
4
5
  import type { BlockToolAdapter } from '../../../tools/block';
5
6
  import { isObject } from '../../../utils';
7
+ import { applyLinkConfig } from '../../../utils/apply-link-config';
6
8
  import { clean } from '../../../utils/sanitizer';
7
9
  import { SAFE_STRUCTURAL_TAGS, collectTagNames } from '../constants';
8
10
  import type { SanitizerConfigBuilder } from '../sanitizer-config';
@@ -18,12 +20,16 @@ import { BasePasteHandler } from './base';
18
20
  * Handles HTML string content.
19
21
  */
20
22
  export class HtmlHandler extends BasePasteHandler implements PasteHandler {
23
+ private readonly linkConfig?: BlokConfig['link'];
24
+
21
25
  constructor(
22
26
  Blok: BlokModules,
23
27
  toolRegistry: ToolRegistry,
24
- sanitizerBuilder: SanitizerConfigBuilder
28
+ sanitizerBuilder: SanitizerConfigBuilder,
29
+ config?: BlokConfig
25
30
  ) {
26
31
  super(Blok, toolRegistry, sanitizerBuilder);
32
+ this.linkConfig = config?.link;
27
33
  }
28
34
 
29
35
  canHandle(data: unknown): number {
@@ -64,6 +70,13 @@ export class HtmlHandler extends BasePasteHandler implements PasteHandler {
64
70
 
65
71
  wrapper.innerHTML = innerHTML;
66
72
 
73
+ // Apply the editor's `link` config to pasted anchors before they are split
74
+ // into blocks, mirroring the interactive Link inline tool. Pasted `<a>`
75
+ // would otherwise keep their original (foreign) target/rel/href.
76
+ if (this.linkConfig !== undefined) {
77
+ applyLinkConfig(wrapper, this.linkConfig);
78
+ }
79
+
67
80
  const nodes = this.getNodes(wrapper);
68
81
 
69
82
  // Pre-expand DETAILS nodes: extract child elements (non-summary) before
@@ -59,7 +59,7 @@ export class Paste extends Module {
59
59
  new TableCellsHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder),
60
60
  new FilesHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder),
61
61
  new PatternHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder, this.config),
62
- new HtmlHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder),
62
+ new HtmlHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder, this.config),
63
63
  new MarkdownHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder),
64
64
  new TextHandler(this.Blok, this.toolRegistry, this.sanitizerBuilder, this.config),
65
65
  ];
@@ -13,6 +13,8 @@ import {
13
13
  type DataFormatAnalysis,
14
14
  } from '../utils/data-model-transform';
15
15
  import { migrateMarkColors } from '../utils/color-migration';
16
+ import { applyLinkConfig } from '../utils/apply-link-config';
17
+ import { DATA_ATTR } from '../constants';
16
18
  import { BlocksRendered } from '../events';
17
19
 
18
20
  /**
@@ -86,6 +88,14 @@ export class Renderer extends Module {
86
88
  * @param blocksData - blocks to render
87
89
  */
88
90
  public render(blocksData: OutputBlockData[]): Promise<void> {
91
+ const { wrapper } = this.Blok.UI.nodes;
92
+
93
+ /**
94
+ * Flip the render-readiness gate off synchronously: while a (re-)render is
95
+ * in flight the previously rendered content may already be cleared.
96
+ */
97
+ wrapper.removeAttribute(DATA_ATTR.rendered);
98
+
89
99
  return new Promise((resolve) => {
90
100
  const renderedCount = this.insertRenderedBlocks(blocksData);
91
101
 
@@ -93,7 +103,9 @@ export class Renderer extends Module {
93
103
  * Wait till browser will render inserted Blocks and resolve a promise
94
104
  */
95
105
  window.requestIdleCallback(() => {
106
+ wrapper.setAttribute(DATA_ATTR.rendered, '');
96
107
  this.eventsDispatcher.emit(BlocksRendered, { count: renderedCount });
108
+ this.config.onAfterRender?.(this.Blok.API.methods);
97
109
  resolve();
98
110
  }, { timeout: 2000 });
99
111
  });
@@ -107,7 +119,15 @@ export class Renderer extends Module {
107
119
  private insertRenderedBlocks(blocksData: OutputBlockData[]): number {
108
120
  const { Tools, BlockManager } = this.Blok;
109
121
 
110
- if (blocksData.length === 0) {
122
+ // Give consumers a chance to transform the blocks array before anything is
123
+ // rendered — e.g. to run app-specific legacy-data migrations inside Blok.
124
+ // Runs on the raw saved shape (before format analysis / hierarchical
125
+ // expansion) so the hook sees exactly what was passed to render().
126
+ const sourceBlocks = this.config.onBeforeRender !== undefined
127
+ ? this.config.onBeforeRender(blocksData)
128
+ : blocksData;
129
+
130
+ if (sourceBlocks.length === 0) {
111
131
  BlockManager.insert();
112
132
 
113
133
  return 1;
@@ -115,13 +135,13 @@ export class Renderer extends Module {
115
135
 
116
136
  // Analyze and potentially transform the input data
117
137
  const dataModelConfig = this.config.dataModel || 'auto';
118
- const analysis = analyzeDataFormat(blocksData);
138
+ const analysis = analyzeDataFormat(sourceBlocks);
119
139
  this.detectedInputFormat = analysis.format;
120
140
 
121
141
  // Transform to hierarchical if config requires it
122
142
  const expandedBlocks = shouldExpandToHierarchical(dataModelConfig, analysis.format)
123
- ? expandToHierarchical(blocksData)
124
- : blocksData;
143
+ ? expandToHierarchical(sourceBlocks)
144
+ : sourceBlocks;
125
145
 
126
146
  // Recover migrated cells whose text a pre-fix save detached to root:
127
147
  // re-attach `cell-<row>-<col>`-id orphans back into their empty cell.
@@ -240,6 +260,14 @@ export class Renderer extends Module {
240
260
  BlockManager.insertMany(blocks);
241
261
  migrateMarkColors(this.Blok.UI.nodes.redactor);
242
262
 
263
+ // Apply the editor's `link` config (target / rel / transformHref) to every
264
+ // anchor coming from stored block HTML, mirroring the interactive Link
265
+ // inline tool. Without this, link config only governs links the user
266
+ // creates by hand — anchors from saved articles keep their stored attrs.
267
+ if (this.config.link !== undefined) {
268
+ applyLinkConfig(this.Blok.UI.nodes.redactor, this.config.link);
269
+ }
270
+
243
271
  return blocks.length;
244
272
  }
245
273
 
@@ -1,4 +1,5 @@
1
1
  import type { MenuConfigItem } from '../../../../types/tools';
2
+ import type { BlockTuneRenderContext } from '../../../../types/block-tunes/block-tune';
2
3
  import { Module } from '../../__module';
3
4
  import type { Block } from '../../block';
4
5
  import { BlockAPI } from '../../block/api';
@@ -194,8 +195,19 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
194
195
  this.Blok.BlockSelection.clearCache();
195
196
  }
196
197
 
198
+ /**
199
+ * Expose the (not-yet-created) tune popover element to custom tunes via
200
+ * their render context. The popover is built further down, so the element
201
+ * is filled in after construction; tunes capturing the context resolve it
202
+ * lazily (e.g. when opening a sub-menu) instead of reaching into the DOM.
203
+ */
204
+ const popoverRef: { current: HTMLElement | null } = { current: null };
205
+ const renderContext: BlockTuneRenderContext = {
206
+ getPopoverElement: () => popoverRef.current,
207
+ };
208
+
197
209
  /** Get tool-specific tunes and common tunes (delete, move, etc.) */
198
- const { toolTunes, commonTunes } = block.getTunes();
210
+ const { toolTunes, commonTunes } = block.getTunes(renderContext);
199
211
 
200
212
  const items = await this.getTunesItems(block, commonTunes, toolTunes);
201
213
 
@@ -235,7 +247,8 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
235
247
  }
236
248
 
237
249
  this.popover = new PopoverClass(popoverParams);
238
- this.popover.getElement().setAttribute('data-blok-testid', 'block-tunes-popover');
250
+ popoverRef.current = this.popover.getElement();
251
+ popoverRef.current.setAttribute('data-blok-testid', 'block-tunes-popover');
239
252
 
240
253
  this.popover.on(PopoverEvent.Closed, this.onPopoverClose);
241
254
 
@@ -0,0 +1,40 @@
1
+ import type { BlokConfig } from '../../../types';
2
+
3
+ /**
4
+ * Anchor-building configuration shared by the interactive Link inline tool,
5
+ * the render pipeline and the paste pipeline.
6
+ */
7
+ type LinkConfig = NonNullable<BlokConfig['link']>;
8
+
9
+ /**
10
+ * Applies the editor's `link` config to every anchor inside `root`.
11
+ *
12
+ * Mirrors the behaviour of the interactive Link inline tool so that anchors
13
+ * coming from stored block HTML (render) or pasted markup (paste) receive the
14
+ * same treatment as anchors the user creates by hand:
15
+ *
16
+ * - `target` is forced (default `_blank`),
17
+ * - `rel` is forced (default `nofollow`),
18
+ * - `transformHref` rewrites the existing href when provided.
19
+ *
20
+ * @param root - element (or fragment) whose descendant anchors should be processed
21
+ * @param link - the resolved `link` config from the editor configuration
22
+ */
23
+ export function applyLinkConfig(root: ParentNode, link: LinkConfig): void {
24
+ const target = link.target ?? '_blank';
25
+ const rel = link.rel ?? 'nofollow';
26
+ const { transformHref } = link;
27
+
28
+ root.querySelectorAll('a').forEach((anchor) => {
29
+ if (transformHref !== undefined) {
30
+ const href = anchor.getAttribute('href');
31
+
32
+ if (href !== null) {
33
+ anchor.setAttribute('href', transformHref(href));
34
+ }
35
+ }
36
+
37
+ anchor.setAttribute('target', target);
38
+ anchor.setAttribute('rel', rel);
39
+ });
40
+ }
@@ -24,9 +24,17 @@ import type { UseBlokConfig } from './types';
24
24
  * mount, changing it to new *content* re-renders the editor via `render()` — no
25
25
  * recreation. Updates are deep-equal–deduped (a new reference with identical
26
26
  * content is a no-op, so it won't clobber the caret) and serialized (rapid
27
- * changes can't overlap). Read content via `onChange` or the ref
28
- * (`ref.current.save()`). For ad-hoc reloads you can still call
27
+ * changes can't overlap). For ad-hoc reloads you can still call
29
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.
30
38
  */
31
39
  export interface BlokEditorProps
32
40
  extends Omit<UseBlokConfig, 'onReady'>,
@@ -30,6 +30,9 @@ export const USE_BLOK_CONFIG_KEYS = [
30
30
  'onBeforePaste',
31
31
  'onReady',
32
32
  'onChange',
33
+ 'onSave',
34
+ 'onBeforeRender',
35
+ 'onAfterRender',
33
36
  'inlineToolbar',
34
37
  'tunes',
35
38
  'style',
@@ -13,6 +13,10 @@ import type { BlokConfig, Blok, EditorWidth } from '@/types';
13
13
  * - `placeholder` — calls `editor.placeholder.set(value)`
14
14
  * - `data` — re-renders via `editor.render(value)` when content changes
15
15
  * (deep-equal–deduped and serialized; seeds the initial content at creation)
16
+ *
17
+ * The `onSave` config (inherited from `BlokConfig`) is the controlled output
18
+ * half: it fires with the full serialized `OutputData` on every content change,
19
+ * so `data` + `onSave` form a controlled component without manual `save()` calls.
16
20
  */
17
21
  export interface UseBlokConfig extends Omit<BlokConfig, 'holder'> {
18
22
  /** Editor content width mode. Synced reactively after mount via `editor.width.set()`. */