@jackuait/blok 0.10.0-beta.9 → 0.10.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.
Files changed (269) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-DDu252IK.mjs → blok-u_68bnlk.mjs} +1617 -1562
  3. package/dist/chunks/{constants-DMW9a31I.mjs → constants-VDhCUk4c.mjs} +56 -48
  4. package/dist/chunks/{i18next-loader-CwsYu0n6.mjs → i18next-loader-CDnSPae_.mjs} +1 -1
  5. package/dist/chunks/{lightweight-i18n-Cvv8CWh4.mjs → lightweight-i18n-DZmo8dAI.mjs} +1 -0
  6. package/dist/chunks/{messages-DG-4DPmP.mjs → messages-1_6UkKLS.mjs} +1 -0
  7. package/dist/{messages-CqXtJTpU.mjs → chunks/messages-4Ck88DYZ2.mjs} +1 -0
  8. package/dist/chunks/{messages-DGL1ySqb2.mjs → messages-8Ld7P_9j2.mjs} +1 -0
  9. package/dist/{messages-DLX_iBDJ.mjs → chunks/messages-BAlZjPcl.mjs} +1 -0
  10. package/dist/chunks/{messages-p1mbe__S.mjs → messages-BHMiK51R.mjs} +1 -0
  11. package/dist/chunks/{messages-Cdf0W9H02.mjs → messages-BJ-vT1SU2.mjs} +1 -0
  12. package/dist/{messages-Smt4GBbj.mjs → chunks/messages-BK8Cp2d0.mjs} +1 -0
  13. package/dist/{messages-Ci0KqX-J.mjs → chunks/messages-BKN3YVIj.mjs} +1 -0
  14. package/dist/chunks/{messages-BXM80fdr2.mjs → messages-BMD37y3q2.mjs} +1 -0
  15. package/dist/{messages-B19o-Teb.mjs → chunks/messages-BONyZroH.mjs} +1 -0
  16. package/dist/{messages-BwHs4cm1.mjs → chunks/messages-BRAoJpOu.mjs} +1 -0
  17. package/dist/{messages-DY4IqlhY.mjs → chunks/messages-BRoa9tGl.mjs} +1 -0
  18. package/dist/chunks/{messages-RInp1ytX.mjs → messages-BbEW9bQz.mjs} +1 -0
  19. package/dist/{messages-BIHc0KHY.mjs → chunks/messages-BeGZqQwz.mjs} +1 -0
  20. package/dist/{messages-CmB406HW.mjs → chunks/messages-BfAcUavP.mjs} +1 -0
  21. package/dist/chunks/{messages-Cu-Wevxs2.mjs → messages-BgM91Lxm2.mjs} +1 -0
  22. package/dist/{messages-BYNcD6uR.mjs → chunks/messages-BlxwW7M6.mjs} +1 -0
  23. package/dist/chunks/{messages-rCd0Rrw6.mjs → messages-Bz0-KNEB.mjs} +1 -0
  24. package/dist/{messages-7QD-X6XT2.mjs → chunks/messages-C0IFfhnp.mjs} +1 -0
  25. package/dist/{messages-Dl5Y2-Ia.mjs → chunks/messages-C15z2r5U.mjs} +1 -0
  26. package/dist/chunks/{messages-MxpWO1db.mjs → messages-C1S9ztpF.mjs} +1 -0
  27. package/dist/chunks/{messages-8IHf7ZP3.mjs → messages-CC_noR8y.mjs} +1 -0
  28. package/dist/chunks/{messages-COO5xmcA.mjs → messages-CD_MnBln.mjs} +1 -0
  29. package/dist/chunks/{messages-BYlSMRkd.mjs → messages-CIfUm1Oa.mjs} +1 -0
  30. package/dist/{messages-BbYq1pk-.mjs → chunks/messages-CPBN4zWc.mjs} +1 -0
  31. package/dist/{messages-DPA-mMWC2.mjs → chunks/messages-CQBo3lmL2.mjs} +1 -0
  32. package/dist/{messages-DnGJD4TL.mjs → chunks/messages-CRF7nNrO.mjs} +1 -0
  33. package/dist/{messages-D8FQWulF2.mjs → chunks/messages-CTCe595D2.mjs} +1 -0
  34. package/dist/{messages-BRZX964b2.mjs → chunks/messages-CW35K1pq.mjs} +1 -0
  35. package/dist/{messages-DnG0ef8t2.mjs → chunks/messages-CZSlfnkO2.mjs} +1 -0
  36. package/dist/chunks/{messages-iS34FHFB.mjs → messages-ChK7v1PV.mjs} +1 -0
  37. package/dist/{messages-BiUGXvYr2.mjs → chunks/messages-Clku7Cf-2.mjs} +1 -0
  38. package/dist/{messages-DIJlIqlQ2.mjs → chunks/messages-CszmHAvQ.mjs} +1 -0
  39. package/dist/chunks/{messages-DWu1r4gc2.mjs → messages-CvANwuht2.mjs} +1 -0
  40. package/dist/{messages-nUVjeh7K.mjs → chunks/messages-CxiURE2X.mjs} +1 -0
  41. package/dist/chunks/{messages-A_MkXDlG.mjs → messages-CxxyR4vY.mjs} +1 -0
  42. package/dist/{messages-ynAe7ewZ.mjs → chunks/messages-D22e9h7V2.mjs} +1 -0
  43. package/dist/{messages-DYTTu0O12.mjs → chunks/messages-D7dx_6k8.mjs} +1 -0
  44. package/dist/chunks/{messages-BA8Iv99Y2.mjs → messages-DBMaLL8b2.mjs} +1 -0
  45. package/dist/{messages-DbySKTKt2.mjs → chunks/messages-DB_-5Xln.mjs} +1 -0
  46. package/dist/{messages-CcF4y-E4.mjs → chunks/messages-DEBy3nuJ2.mjs} +1 -0
  47. package/dist/chunks/{messages-NEqrrYvE2.mjs → messages-DMoERagV2.mjs} +1 -0
  48. package/dist/chunks/{messages-Bxvi1ebN.mjs → messages-DPzHD51Y.mjs} +1 -0
  49. package/dist/chunks/{messages-jfVpL9c-2.mjs → messages-DSrdy9Nw2.mjs} +1 -0
  50. package/dist/chunks/{messages-Cmf6NhSC.mjs → messages-DTN1XGll.mjs} +1 -0
  51. package/dist/{messages-5jvKxQNu2.mjs → chunks/messages-DUeiPraX.mjs} +1 -0
  52. package/dist/chunks/{messages-G416eyjY.mjs → messages-DUr9WAkD.mjs} +1 -0
  53. package/dist/chunks/{messages-Ck81cQkn2.mjs → messages-DVr1sqfI2.mjs} +1 -0
  54. package/dist/{messages-BYmmMDrN2.mjs → chunks/messages-DjvaiALg2.mjs} +1 -0
  55. package/dist/chunks/{messages-D55HRx5O2.mjs → messages-DrfRYiM32.mjs} +1 -0
  56. package/dist/chunks/{messages-B2N4fUi72.mjs → messages-DtoId_bw2.mjs} +1 -0
  57. package/dist/{messages-Bq3F2Tp_.mjs → chunks/messages-Du2BffA7.mjs} +1 -0
  58. package/dist/{messages-CjbnogEC.mjs → chunks/messages-DxHh0O8j2.mjs} +1 -0
  59. package/dist/{messages-BECMxmfX.mjs → chunks/messages-EDMC5ukV.mjs} +1 -0
  60. package/dist/{messages-BTQPpoM42.mjs → chunks/messages-ElIGUi0O2.mjs} +1 -0
  61. package/dist/chunks/{messages-BhzzNkN-.mjs → messages-JSQjKQ8I.mjs} +1 -0
  62. package/dist/{messages-CWIXvnDf2.mjs → chunks/messages-Q7-4ZJLB2.mjs} +1 -0
  63. package/dist/chunks/{messages-DOuS1Qge.mjs → messages-QMOmwcZb.mjs} +1 -0
  64. package/dist/{messages-hWwSRF-2.mjs → chunks/messages-QilfinOn2.mjs} +1 -0
  65. package/dist/{messages-BmAn22OX.mjs → chunks/messages-a07QVz8U.mjs} +1 -0
  66. package/dist/chunks/{messages-BYxLFj7y.mjs → messages-eFd4YYzt.mjs} +1 -0
  67. package/dist/chunks/{messages-BSghd0ez.mjs → messages-euM2m3wQ.mjs} +1 -0
  68. package/dist/chunks/{messages-BVjoM7P0.mjs → messages-kGmxkeFH.mjs} +1 -0
  69. package/dist/{messages-DMr62KiO2.mjs → chunks/messages-oMc7qugU2.mjs} +1 -0
  70. package/dist/chunks/{messages-DzTk8bJ5.mjs → messages-sDdNf8O9.mjs} +1 -0
  71. package/dist/{messages-Bm0Feca1.mjs → chunks/messages-wl8YrvGG.mjs} +1 -0
  72. package/dist/chunks/{messages-BAsb5CgZ.mjs → messages-zt6zdYWh.mjs} +1 -0
  73. package/dist/chunks/{tools-XmzH2rgQ.mjs → tools-1ZFajlGN.mjs} +1619 -1307
  74. package/dist/full.mjs +3 -3
  75. package/dist/locales.mjs +68 -67
  76. package/dist/{messages-F2xRoY1w.mjs → messages-2ZWBTerL.mjs} +1 -0
  77. package/dist/{messages-Dl3Sv6Rq2.mjs → messages-53w0fPZS2.mjs} +1 -0
  78. package/dist/{chunks/messages-BDZA10kl2.mjs → messages-98nQiC7t2.mjs} +1 -0
  79. package/dist/{chunks/messages-JyvWu4rf2.mjs → messages-A96tMxeU.mjs} +1 -0
  80. package/dist/{messages-Ce6KVEbT.mjs → messages-BE_z-zrb.mjs} +1 -0
  81. package/dist/{chunks/messages-CSJ_zb3a2.mjs → messages-BK_LsgY4.mjs} +1 -0
  82. package/dist/{messages-CJTy6JZt.mjs → messages-BbJ7ZXY8.mjs} +1 -0
  83. package/dist/{chunks/messages-DMVXnAYj.mjs → messages-BcVB3osF.mjs} +1 -0
  84. package/dist/{chunks/messages-CSL-6xfb2.mjs → messages-BckDk9aq2.mjs} +1 -0
  85. package/dist/{chunks/messages-C0HvoMPb.mjs → messages-Be_2RHZD.mjs} +1 -0
  86. package/dist/{chunks/messages-Dr0Ekmbz.mjs → messages-BesJaI6A.mjs} +1 -0
  87. package/dist/{chunks/messages-D3zojZ94.mjs → messages-BiTMwiKH.mjs} +1 -0
  88. package/dist/{messages-B1ZUQagA.mjs → messages-BmH2cQHQ.mjs} +1 -0
  89. package/dist/{chunks/messages-Bfnq1xv4.mjs → messages-BrOWqNCu2.mjs} +1 -0
  90. package/dist/{messages-Dnp9N6RU2.mjs → messages-Brd5R-da2.mjs} +1 -0
  91. package/dist/{chunks/messages-DJoNVjqP.mjs → messages-C0GSBBCo2.mjs} +1 -0
  92. package/dist/{messages-Dw__BcTj.mjs → messages-C1vc5584.mjs} +1 -0
  93. package/dist/{messages-aMXpHt5X2.mjs → messages-C6ONf71u2.mjs} +1 -0
  94. package/dist/{chunks/messages-BeFqtIrc2.mjs → messages-C7lJg8fy2.mjs} +1 -0
  95. package/dist/{messages-CSUHBs4c2.mjs → messages-CRNogopy2.mjs} +1 -0
  96. package/dist/{messages-DLlc9QPw.mjs → messages-CT-Kdas6.mjs} +1 -0
  97. package/dist/{chunks/messages-DlLXpgWM2.mjs → messages-CTTmWn4Y2.mjs} +1 -0
  98. package/dist/{messages-D0aw5_0k2.mjs → messages-CZbcxlZt2.mjs} +1 -0
  99. package/dist/{chunks/messages-Bp8qin1R.mjs → messages-C_Qn9SbQ.mjs} +1 -0
  100. package/dist/{messages-96iaAUds2.mjs → messages-CdEASHDp2.mjs} +1 -0
  101. package/dist/{messages-Dy-Y_nEI.mjs → messages-CdduYw-q.mjs} +1 -0
  102. package/dist/{chunks/messages-Je5YvxiY.mjs → messages-Che99vKP.mjs} +1 -0
  103. package/dist/{messages-nlhESX9t.mjs → messages-CisR4PNV.mjs} +1 -0
  104. package/dist/{chunks/messages-BE6lHKwf.mjs → messages-ClGvlFcH2.mjs} +1 -0
  105. package/dist/{chunks/messages-FWfsxpBz.mjs → messages-CnuH-BZK2.mjs} +1 -0
  106. package/dist/{chunks/messages-aZcy0JQq2.mjs → messages-D0005ti32.mjs} +1 -0
  107. package/dist/{messages-B7ieAJBd2.mjs → messages-D05jqBIa2.mjs} +1 -0
  108. package/dist/{messages-DTh9a8mR.mjs → messages-D0lLw9KM.mjs} +1 -0
  109. package/dist/{chunks/messages-ihCjSFJI2.mjs → messages-D3rwCtKn.mjs} +1 -0
  110. package/dist/{chunks/messages-xuqyb6Ff2.mjs → messages-D6VIFnSW.mjs} +1 -0
  111. package/dist/{chunks/messages-KdawW5Na.mjs → messages-D81w6AmW.mjs} +1 -0
  112. package/dist/{chunks/messages-BUVhHx0q2.mjs → messages-DBhvm8NK.mjs} +1 -0
  113. package/dist/{chunks/messages-C7VGpihw.mjs → messages-DK6dA0O2.mjs} +1 -0
  114. package/dist/{messages-rk-A1Wa42.mjs → messages-DKHbt-7l2.mjs} +1 -0
  115. package/dist/{messages-BIoeoik5.mjs → messages-DM4Gjc9h.mjs} +1 -0
  116. package/dist/{chunks/messages-B9kmbUWV.mjs → messages-DODrhcop.mjs} +1 -0
  117. package/dist/{messages-BsycN_JI2.mjs → messages-DOGbHYv-2.mjs} +1 -0
  118. package/dist/{chunks/messages-BQYvBqm2.mjs → messages-DQORja0D.mjs} +1 -0
  119. package/dist/{chunks/messages-CVdpweyf2.mjs → messages-DSmxJWju2.mjs} +1 -0
  120. package/dist/{messages-CR_L_UtK.mjs → messages-DVL0KZE5.mjs} +1 -0
  121. package/dist/{chunks/messages-Cs81Z_Bh.mjs → messages-DYuD5-rO.mjs} +1 -0
  122. package/dist/{chunks/messages-CKBhDGI3.mjs → messages-Ddq3Ce3E2.mjs} +1 -0
  123. package/dist/{messages-xh2eOLvs.mjs → messages-DfFZ6Yj5.mjs} +1 -0
  124. package/dist/{chunks/messages-C6Mpiacw.mjs → messages-Dnd5YSWv.mjs} +1 -0
  125. package/dist/{messages-BJeGJksD.mjs → messages-Do7Xjy0n.mjs} +1 -0
  126. package/dist/{chunks/messages-C3aX3q0H.mjs → messages-DopaMHC42.mjs} +1 -0
  127. package/dist/{messages-dv19AkyJ.mjs → messages-DpJGbx3q.mjs} +1 -0
  128. package/dist/{messages-DBiVgUs2.mjs → messages-DpwMKDV0.mjs} +1 -0
  129. package/dist/{messages-j7o5rT9s.mjs → messages-Dqu4aX9s.mjs} +1 -0
  130. package/dist/{chunks/messages-BjadX8jR2.mjs → messages-E8NjqzWq2.mjs} +1 -0
  131. package/dist/{messages-aWZH50vu2.mjs → messages-JNrYldAa2.mjs} +1 -0
  132. package/dist/{chunks/messages-B4UMuyjT.mjs → messages-LMaR2_bE.mjs} +1 -0
  133. package/dist/{messages-E_ZuzGDt.mjs → messages-LYJbLq_F.mjs} +1 -0
  134. package/dist/{chunks/messages-D9N2MvQx2.mjs → messages-Q5sQeVap2.mjs} +1 -0
  135. package/dist/{chunks/messages-Bphq_Bt3.mjs → messages-Xc0KUbYl.mjs} +1 -0
  136. package/dist/{chunks/messages-DlonA3wa.mjs → messages-_PLyRfVw.mjs} +1 -0
  137. package/dist/{chunks/messages-TRUuyiFB.mjs → messages-apA6BStA.mjs} +1 -0
  138. package/dist/{chunks/messages-B0vPBsWq.mjs → messages-bkGniiaz.mjs} +1 -0
  139. package/dist/{messages-DkLU_rWm.mjs → messages-neGD3WGq.mjs} +1 -0
  140. package/dist/{messages-Ddnj2iTG2.mjs → messages-qbKjjvgd2.mjs} +1 -0
  141. package/dist/{messages-BiiongNz2.mjs → messages-qfvXgPpu2.mjs} +1 -0
  142. package/dist/{messages-Dvn35ksS.mjs → messages-uwK7ktqk.mjs} +1 -0
  143. package/dist/react.mjs +2 -2
  144. package/dist/tools.mjs +2 -2
  145. package/package.json +3 -5
  146. package/src/cli/commands/convert-gdocs/index.ts +26 -0
  147. package/src/cli/commands/convert-html/block-builder.ts +392 -0
  148. package/src/cli/commands/convert-html/id-generator.ts +11 -0
  149. package/src/cli/commands/convert-html/index.ts +23 -0
  150. package/src/cli/commands/convert-html/preprocessor.ts +422 -0
  151. package/src/cli/commands/convert-html/sanitizer.ts +93 -0
  152. package/src/cli/commands/convert-html/types.ts +15 -0
  153. package/src/cli/index.ts +56 -5
  154. package/src/components/block/index.ts +58 -10
  155. package/src/components/constants/data-attributes.ts +10 -0
  156. package/src/components/i18n/locales/am/messages.json +1 -0
  157. package/src/components/i18n/locales/ar/messages.json +1 -0
  158. package/src/components/i18n/locales/az/messages.json +1 -0
  159. package/src/components/i18n/locales/bg/messages.json +1 -0
  160. package/src/components/i18n/locales/bn/messages.json +1 -0
  161. package/src/components/i18n/locales/bs/messages.json +1 -0
  162. package/src/components/i18n/locales/cs/messages.json +1 -0
  163. package/src/components/i18n/locales/da/messages.json +1 -0
  164. package/src/components/i18n/locales/de/messages.json +1 -0
  165. package/src/components/i18n/locales/dv/messages.json +1 -0
  166. package/src/components/i18n/locales/el/messages.json +1 -0
  167. package/src/components/i18n/locales/en/messages.json +1 -0
  168. package/src/components/i18n/locales/es/messages.json +1 -0
  169. package/src/components/i18n/locales/et/messages.json +1 -0
  170. package/src/components/i18n/locales/fa/messages.json +1 -0
  171. package/src/components/i18n/locales/fi/messages.json +1 -0
  172. package/src/components/i18n/locales/fil/messages.json +1 -0
  173. package/src/components/i18n/locales/fr/messages.json +1 -0
  174. package/src/components/i18n/locales/gu/messages.json +1 -0
  175. package/src/components/i18n/locales/he/messages.json +1 -0
  176. package/src/components/i18n/locales/hi/messages.json +1 -0
  177. package/src/components/i18n/locales/hr/messages.json +1 -0
  178. package/src/components/i18n/locales/hu/messages.json +1 -0
  179. package/src/components/i18n/locales/hy/messages.json +1 -0
  180. package/src/components/i18n/locales/id/messages.json +1 -0
  181. package/src/components/i18n/locales/it/messages.json +1 -0
  182. package/src/components/i18n/locales/ja/messages.json +1 -0
  183. package/src/components/i18n/locales/ka/messages.json +1 -0
  184. package/src/components/i18n/locales/km/messages.json +1 -0
  185. package/src/components/i18n/locales/kn/messages.json +1 -0
  186. package/src/components/i18n/locales/ko/messages.json +1 -0
  187. package/src/components/i18n/locales/ku/messages.json +1 -0
  188. package/src/components/i18n/locales/lo/messages.json +1 -0
  189. package/src/components/i18n/locales/lt/messages.json +1 -0
  190. package/src/components/i18n/locales/lv/messages.json +1 -0
  191. package/src/components/i18n/locales/mk/messages.json +1 -0
  192. package/src/components/i18n/locales/ml/messages.json +1 -0
  193. package/src/components/i18n/locales/mn/messages.json +1 -0
  194. package/src/components/i18n/locales/mr/messages.json +1 -0
  195. package/src/components/i18n/locales/ms/messages.json +1 -0
  196. package/src/components/i18n/locales/my/messages.json +1 -0
  197. package/src/components/i18n/locales/ne/messages.json +1 -0
  198. package/src/components/i18n/locales/nl/messages.json +1 -0
  199. package/src/components/i18n/locales/no/messages.json +1 -0
  200. package/src/components/i18n/locales/pa/messages.json +1 -0
  201. package/src/components/i18n/locales/pl/messages.json +1 -0
  202. package/src/components/i18n/locales/ps/messages.json +1 -0
  203. package/src/components/i18n/locales/pt/messages.json +1 -0
  204. package/src/components/i18n/locales/ro/messages.json +1 -0
  205. package/src/components/i18n/locales/ru/messages.json +1 -0
  206. package/src/components/i18n/locales/sd/messages.json +1 -0
  207. package/src/components/i18n/locales/si/messages.json +1 -0
  208. package/src/components/i18n/locales/sk/messages.json +1 -0
  209. package/src/components/i18n/locales/sl/messages.json +1 -0
  210. package/src/components/i18n/locales/sq/messages.json +1 -0
  211. package/src/components/i18n/locales/sr/messages.json +1 -0
  212. package/src/components/i18n/locales/sv/messages.json +1 -0
  213. package/src/components/i18n/locales/sw/messages.json +1 -0
  214. package/src/components/i18n/locales/ta/messages.json +1 -0
  215. package/src/components/i18n/locales/te/messages.json +1 -0
  216. package/src/components/i18n/locales/th/messages.json +1 -0
  217. package/src/components/i18n/locales/tr/messages.json +1 -0
  218. package/src/components/i18n/locales/ug/messages.json +1 -0
  219. package/src/components/i18n/locales/uk/messages.json +1 -0
  220. package/src/components/i18n/locales/ur/messages.json +1 -0
  221. package/src/components/i18n/locales/vi/messages.json +1 -0
  222. package/src/components/i18n/locales/yi/messages.json +1 -0
  223. package/src/components/i18n/locales/zh/messages.json +1 -0
  224. package/src/components/icons/index.ts +29 -18
  225. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +18 -0
  226. package/src/components/modules/blockManager/hierarchy.ts +4 -1
  227. package/src/components/modules/readonly.ts +46 -0
  228. package/src/components/modules/rectangleSelection.ts +25 -5
  229. package/src/components/modules/toolbar/index.ts +96 -19
  230. package/src/components/modules/toolbar/positioning.ts +11 -2
  231. package/src/components/modules/toolbar/styles.ts +0 -2
  232. package/src/components/modules/uiControllers/controllers/blockHover.ts +44 -1
  233. package/src/components/tools/block.ts +10 -0
  234. package/src/components/utils/placeholder.ts +9 -2
  235. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +11 -0
  236. package/src/components/utils/popover/popover-abstract.ts +7 -0
  237. package/src/styles/main.css +16 -0
  238. package/src/tools/callout/constants.ts +2 -1
  239. package/src/tools/callout/dom-builder.ts +13 -1
  240. package/src/tools/callout/index.ts +21 -7
  241. package/src/tools/code/constants.ts +28 -8
  242. package/src/tools/code/dom-builder.ts +133 -64
  243. package/src/tools/code/index.ts +280 -91
  244. package/src/tools/code/language-detector.ts +118 -0
  245. package/src/tools/divider/index.ts +5 -0
  246. package/src/tools/header/index.ts +47 -1
  247. package/src/tools/list/dom-builder.ts +3 -1
  248. package/src/tools/list/index.ts +55 -3
  249. package/src/tools/list/list-helpers.ts +2 -2
  250. package/src/tools/nested-blocks.ts +25 -0
  251. package/src/tools/paragraph/index.ts +47 -6
  252. package/src/tools/quote/index.ts +43 -8
  253. package/src/tools/stub/index.ts +10 -0
  254. package/src/tools/table/index.ts +238 -6
  255. package/src/tools/table/table-add-controls.ts +37 -5
  256. package/src/tools/table/table-cell-blocks.ts +57 -18
  257. package/src/tools/table/table-core.ts +2 -0
  258. package/src/tools/table/table-corner-drag.ts +247 -0
  259. package/src/tools/table/table-operations.ts +41 -14
  260. package/src/tools/toggle/dom-builder.ts +1 -0
  261. package/src/tools/toggle/index.ts +25 -0
  262. package/src/tools/toggle/toggle-lifecycle.ts +5 -4
  263. package/src/types-internal/jsdom.d.ts +9 -0
  264. package/types/tools/adapters/block-tool-adapter.d.ts +6 -0
  265. package/types/tools/block-tool.d.ts +20 -0
  266. package/types/utils/popover/popover-item.d.ts +6 -0
  267. package/bin/blok.mjs +0 -10
  268. package/dist/cli.mjs +0 -37
  269. package/src/tools/code/language-picker.ts +0 -241
@@ -54,8 +54,10 @@ import type { PendingHighlight } from './table-row-col-action-handler';
54
54
  import { TableRowColControls } from './table-row-col-controls';
55
55
  import type { RowColAction } from './table-row-col-controls';
56
56
  import { registerAdditionalRestrictedTools } from './table-restrictions';
57
+ import { TableCornerDrag } from './table-corner-drag';
57
58
  import { TableScrollHaze } from './table-scroll-haze';
58
59
  import type { CellPlacement, ClipboardBlockData, LegacyCellContent, TableCellsClipboard, TableData, TableConfig } from './types';
60
+ import { isCellWithBlocks } from './types';
59
61
 
60
62
  const DEFAULT_ROWS = 3;
61
63
  const DEFAULT_COLS = 3;
@@ -93,6 +95,7 @@ export class Table implements BlockTool {
93
95
  private rowColControls: TableRowColControls | null = null;
94
96
  private cellBlocks: TableCellBlocks | null = null;
95
97
  private cellSelection: TableCellSelection | null = null;
98
+ private cornerDrag: TableCornerDrag | null = null;
96
99
  private scrollHaze: TableScrollHaze | null = null;
97
100
  private element: HTMLDivElement | null = null;
98
101
  private gridElement: HTMLElement | null = null;
@@ -102,6 +105,8 @@ export class Table implements BlockTool {
102
105
  private pendingHighlight: PendingHighlight | null = null;
103
106
  private isNewTable = false;
104
107
  private unregisterRestrictedTools: (() => void) | null = null;
108
+ private gridPasteCleanup: (() => void) | null = null;
109
+ private keyboardNavCleanup: (() => void) | null = null;
105
110
 
106
111
  /**
107
112
  * Generation counter for setData calls.
@@ -193,12 +198,18 @@ export class Table implements BlockTool {
193
198
  this.resize = null;
194
199
  this.addControls?.destroy();
195
200
  this.addControls = null;
201
+ this.cornerDrag?.destroy();
202
+ this.cornerDrag = null;
196
203
  this.rowColControls?.destroy();
197
204
  this.rowColControls = null;
198
205
  this.cellSelection?.destroy();
199
206
  this.cellSelection = null;
200
207
  this.scrollHaze?.destroy();
201
208
  this.scrollHaze = null;
209
+ this.gridPasteCleanup?.();
210
+ this.gridPasteCleanup = null;
211
+ this.keyboardNavCleanup?.();
212
+ this.keyboardNavCleanup = null;
202
213
  }
203
214
 
204
215
  /**
@@ -256,6 +267,8 @@ export class Table implements BlockTool {
256
267
  newTbody: Element,
257
268
  blockHolders: Map<string, HTMLElement>
258
269
  ): void {
270
+ const mounted = new Set<string>();
271
+
259
272
  content.forEach((rowData, r) => {
260
273
  rowData.forEach((cellContent, c) => {
261
274
  if (typeof cellContent === 'string') {
@@ -283,8 +296,9 @@ export class Table implements BlockTool {
283
296
  cellContent.blocks.forEach(blockId => {
284
297
  const holder = blockHolders.get(blockId);
285
298
 
286
- if (holder) {
299
+ if (holder && !mounted.has(blockId)) {
287
300
  container.appendChild(holder);
301
+ mounted.add(blockId);
288
302
  }
289
303
  });
290
304
  });
@@ -323,6 +337,7 @@ export class Table implements BlockTool {
323
337
  private initSubsystems(gridEl: HTMLElement): void {
324
338
  this.initResize(gridEl);
325
339
  this.initAddControls(gridEl);
340
+ this.initCornerDrag(gridEl);
326
341
  this.initRowColControls(gridEl);
327
342
  this.initCellSelection(gridEl);
328
343
  this.initGridPasteListener(gridEl);
@@ -482,7 +497,7 @@ export class Table implements BlockTool {
482
497
 
483
498
  if (!this.readOnly) {
484
499
  this.initCellBlocks(gridEl);
485
- setupKeyboardNavigation(gridEl, this.cellBlocks);
500
+ this.keyboardNavCleanup = setupKeyboardNavigation(gridEl, this.cellBlocks);
486
501
  }
487
502
 
488
503
  return wrapper;
@@ -558,6 +573,64 @@ export class Table implements BlockTool {
558
573
  }
559
574
  }
560
575
 
576
+ /**
577
+ * Toggle read-only mode in place without re-rendering.
578
+ * Entering readonly tears down all interactive subsystems and cell blocks;
579
+ * exiting readonly recreates them.
580
+ */
581
+ public setReadOnly(state: boolean): void {
582
+ const wrapper = this.element;
583
+ const gridEl = this.gridElement;
584
+
585
+ if (!wrapper || !gridEl) {
586
+ return;
587
+ }
588
+
589
+ this.readOnly = state;
590
+
591
+ if (state) {
592
+ // Entering readonly: tear down interactive subsystems
593
+ this.teardownSubsystems();
594
+ this.cellBlocks?.destroy();
595
+ this.cellBlocks = null;
596
+
597
+ // Remove grip overlay
598
+ if (this.gripOverlay) {
599
+ this.gripOverlay.remove();
600
+ this.gripOverlay = null;
601
+ }
602
+
603
+ // Update wrapper classes and attributes
604
+ WRAPPER_EDIT_CLASSES.forEach(cls => wrapper.classList.remove(cls));
605
+ wrapper.setAttribute('data-blok-table-readonly', '');
606
+
607
+ // Mount cell content as non-interactive
608
+ const snap = this.model.snapshot();
609
+
610
+ mountCellBlocksReadOnly(gridEl, snap.content, this.api, this.blockId ?? '');
611
+ } else {
612
+ // Exiting readonly: restore interactive subsystems
613
+ wrapper.removeAttribute('data-blok-table-readonly');
614
+ WRAPPER_EDIT_CLASSES.forEach(cls => wrapper.classList.add(cls));
615
+
616
+ // Create grip overlay
617
+ const overlay = document.createElement('div');
618
+
619
+ overlay.setAttribute('data-blok-table-grip-overlay', '');
620
+ overlay.style.position = 'absolute';
621
+ overlay.style.inset = '0';
622
+ overlay.style.pointerEvents = 'none';
623
+ overlay.style.zIndex = '3';
624
+ wrapper.appendChild(overlay);
625
+ this.gripOverlay = overlay;
626
+
627
+ // Initialize cell blocks and subsystems
628
+ this.initCellBlocks(gridEl);
629
+ this.keyboardNavCleanup = setupKeyboardNavigation(gridEl, this.cellBlocks);
630
+ this.initSubsystems(gridEl);
631
+ }
632
+ }
633
+
561
634
  /**
562
635
  * Remove blocks that claim this table as parent but are not referenced in any cell.
563
636
  *
@@ -595,7 +668,28 @@ export class Table implements BlockTool {
595
668
  }
596
669
 
597
670
  public save(_blockContent: HTMLElement): TableData {
598
- return this.model.snapshot();
671
+ const data = this.model.snapshot();
672
+
673
+ // Filter out block IDs that don't belong to this table.
674
+ // Corrupted data may contain cross-table references; persisting them
675
+ // causes DOM node stealing and data loss on subsequent renders.
676
+ data.content = data.content.map(row =>
677
+ row.map(cell => {
678
+ if (!isCellWithBlocks(cell)) {
679
+ return cell;
680
+ }
681
+
682
+ const filtered = cell.blocks.filter(blockId => {
683
+ const block = this.api.blocks.getById?.(blockId);
684
+
685
+ return !block || block.parentId === this.blockId;
686
+ });
687
+
688
+ return { ...cell, blocks: filtered };
689
+ })
690
+ );
691
+
692
+ return data;
599
693
  }
600
694
 
601
695
  public validate(savedData: TableData): boolean {
@@ -900,6 +994,10 @@ export class Table implements BlockTool {
900
994
  wrapper: this.element,
901
995
  grid: gridEl,
902
996
  i18n: this.api.i18n,
997
+ getTableSize: () => ({
998
+ rows: this.model.rows,
999
+ cols: this.model.cols,
1000
+ }),
903
1001
  getNewColumnWidth: () => {
904
1002
  const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
905
1003
 
@@ -1040,6 +1138,131 @@ export class Table implements BlockTool {
1040
1138
  }
1041
1139
  }
1042
1140
 
1141
+ private initCornerDrag(gridEl: HTMLElement): void {
1142
+ this.cornerDrag?.destroy();
1143
+
1144
+ if (!this.element) {
1145
+ return;
1146
+ }
1147
+
1148
+ this.cornerDrag = new TableCornerDrag({
1149
+ wrapper: this.element,
1150
+ gridEl,
1151
+ onAddRow: () => {
1152
+ this.runStructuralOp(() => {
1153
+ this.grid.addRow(gridEl);
1154
+ this.model.addRow();
1155
+ populateNewCells(gridEl, this.cellBlocks);
1156
+ updateHeadingStyles(this.gridElement, this.model.withHeadings);
1157
+ updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
1158
+ });
1159
+ },
1160
+ onAddColumn: () => {
1161
+ this.runStructuralOp(() => {
1162
+ const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
1163
+ const halfWidth = this.model.initialColWidth !== undefined
1164
+ ? Math.round((this.model.initialColWidth / 2) * 100) / 100
1165
+ : computeHalfAvgWidth(colWidths);
1166
+ const newWidths = [...colWidths, halfWidth];
1167
+
1168
+ this.grid.addColumn(gridEl, undefined, colWidths, halfWidth);
1169
+ this.model.addColumn(undefined, halfWidth);
1170
+ this.model.setColWidths(newWidths);
1171
+ applyPixelWidths(gridEl, newWidths);
1172
+ enableScrollOverflow(this.ensureScrollContainer());
1173
+ populateNewCells(gridEl, this.cellBlocks);
1174
+ updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
1175
+ });
1176
+ },
1177
+ onRemoveLastRow: () => {
1178
+ this.runStructuralOp(() => {
1179
+ const rowCount = this.grid.getRowCount(gridEl);
1180
+
1181
+ if (rowCount <= 1) {
1182
+ return;
1183
+ }
1184
+
1185
+ const { blocksToDelete } = this.model.deleteRow(rowCount - 1);
1186
+
1187
+ this.cellBlocks?.deleteBlocks(blocksToDelete);
1188
+ this.grid.deleteRow(gridEl, rowCount - 1);
1189
+ });
1190
+ },
1191
+ onRemoveLastColumn: () => {
1192
+ this.runStructuralOp(() => {
1193
+ const colCount = this.grid.getColumnCount(gridEl);
1194
+
1195
+ if (colCount <= 1) {
1196
+ return;
1197
+ }
1198
+
1199
+ const { blocksToDelete } = this.model.deleteColumn(colCount - 1);
1200
+
1201
+ this.cellBlocks?.deleteBlocks(blocksToDelete);
1202
+ this.grid.deleteColumn(gridEl, colCount - 1);
1203
+
1204
+ const updatedWidths = this.model.colWidths;
1205
+
1206
+ if (updatedWidths) {
1207
+ applyPixelWidths(gridEl, updatedWidths);
1208
+ }
1209
+ });
1210
+ },
1211
+ onDragStart: () => {
1212
+ if (this.resize) {
1213
+ this.resize.enabled = false;
1214
+ }
1215
+ this.rowColControls?.hideAllGrips();
1216
+ this.rowColControls?.setGripsDisplay(false);
1217
+ this.addControls?.setDisplay(false);
1218
+ },
1219
+ onDragEnd: () => {
1220
+ this.initResize(gridEl);
1221
+ this.rowColControls?.refresh();
1222
+ this.addControls?.setDisplay(true);
1223
+ this.addControls?.syncRowButtonWidth();
1224
+ },
1225
+ getTableSize: () => {
1226
+ return { rows: this.model.rows, cols: this.model.cols };
1227
+ },
1228
+ canRemoveLastRow: () => {
1229
+ return this.model.rows > 1 && isRowEmpty(gridEl, this.model.rows - 1);
1230
+ },
1231
+ canRemoveLastColumn: () => {
1232
+ return this.model.cols > 1 && isColumnEmpty(gridEl, this.model.cols - 1);
1233
+ },
1234
+ onClickAdd: () => {
1235
+ this.runTransactedStructuralOp(() => {
1236
+ // Add row
1237
+ this.grid.addRow(gridEl);
1238
+ this.model.addRow();
1239
+ populateNewCells(gridEl, this.cellBlocks);
1240
+ updateHeadingStyles(this.gridElement, this.model.withHeadings);
1241
+ updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
1242
+
1243
+ // Add column
1244
+ const colWidths = this.model.colWidths ?? readPixelWidths(gridEl);
1245
+ const halfWidth = this.model.initialColWidth !== undefined
1246
+ ? Math.round((this.model.initialColWidth / 2) * 100) / 100
1247
+ : computeHalfAvgWidth(colWidths);
1248
+ const newWidths = [...colWidths, halfWidth];
1249
+
1250
+ this.grid.addColumn(gridEl, undefined, colWidths, halfWidth);
1251
+ this.model.addColumn(undefined, halfWidth);
1252
+ this.model.setColWidths(newWidths);
1253
+ applyPixelWidths(gridEl, newWidths);
1254
+ populateNewCells(gridEl, this.cellBlocks);
1255
+ updateHeadingColumnStyles(this.gridElement, this.model.withHeadingColumn);
1256
+
1257
+ // Refresh subsystems
1258
+ this.initResize(gridEl);
1259
+ this.rowColControls?.refresh();
1260
+ this.addControls?.syncRowButtonWidth();
1261
+ });
1262
+ },
1263
+ });
1264
+ }
1265
+
1043
1266
  private initRowColControls(gridEl: HTMLElement): void {
1044
1267
  this.rowColControls?.destroy();
1045
1268
 
@@ -1063,6 +1286,7 @@ export class Table implements BlockTool {
1063
1286
  }
1064
1287
 
1065
1288
  this.addControls?.setDisplay(!isDragging);
1289
+ this.cornerDrag?.setDisplay(!isDragging);
1066
1290
 
1067
1291
  if (isDragging) {
1068
1292
  this.api.toolbar.close({ setExplicitlyClosed: false });
@@ -1442,6 +1666,7 @@ export class Table implements BlockTool {
1442
1666
  }
1443
1667
 
1444
1668
  this.addControls?.setInteractive(!hasSelection);
1669
+ this.cornerDrag?.setInteractive(!hasSelection);
1445
1670
  this.rowColControls?.setGripsDisplay(!hasSelection);
1446
1671
  },
1447
1672
  onSelectionRangeChange: () => {
@@ -1531,9 +1756,14 @@ export class Table implements BlockTool {
1531
1756
  }
1532
1757
 
1533
1758
  private initGridPasteListener(gridEl: HTMLElement): void {
1534
- gridEl.addEventListener('paste', (e: ClipboardEvent) => {
1759
+ const handler = (e: ClipboardEvent): void => {
1535
1760
  this.handleGridPaste(e, gridEl);
1536
- });
1761
+ };
1762
+
1763
+ gridEl.addEventListener('paste', handler);
1764
+ this.gridPasteCleanup = () => {
1765
+ gridEl.removeEventListener('paste', handler);
1766
+ };
1537
1767
  }
1538
1768
 
1539
1769
  private handleGridPaste(e: ClipboardEvent, gridEl: HTMLElement): void {
@@ -1633,7 +1863,9 @@ export class Table implements BlockTool {
1633
1863
 
1634
1864
  const range = selection.getRangeAt(0);
1635
1865
 
1636
- range.deleteContents();
1866
+ if (!range.collapsed) {
1867
+ range.deleteContents();
1868
+ }
1637
1869
 
1638
1870
  const fragment = document.createDocumentFragment();
1639
1871
  const wrapper = document.createElement('div');
@@ -1,7 +1,7 @@
1
1
  import type { I18n } from '../../../types/api';
2
2
  import { IconPlus } from '../../components/icons';
3
3
  import { createTooltipContent } from '../../components/modules/toolbar/tooltip';
4
- import { hide as hideTooltip, onHover } from '../../components/utils/tooltip';
4
+ import { hide as hideTooltip, onHover, show as showTooltip } from '../../components/utils/tooltip';
5
5
  import { twMerge } from '../../components/utils/tw';
6
6
 
7
7
  const ADD_ROW_ATTR = 'data-blok-table-add-row';
@@ -29,7 +29,7 @@ const VISUAL_CLASSES = [
29
29
  'justify-center',
30
30
  'border',
31
31
  'border-gray-300',
32
- 'rounded-full',
32
+ 'rounded-sm',
33
33
  'group-hover/add:bg-gray-50',
34
34
  ];
35
35
 
@@ -56,6 +56,7 @@ interface TableAddControlsOptions {
56
56
  onDragAddCol: () => void;
57
57
  onDragRemoveCol: () => void;
58
58
  onDragEnd: () => void;
59
+ getTableSize: () => { rows: number; cols: number };
59
60
  /** Returns the pixel width of a newly added column, used as the drag unit size. */
60
61
  getNewColumnWidth?: () => number;
61
62
  }
@@ -94,6 +95,7 @@ export class TableAddControls {
94
95
  private boundPointerCancel: (e: PointerEvent) => void;
95
96
  private boundRowPointerDown: (e: PointerEvent) => void;
96
97
  private boundColPointerDown: (e: PointerEvent) => void;
98
+ private getTableSize: () => { rows: number; cols: number };
97
99
  private getNewColumnWidth: (() => number) | undefined;
98
100
  private scrollContainer: HTMLElement | null = null;
99
101
  private boundScrollHandler: (() => void) | null = null;
@@ -112,6 +114,7 @@ export class TableAddControls {
112
114
  this.onDragAddCol = options.onDragAddCol;
113
115
  this.onDragRemoveCol = options.onDragRemoveCol;
114
116
  this.onDragEnd = options.onDragEnd;
117
+ this.getTableSize = options.getTableSize;
115
118
  this.getNewColumnWidth = options.getNewColumnWidth;
116
119
  this.boundMouseMove = this.handleMouseMove.bind(this);
117
120
  this.boundDocumentMouseMove = this.handleDocumentMouseMove.bind(this);
@@ -324,6 +327,20 @@ export class TableAddControls {
324
327
  this.addColBtn.remove();
325
328
  }
326
329
 
330
+ private showDimensionTooltip(): void {
331
+ if (!this.dragState) {
332
+ return;
333
+ }
334
+
335
+ const size = this.getTableSize();
336
+ const target = this.dragState.axis === 'row' ? this.addRowBtn : this.addColBtn;
337
+ const opts = this.dragState.axis === 'row'
338
+ ? { placement: 'bottom' as const, marginTop: -16 }
339
+ : { placement: 'bottom' as const };
340
+
341
+ showTooltip(target, `${size.cols}\u00D7${size.rows}`, opts);
342
+ }
343
+
327
344
  private handlePointerDown(axis: 'row' | 'col', e: PointerEvent): void {
328
345
  e.preventDefault();
329
346
 
@@ -380,8 +397,18 @@ export class TableAddControls {
380
397
  if (Math.abs(delta) > DRAG_THRESHOLD && !this.dragState.didDrag) {
381
398
  this.dragState.didDrag = true;
382
399
  document.body.style.cursor = axis === 'row' ? 'row-resize' : 'col-resize';
383
- hideTooltip();
400
+ this.showDimensionTooltip();
384
401
  this.onDragStart();
402
+
403
+ return;
404
+ }
405
+
406
+ if (this.dragState.didDrag) {
407
+ this.showDimensionTooltip();
408
+ }
409
+
410
+ if (this.dragState.didDrag) {
411
+ this.showDimensionTooltip();
385
412
  }
386
413
  }
387
414
 
@@ -400,6 +427,7 @@ export class TableAddControls {
400
427
  target.removeEventListener('pointercancel', this.boundPointerCancel);
401
428
 
402
429
  document.body.style.cursor = '';
430
+ hideTooltip();
403
431
  this.dragState = null;
404
432
 
405
433
  if (!didDrag) {
@@ -431,6 +459,7 @@ export class TableAddControls {
431
459
  target.removeEventListener('pointercancel', this.boundPointerCancel);
432
460
 
433
461
  document.body.style.cursor = '';
462
+ hideTooltip();
434
463
  this.dragState = null;
435
464
 
436
465
  if (didDrag) {
@@ -495,8 +524,8 @@ export class TableAddControls {
495
524
  * Document-level mousemove handler.
496
525
  * Catches mouse movements outside the wrapper (e.g. in the ::after
497
526
  * pseudo-element zone below the grid, which has pointer-events-none).
498
- * Only delegates to handleMouseMove when the cursor is within the
499
- * proximity zone around the grid to avoid unnecessary work.
527
+ * Delegates to handleMouseMove when the cursor is within the proximity
528
+ * zone around the grid; schedules hiding when the cursor is far away.
500
529
  */
501
530
  private handleDocumentMouseMove(e: MouseEvent): void {
502
531
  if (this.wrapper.contains(e.target as Node)) {
@@ -513,6 +542,9 @@ export class TableAddControls {
513
542
 
514
543
  if (nearGrid) {
515
544
  this.handleMouseMove(e);
545
+ } else {
546
+ this.scheduleHideRow();
547
+ this.scheduleHideCol();
516
548
  }
517
549
  }
518
550
 
@@ -1,4 +1,5 @@
1
1
  import type { API } from '../../../types';
2
+ import { DATA_ATTR } from '../../components/constants/data-attributes';
2
3
 
3
4
  import { CELL_ATTR, ROW_ATTR, CELL_COL_ATTR } from './table-core';
4
5
  import type { TableModel } from './table-model';
@@ -7,15 +8,6 @@ import { isCellWithBlocks } from './types';
7
8
 
8
9
  export const CELL_BLOCKS_ATTR = 'data-blok-table-cell-blocks';
9
10
 
10
- /**
11
- * Check if an element is inside a block-based table cell
12
- */
13
- export const isInCellBlock = (element: HTMLElement): boolean => {
14
- const cellBlocksContainer = element.closest(`[${CELL_BLOCKS_ATTR}]`);
15
-
16
- return cellBlocksContainer !== null;
17
- };
18
-
19
11
  /**
20
12
  * Get the cell element that contains the given element
21
13
  */
@@ -390,9 +382,9 @@ export class TableCellBlocks {
390
382
  const referencedBlockIds = isCellWithBlocks(cellContent) && cellContent.blocks.length > 0
391
383
  ? [...cellContent.blocks]
392
384
  : null;
393
- const mountedIds = referencedBlockIds
385
+ const { mountedIds, replacements } = referencedBlockIds
394
386
  ? this.mountBlocksInCell(container, referencedBlockIds)
395
- : [];
387
+ : { mountedIds: [] as string[], replacements: new Map<string, string>() };
396
388
 
397
389
  const cellColorProps: Pick<CellContent, 'color' | 'textColor'> = {};
398
390
 
@@ -406,7 +398,12 @@ export class TableCellBlocks {
406
398
  }
407
399
 
408
400
  if (mountedIds.length > 0) {
409
- normalizedRow.push({ blocks: referencedBlockIds ?? mountedIds, ...cellColorProps });
401
+ const baseIds = referencedBlockIds ?? mountedIds;
402
+ const blockIds = replacements.size > 0
403
+ ? baseIds.map(id => replacements.get(id) ?? id)
404
+ : baseIds;
405
+
406
+ normalizedRow.push({ blocks: blockIds, ...cellColorProps });
410
407
  } else {
411
408
  const text = typeof cellContent === 'string'
412
409
  ? cellContent
@@ -455,10 +452,15 @@ export class TableCellBlocks {
455
452
 
456
453
  /**
457
454
  * Mount existing blocks into a cell container by their IDs.
458
- * Returns the IDs of blocks that were successfully mounted.
455
+ * Returns the IDs of blocks that were successfully mounted and a map of
456
+ * original→duplicate IDs for blocks that were already in another cell.
459
457
  */
460
- private mountBlocksInCell(container: HTMLElement, blockIds: string[]): string[] {
458
+ private mountBlocksInCell(
459
+ container: HTMLElement,
460
+ blockIds: string[]
461
+ ): { mountedIds: string[]; replacements: Map<string, string> } {
461
462
  const mountedIds: string[] = [];
463
+ const replacements = new Map<string, string>();
462
464
 
463
465
  for (const blockId of blockIds) {
464
466
  const index = this.api.blocks.getBlockIndex(blockId);
@@ -473,11 +475,30 @@ export class TableCellBlocks {
473
475
  continue;
474
476
  }
475
477
 
478
+ // Guard: if the block is already mounted in another nested container
479
+ // (table cell, toggle, callout, header), create a duplicate with the
480
+ // same tool name and data rather than stealing the DOM node.
481
+ if (block.holder.closest(`[${DATA_ATTR.nestedBlocks}]`)) {
482
+ const duplicate = this.api.blocks.insert(
483
+ block.name,
484
+ block.preservedData,
485
+ {},
486
+ this.api.blocks.getBlocksCount(),
487
+ false
488
+ );
489
+
490
+ container.appendChild(duplicate.holder);
491
+ this.api.blocks.setBlockParent(duplicate.id, this.tableBlockId);
492
+ mountedIds.push(duplicate.id);
493
+ replacements.set(blockId, duplicate.id);
494
+ continue;
495
+ }
496
+
476
497
  container.appendChild(block.holder);
477
498
  this.api.blocks.setBlockParent(blockId, this.tableBlockId);
478
499
  mountedIds.push(blockId);
479
500
  }
480
- return mountedIds;
501
+ return { mountedIds, replacements };
481
502
  }
482
503
 
483
504
  /**
@@ -508,6 +529,12 @@ export class TableCellBlocks {
508
529
  return;
509
530
  }
510
531
 
532
+ // Guard: skip blocks already mounted in another nested container.
533
+ // Without this, insertBefore would steal the DOM node from the other container.
534
+ if (block.holder.closest(`[${DATA_ATTR.nestedBlocks}]`)) {
535
+ return;
536
+ }
537
+
511
538
  // Insert at the correct DOM position based on the flat array order,
512
539
  // so that pressing Enter on a non-last paragraph inserts the new block
513
540
  // right after the current one instead of always at the end of the cell.
@@ -1026,7 +1053,11 @@ export class TableCellBlocks {
1026
1053
  }
1027
1054
 
1028
1055
  /**
1029
- * Delete blocks by their IDs (in reverse index order to avoid shifting issues)
1056
+ * Delete blocks by their IDs (in reverse index order to avoid shifting issues).
1057
+ * Preserves scroll position because api.blocks.delete() is async — its internal
1058
+ * `await` defers Caret.setToBlock() to microtasks that run AFTER this method returns,
1059
+ * causing unwanted page jumps via element.focus() and window.scrollBy().
1060
+ * We use Promise.all().then() to schedule the scroll restore after all those microtasks.
1030
1061
  */
1031
1062
  public deleteBlocks(blockIds: string[]): void {
1032
1063
  const blockIndices = blockIds
@@ -1034,8 +1065,16 @@ export class TableCellBlocks {
1034
1065
  .filter((index): index is number => index !== undefined)
1035
1066
  .sort((a, b) => b - a);
1036
1067
 
1037
- blockIndices.forEach(index => {
1038
- void this.api.blocks.delete(index);
1068
+ const savedScrollY = window.scrollY;
1069
+
1070
+ const deletePromises = blockIndices.map(index => {
1071
+ return this.api.blocks.delete(index);
1072
+ });
1073
+
1074
+ void Promise.all(deletePromises).then(() => {
1075
+ if (window.scrollY !== savedScrollY) {
1076
+ window.scrollTo(0, savedScrollY);
1077
+ }
1039
1078
  });
1040
1079
  }
1041
1080
 
@@ -1,4 +1,5 @@
1
1
  import { twMerge } from '../../components/utils/tw';
2
+ import { DATA_ATTR } from '../../components/constants/data-attributes';
2
3
 
3
4
  import { CELL_BLOCKS_ATTR } from './table-cell-blocks';
4
5
  import type { TableModel } from './table-model';
@@ -631,6 +632,7 @@ export class TableGrid {
631
632
  const blocksContainer = document.createElement('div');
632
633
 
633
634
  blocksContainer.setAttribute(CELL_BLOCKS_ATTR, '');
635
+ blocksContainer.setAttribute(DATA_ATTR.nestedBlocks, '');
634
636
  blocksContainer.style.display = 'flex';
635
637
  blocksContainer.style.flexDirection = 'column';
636
638
  blocksContainer.style.minHeight = '100%';