@jackuait/blok 0.6.0-beta.9 → 0.7.0-beta.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 (336) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-Bn6Q_o8h.mjs → blok-ob9Fwr1L.mjs} +3414 -2975
  3. package/dist/chunks/i18next-B47TKgbU.mjs +1303 -0
  4. package/dist/chunks/{i18next-loader-DjR4d8M7.mjs → i18next-loader-Bu3vFvye.mjs} +2 -2
  5. package/dist/chunks/{index-oe38cp86.mjs → index-CZmRzRIX.mjs} +12 -12
  6. package/dist/chunks/{inline-tool-convert-SRTkyaZn.mjs → inline-tool-convert-CvFW2iie.mjs} +1579 -961
  7. package/dist/chunks/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
  8. package/dist/chunks/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
  9. package/dist/{messages-DnXLrlHh.mjs → chunks/messages-4v4MuVEc.mjs} +6 -0
  10. package/dist/chunks/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
  11. package/dist/chunks/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
  12. package/dist/chunks/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
  13. package/dist/{messages-uKX8WBaD.mjs → chunks/messages-96kNZDll.mjs} +6 -0
  14. package/dist/chunks/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
  15. package/dist/{messages-DBn76jVV.mjs → chunks/messages-B217znr-.mjs} +8 -2
  16. package/dist/{messages-DT4dP5uK.mjs → chunks/messages-B8WNljW3.mjs} +6 -0
  17. package/dist/chunks/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
  18. package/dist/{messages-CZygwLwM.mjs → chunks/messages-BI43k_BD.mjs} +6 -0
  19. package/dist/{messages-CzTufCHu.mjs → chunks/messages-BJ6zrz2j.mjs} +6 -0
  20. package/dist/{messages-BoJc_p1r.mjs → chunks/messages-BUl_Rcnj.mjs} +6 -0
  21. package/dist/chunks/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
  22. package/dist/{messages-C2htQ_3F.mjs → chunks/messages-BcpCubnC.mjs} +6 -0
  23. package/dist/{messages-D5C3J9qr.mjs → chunks/messages-Bm-E4iRC.mjs} +6 -0
  24. package/dist/chunks/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
  25. package/dist/chunks/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
  26. package/dist/chunks/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
  27. package/dist/{messages-Dz9L52ol.mjs → chunks/messages-CRJ_mchV.mjs} +6 -0
  28. package/dist/chunks/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
  29. package/dist/chunks/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
  30. package/dist/{messages-R3hUSvr3.mjs → chunks/messages-CfiyT2Wi.mjs} +6 -0
  31. package/dist/{messages-CJdUsQ-c.mjs → chunks/messages-CgTq3QhU.mjs} +6 -0
  32. package/dist/chunks/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
  33. package/dist/{messages-Q7AO_FLv.mjs → chunks/messages-Cjjo7yHR.mjs} +6 -0
  34. package/dist/{messages-C99mq906.mjs → chunks/messages-Cl6ayUaq.mjs} +6 -0
  35. package/dist/chunks/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
  36. package/dist/chunks/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
  37. package/dist/chunks/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
  38. package/dist/{messages-BWF-zUpY.mjs → chunks/messages-CrCYPCk3.mjs} +6 -0
  39. package/dist/{messages-D-ZtY5v0.mjs → chunks/messages-Cs8zmZ3L.mjs} +6 -0
  40. package/dist/{messages-DprmQg6V.mjs → chunks/messages-CzK0LEhb.mjs} +6 -0
  41. package/dist/chunks/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
  42. package/dist/chunks/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
  43. package/dist/chunks/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
  44. package/dist/{messages-kep5wtm4.mjs → chunks/messages-D4qqwVgQ.mjs} +6 -0
  45. package/dist/chunks/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
  46. package/dist/{messages-CY8_RyFE.mjs → chunks/messages-D7u2bmP2.mjs} +6 -0
  47. package/dist/chunks/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
  48. package/dist/{messages-DLfR5bMd.mjs → chunks/messages-D9ndgBnU.mjs} +6 -0
  49. package/dist/{messages-CVw84KdI.mjs → chunks/messages-DDTQgImT.mjs} +6 -0
  50. package/dist/{messages-_ErNTNhk.mjs → chunks/messages-DH_jBeED.mjs} +6 -0
  51. package/dist/chunks/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
  52. package/dist/chunks/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
  53. package/dist/chunks/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
  54. package/dist/chunks/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
  55. package/dist/chunks/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
  56. package/dist/chunks/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
  57. package/dist/{messages-Z9nEU2xK.mjs → chunks/messages-DviiFSv2.mjs} +6 -0
  58. package/dist/chunks/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
  59. package/dist/{messages-w7v1GNaE.mjs → chunks/messages-Dzzn6XoD.mjs} +6 -0
  60. package/dist/{messages-CqWJcCbY.mjs → chunks/messages-GSByFygY.mjs} +6 -0
  61. package/dist/chunks/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
  62. package/dist/chunks/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
  63. package/dist/{messages-BU2nlrLK.mjs → chunks/messages-RonBBCnh.mjs} +6 -0
  64. package/dist/{messages-Bmu_S7GM.mjs → chunks/messages-VDriF5Qy.mjs} +6 -0
  65. package/dist/{messages-CLhcMlTc.mjs → chunks/messages-ZjUAIWb1.mjs} +6 -0
  66. package/dist/{messages-9SihnaXQ.mjs → chunks/messages-b1EdvUm0.mjs} +6 -0
  67. package/dist/{messages-DvFLX36Q.mjs → chunks/messages-begYOTgC.mjs} +6 -0
  68. package/dist/{messages-BMv4xwIr.mjs → chunks/messages-jrncnb-H.mjs} +6 -0
  69. package/dist/{messages-D5iv1Kox.mjs → chunks/messages-nefz1S71.mjs} +6 -0
  70. package/dist/{messages-CQwpzUFp.mjs → chunks/messages-ucTVgS5G.mjs} +6 -0
  71. package/dist/chunks/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
  72. package/dist/{messages-C9eaarcK.mjs → chunks/messages-wmi-iFkH.mjs} +6 -0
  73. package/dist/chunks/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
  74. package/dist/full.mjs +30 -27
  75. package/dist/locales.mjs +90 -84
  76. package/dist/{messages-BogRq8lt.mjs → messages-0AbcLMLm.mjs} +6 -0
  77. package/dist/{messages-DJDG55Vq.mjs → messages-0E0AkrNu.mjs} +6 -0
  78. package/dist/{chunks/messages-DnXLrlHh.mjs → messages-4v4MuVEc.mjs} +6 -0
  79. package/dist/{messages-DnIhyAJk.mjs → messages-62v-CLC-.mjs} +6 -0
  80. package/dist/{messages-Dzwxv9v1.mjs → messages-8DeO60Oo.mjs} +6 -0
  81. package/dist/{messages-B1Aww8q7.mjs → messages-8IPXkrDl.mjs} +6 -0
  82. package/dist/{chunks/messages-uKX8WBaD.mjs → messages-96kNZDll.mjs} +6 -0
  83. package/dist/{messages-BL0tXcDf.mjs → messages-B1FZ8lxU.mjs} +6 -0
  84. package/dist/{chunks/messages-DBn76jVV.mjs → messages-B217znr-.mjs} +8 -2
  85. package/dist/{chunks/messages-DT4dP5uK.mjs → messages-B8WNljW3.mjs} +6 -0
  86. package/dist/{messages-BdeLo0N9.mjs → messages-BC8IN4Bf.mjs} +6 -0
  87. package/dist/{chunks/messages-CZygwLwM.mjs → messages-BI43k_BD.mjs} +6 -0
  88. package/dist/{chunks/messages-CzTufCHu.mjs → messages-BJ6zrz2j.mjs} +6 -0
  89. package/dist/{chunks/messages-BoJc_p1r.mjs → messages-BUl_Rcnj.mjs} +6 -0
  90. package/dist/{messages-CnwibSvh.mjs → messages-BZlmVRwn.mjs} +6 -0
  91. package/dist/{chunks/messages-C2htQ_3F.mjs → messages-BcpCubnC.mjs} +6 -0
  92. package/dist/{chunks/messages-D5C3J9qr.mjs → messages-Bm-E4iRC.mjs} +6 -0
  93. package/dist/{messages-BELRf6DU.mjs → messages-C4jL-90N.mjs} +6 -0
  94. package/dist/{messages-1fC8IMyX.mjs → messages-CDBLbUOQ.mjs} +6 -0
  95. package/dist/{messages-7QoX8DkW.mjs → messages-CH4hrauY.mjs} +6 -0
  96. package/dist/{chunks/messages-Dz9L52ol.mjs → messages-CRJ_mchV.mjs} +6 -0
  97. package/dist/{messages-JELdtT6E.mjs → messages-CW4c4cRk.mjs} +6 -0
  98. package/dist/{messages-CKI54h6O.mjs → messages-C_4otP7U.mjs} +6 -0
  99. package/dist/{chunks/messages-R3hUSvr3.mjs → messages-CfiyT2Wi.mjs} +6 -0
  100. package/dist/{chunks/messages-CJdUsQ-c.mjs → messages-CgTq3QhU.mjs} +6 -0
  101. package/dist/{messages-D1Hv8XGo.mjs → messages-Chb7k3Rg.mjs} +6 -0
  102. package/dist/{chunks/messages-Q7AO_FLv.mjs → messages-Cjjo7yHR.mjs} +6 -0
  103. package/dist/{chunks/messages-C99mq906.mjs → messages-Cl6ayUaq.mjs} +6 -0
  104. package/dist/{messages-Diu6jAaR.mjs → messages-CmR9ftc_.mjs} +6 -0
  105. package/dist/{messages-LPVfA-8K.mjs → messages-Cr49Nt3U.mjs} +6 -0
  106. package/dist/{messages-DqM1LFg5.mjs → messages-Cr94GzbX.mjs} +6 -0
  107. package/dist/{chunks/messages-BWF-zUpY.mjs → messages-CrCYPCk3.mjs} +6 -0
  108. package/dist/{chunks/messages-D-ZtY5v0.mjs → messages-Cs8zmZ3L.mjs} +6 -0
  109. package/dist/{chunks/messages-DprmQg6V.mjs → messages-CzK0LEhb.mjs} +6 -0
  110. package/dist/{messages-BSbjsyHY.mjs → messages-D00x4S8o.mjs} +6 -0
  111. package/dist/{messages-Xq8UmkVs.mjs → messages-D1mn7Zd5.mjs} +6 -0
  112. package/dist/{messages-BC86qLvI.mjs → messages-D2NOpHn9.mjs} +6 -0
  113. package/dist/{chunks/messages-kep5wtm4.mjs → messages-D4qqwVgQ.mjs} +6 -0
  114. package/dist/{messages-7W4d0DwD.mjs → messages-D5S1Dnpm.mjs} +6 -0
  115. package/dist/{chunks/messages-CY8_RyFE.mjs → messages-D7u2bmP2.mjs} +6 -0
  116. package/dist/{messages-BFG6Wlgy.mjs → messages-D85FqxgY.mjs} +6 -0
  117. package/dist/{chunks/messages-DLfR5bMd.mjs → messages-D9ndgBnU.mjs} +6 -0
  118. package/dist/{chunks/messages-CVw84KdI.mjs → messages-DDTQgImT.mjs} +6 -0
  119. package/dist/{chunks/messages-_ErNTNhk.mjs → messages-DH_jBeED.mjs} +6 -0
  120. package/dist/{messages-CMkNSDTo.mjs → messages-DRXWF0PV.mjs} +6 -0
  121. package/dist/{messages-BYyy6Wqf.mjs → messages-DVQvl8Qj.mjs} +6 -0
  122. package/dist/{messages-CznZadDf.mjs → messages-DXktiao_.mjs} +6 -0
  123. package/dist/{messages-DhLKYm2j.mjs → messages-DdK-nFGm.mjs} +6 -0
  124. package/dist/{messages-BMXCuEKO.mjs → messages-DlJbPF2T.mjs} +6 -0
  125. package/dist/{messages-CvGLfqmV.mjs → messages-DnVlmiNT.mjs} +6 -0
  126. package/dist/{chunks/messages-Z9nEU2xK.mjs → messages-DviiFSv2.mjs} +6 -0
  127. package/dist/{messages-BB5z9Uba.mjs → messages-DzqM3Fel.mjs} +6 -0
  128. package/dist/{chunks/messages-w7v1GNaE.mjs → messages-Dzzn6XoD.mjs} +6 -0
  129. package/dist/{chunks/messages-CqWJcCbY.mjs → messages-GSByFygY.mjs} +6 -0
  130. package/dist/{messages-_ncGrKHh.mjs → messages-L_kl2Qvh.mjs} +6 -0
  131. package/dist/{messages-BrPFGbM-.mjs → messages-Phkd7XmE.mjs} +6 -0
  132. package/dist/{chunks/messages-BU2nlrLK.mjs → messages-RonBBCnh.mjs} +6 -0
  133. package/dist/{chunks/messages-Bmu_S7GM.mjs → messages-VDriF5Qy.mjs} +6 -0
  134. package/dist/{chunks/messages-CLhcMlTc.mjs → messages-ZjUAIWb1.mjs} +6 -0
  135. package/dist/{chunks/messages-9SihnaXQ.mjs → messages-b1EdvUm0.mjs} +6 -0
  136. package/dist/{chunks/messages-DvFLX36Q.mjs → messages-begYOTgC.mjs} +6 -0
  137. package/dist/{chunks/messages-BMv4xwIr.mjs → messages-jrncnb-H.mjs} +6 -0
  138. package/dist/{chunks/messages-D5iv1Kox.mjs → messages-nefz1S71.mjs} +6 -0
  139. package/dist/{chunks/messages-CQwpzUFp.mjs → messages-ucTVgS5G.mjs} +6 -0
  140. package/dist/{messages-DBRw-7Zc.mjs → messages-v3GipbFl.mjs} +6 -0
  141. package/dist/{chunks/messages-C9eaarcK.mjs → messages-wmi-iFkH.mjs} +6 -0
  142. package/dist/{messages-O5tQus_0.mjs → messages-yHcs38yI.mjs} +6 -0
  143. package/dist/tools.mjs +3537 -1710
  144. package/dist/vendor.LICENSE.txt +109 -109
  145. package/package.json +43 -57
  146. package/src/blok.ts +12 -0
  147. package/src/components/__module.ts +21 -0
  148. package/src/components/block/api.ts +17 -0
  149. package/src/components/block/style-manager.ts +6 -2
  150. package/src/components/block/tool-renderer.ts +33 -30
  151. package/src/components/blocks.ts +132 -15
  152. package/src/components/constants/data-attributes.ts +7 -0
  153. package/src/components/i18n/locales/am/messages.json +6 -0
  154. package/src/components/i18n/locales/ar/messages.json +6 -0
  155. package/src/components/i18n/locales/az/messages.json +6 -0
  156. package/src/components/i18n/locales/bg/messages.json +6 -0
  157. package/src/components/i18n/locales/bn/messages.json +6 -0
  158. package/src/components/i18n/locales/bs/messages.json +6 -0
  159. package/src/components/i18n/locales/cs/messages.json +6 -0
  160. package/src/components/i18n/locales/da/messages.json +6 -0
  161. package/src/components/i18n/locales/de/messages.json +6 -0
  162. package/src/components/i18n/locales/dv/messages.json +6 -0
  163. package/src/components/i18n/locales/el/messages.json +6 -0
  164. package/src/components/i18n/locales/en/messages.json +6 -0
  165. package/src/components/i18n/locales/es/messages.json +6 -0
  166. package/src/components/i18n/locales/et/messages.json +6 -0
  167. package/src/components/i18n/locales/fa/messages.json +6 -0
  168. package/src/components/i18n/locales/fi/messages.json +6 -0
  169. package/src/components/i18n/locales/fil/messages.json +6 -0
  170. package/src/components/i18n/locales/fr/messages.json +6 -0
  171. package/src/components/i18n/locales/gu/messages.json +6 -0
  172. package/src/components/i18n/locales/he/messages.json +6 -0
  173. package/src/components/i18n/locales/hi/messages.json +6 -0
  174. package/src/components/i18n/locales/hr/messages.json +6 -0
  175. package/src/components/i18n/locales/hu/messages.json +6 -0
  176. package/src/components/i18n/locales/hy/messages.json +6 -0
  177. package/src/components/i18n/locales/id/messages.json +6 -0
  178. package/src/components/i18n/locales/it/messages.json +6 -0
  179. package/src/components/i18n/locales/ja/messages.json +6 -0
  180. package/src/components/i18n/locales/ka/messages.json +6 -0
  181. package/src/components/i18n/locales/km/messages.json +6 -0
  182. package/src/components/i18n/locales/kn/messages.json +6 -0
  183. package/src/components/i18n/locales/ko/messages.json +6 -0
  184. package/src/components/i18n/locales/ku/messages.json +6 -0
  185. package/src/components/i18n/locales/lo/messages.json +6 -0
  186. package/src/components/i18n/locales/lt/messages.json +6 -0
  187. package/src/components/i18n/locales/lv/messages.json +6 -0
  188. package/src/components/i18n/locales/mk/messages.json +6 -0
  189. package/src/components/i18n/locales/ml/messages.json +6 -0
  190. package/src/components/i18n/locales/mn/messages.json +6 -0
  191. package/src/components/i18n/locales/mr/messages.json +6 -0
  192. package/src/components/i18n/locales/ms/messages.json +6 -0
  193. package/src/components/i18n/locales/my/messages.json +6 -0
  194. package/src/components/i18n/locales/ne/messages.json +6 -0
  195. package/src/components/i18n/locales/nl/messages.json +6 -0
  196. package/src/components/i18n/locales/no/messages.json +6 -0
  197. package/src/components/i18n/locales/pa/messages.json +6 -0
  198. package/src/components/i18n/locales/pl/messages.json +6 -0
  199. package/src/components/i18n/locales/ps/messages.json +6 -0
  200. package/src/components/i18n/locales/pt/messages.json +6 -0
  201. package/src/components/i18n/locales/ro/messages.json +6 -0
  202. package/src/components/i18n/locales/ru/messages.json +6 -0
  203. package/src/components/i18n/locales/sd/messages.json +6 -0
  204. package/src/components/i18n/locales/si/messages.json +6 -0
  205. package/src/components/i18n/locales/sk/messages.json +6 -0
  206. package/src/components/i18n/locales/sl/messages.json +6 -0
  207. package/src/components/i18n/locales/sq/messages.json +6 -0
  208. package/src/components/i18n/locales/sr/messages.json +6 -0
  209. package/src/components/i18n/locales/sv/messages.json +6 -0
  210. package/src/components/i18n/locales/sw/messages.json +6 -0
  211. package/src/components/i18n/locales/ta/messages.json +6 -0
  212. package/src/components/i18n/locales/te/messages.json +6 -0
  213. package/src/components/i18n/locales/th/messages.json +6 -0
  214. package/src/components/i18n/locales/tr/messages.json +6 -0
  215. package/src/components/i18n/locales/ug/messages.json +6 -0
  216. package/src/components/i18n/locales/uk/messages.json +6 -0
  217. package/src/components/i18n/locales/ur/messages.json +6 -0
  218. package/src/components/i18n/locales/vi/messages.json +6 -0
  219. package/src/components/i18n/locales/yi/messages.json +6 -0
  220. package/src/components/i18n/locales/zh/messages.json +6 -0
  221. package/src/components/icons/index.ts +61 -7
  222. package/src/components/inline-tools/inline-tool-link.ts +1 -1
  223. package/src/components/inline-tools/inline-tool-marker.ts +737 -0
  224. package/src/components/inline-tools/utils/formatting-range-utils.ts +6 -3
  225. package/src/components/inline-tools/utils/marker-dom-utils.ts +17 -0
  226. package/src/components/modules/api/blocks.ts +34 -9
  227. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +75 -29
  228. package/src/components/modules/blockEvents/composers/markdownShortcuts.ts +54 -2
  229. package/src/components/modules/blockEvents/constants.ts +12 -0
  230. package/src/components/modules/blockEvents/index.ts +13 -5
  231. package/src/components/modules/blockManager/blockManager.ts +81 -2
  232. package/src/components/modules/blockManager/hierarchy.ts +20 -2
  233. package/src/components/modules/blockManager/operations.ts +70 -35
  234. package/src/components/modules/blockManager/repository.ts +22 -0
  235. package/src/components/modules/blockManager/types.ts +3 -1
  236. package/src/components/modules/blockManager/yjs-sync.ts +173 -39
  237. package/src/components/modules/blockSelection.ts +3 -0
  238. package/src/components/modules/crossBlockSelection.ts +11 -3
  239. package/src/components/modules/drag/preview/DragPreview.ts +10 -2
  240. package/src/components/modules/drag/target/DropTargetDetector.ts +100 -11
  241. package/src/components/modules/drag/utils/drag.constants.ts +1 -1
  242. package/src/components/modules/normalizeInlineImages.ts +263 -0
  243. package/src/components/modules/paste/google-docs-preprocessor.ts +197 -0
  244. package/src/components/modules/paste/handlers/base.ts +43 -2
  245. package/src/components/modules/paste/handlers/html-handler.ts +1 -1
  246. package/src/components/modules/paste/handlers/index.ts +1 -0
  247. package/src/components/modules/paste/handlers/table-cells-handler.ts +104 -0
  248. package/src/components/modules/paste/index.ts +20 -3
  249. package/src/components/modules/readonly.ts +8 -2
  250. package/src/components/modules/rectangleSelection.ts +5 -2
  251. package/src/components/modules/renderer.ts +35 -0
  252. package/src/components/modules/saver.ts +52 -2
  253. package/src/components/modules/toolbar/blockSettings.ts +52 -44
  254. package/src/components/modules/toolbar/index.ts +124 -17
  255. package/src/components/modules/toolbar/inline/index.ts +4 -4
  256. package/src/components/modules/toolbar/plus-button.ts +3 -3
  257. package/src/components/modules/toolbar/settings-toggler.ts +3 -3
  258. package/src/components/modules/toolbar/styles.ts +7 -7
  259. package/src/components/modules/ui.ts +6 -6
  260. package/src/components/modules/uiControllers/controllers/blockHover.ts +16 -2
  261. package/src/components/modules/uiControllers/handlers/touch.ts +83 -10
  262. package/src/components/modules/yjs/block-observer.ts +9 -3
  263. package/src/components/modules/yjs/document-store.ts +10 -7
  264. package/src/components/modules/yjs/types.ts +8 -6
  265. package/src/components/modules/yjs/undo-history.ts +90 -11
  266. package/src/components/selection/fake-background/shadows.ts +1 -1
  267. package/src/components/shared/color-picker.ts +211 -0
  268. package/src/components/shared/color-presets.ts +25 -0
  269. package/src/components/ui/toolbox.ts +27 -11
  270. package/src/components/utils/color-mapping.ts +241 -0
  271. package/src/components/utils/notifier/draw.ts +9 -9
  272. package/src/components/utils/placeholder.ts +24 -8
  273. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +3 -3
  274. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +15 -12
  275. package/src/components/utils/popover/components/search-input/search-input.const.ts +2 -2
  276. package/src/components/utils/popover/popover-abstract.ts +30 -5
  277. package/src/components/utils/popover/popover-desktop.ts +26 -3
  278. package/src/components/utils/popover/popover-inline.ts +14 -1
  279. package/src/components/utils/popover/popover-mobile.ts +4 -4
  280. package/src/components/utils/popover/popover.const.ts +3 -3
  281. package/src/components/utils/sanitizer.ts +24 -3
  282. package/src/components/utils/tw.ts +17 -5
  283. package/src/full.ts +4 -0
  284. package/src/stories/Header.stories.ts +106 -0
  285. package/src/stories/MarkerColors.stories.ts +730 -0
  286. package/src/stories/Placeholder.stories.ts +7 -2
  287. package/src/stories/Popover.stories.ts +1 -3
  288. package/src/stories/Table.stories.ts +1662 -0
  289. package/src/stories/helpers.ts +2 -0
  290. package/src/styles/main.css +217 -39
  291. package/src/tools/header/index.ts +204 -26
  292. package/src/tools/index.ts +5 -1
  293. package/src/tools/list/caret-manager.ts +28 -10
  294. package/src/tools/list/constants.ts +2 -2
  295. package/src/tools/list/dom-builder.ts +3 -3
  296. package/src/tools/list/static-configs.ts +0 -1
  297. package/src/tools/paragraph/index.ts +9 -5
  298. package/src/tools/table/core/table-commands.ts +99 -0
  299. package/src/tools/table/core/table-controller.ts +231 -0
  300. package/src/tools/table/core/table-events.ts +102 -0
  301. package/src/tools/table/index.ts +1070 -174
  302. package/src/tools/table/ownership/table-event-broker.ts +74 -0
  303. package/src/tools/table/ownership/table-ownership-registry.ts +126 -0
  304. package/src/tools/table/table-add-controls.ts +85 -15
  305. package/src/tools/table/table-cell-blocks.ts +336 -38
  306. package/src/tools/table/table-cell-clipboard.ts +415 -0
  307. package/src/tools/table/table-cell-color-picker.ts +34 -0
  308. package/src/tools/table/table-cell-selection.ts +264 -15
  309. package/src/tools/table/table-core.ts +3 -42
  310. package/src/tools/table/table-heading-toggle.ts +2 -2
  311. package/src/tools/table/table-model.ts +623 -0
  312. package/src/tools/table/table-operations.ts +59 -78
  313. package/src/tools/table/table-resize.ts +15 -11
  314. package/src/tools/table/table-restrictions.ts +69 -3
  315. package/src/tools/table/table-row-col-action-handler.ts +22 -7
  316. package/src/tools/table/table-row-col-controls.ts +129 -12
  317. package/src/tools/table/table-row-col-drag.ts +14 -0
  318. package/src/tools/table/table-scroll-haze.ts +152 -0
  319. package/src/tools/table/types.ts +22 -1
  320. package/src/tools/table/view/table-cell-blocks-adapter.ts +47 -0
  321. package/src/tools/toggle/block-operations.ts +110 -0
  322. package/src/tools/toggle/constants.ts +49 -0
  323. package/src/tools/toggle/dom-builder.ts +125 -0
  324. package/src/tools/toggle/index.ts +280 -0
  325. package/src/tools/toggle/toggle-keyboard.ts +139 -0
  326. package/src/tools/toggle/toggle-lifecycle.ts +80 -0
  327. package/src/tools/toggle/toggle-shortcuts.ts +107 -0
  328. package/src/tools/toggle/types.ts +21 -0
  329. package/src/variants/blok-minimum.ts +13 -0
  330. package/types/api/block.d.ts +13 -0
  331. package/types/api/blocks.d.ts +16 -0
  332. package/types/full.d.ts +2 -0
  333. package/types/tools/table.d.ts +2 -0
  334. package/types/tools-entry.d.ts +2 -1
  335. package/dist/chunks/i18next-CugVlwWp.mjs +0 -1292
  336. package/src/tools/table/data-normalizer.ts +0 -32
@@ -44,12 +44,15 @@ export const createRangeTextWalker = (range: Range): TreeWalker => {
44
44
  * Find first ancestor element matching the predicate
45
45
  * @param node - The node to start searching from
46
46
  * @param predicate - Function to test elements
47
+ * @param boundary - Optional node that stops the upward traversal.
48
+ * When reached, the search returns null instead of continuing.
47
49
  */
48
50
  export const findFormattingAncestor = (
49
51
  node: Node | null,
50
- predicate: (element: Element) => boolean
52
+ predicate: (element: Element) => boolean,
53
+ boundary?: Node
51
54
  ): HTMLElement | null => {
52
- if (!node) {
55
+ if (!node || node === boundary) {
53
56
  return null;
54
57
  }
55
58
 
@@ -57,7 +60,7 @@ export const findFormattingAncestor = (
57
60
  return node as HTMLElement;
58
61
  }
59
62
 
60
- return findFormattingAncestor(node.parentNode, predicate);
63
+ return findFormattingAncestor(node.parentNode, predicate, boundary);
61
64
  };
62
65
 
63
66
  /**
@@ -0,0 +1,17 @@
1
+ import { findFormattingAncestor } from './formatting-range-utils';
2
+
3
+ /**
4
+ * Check if an element is a <mark> tag
5
+ * @param element - The element to check
6
+ */
7
+ export const isMarkTag = (element: Element): boolean => {
8
+ return element.tagName === 'MARK';
9
+ };
10
+
11
+ /**
12
+ * Find closest <mark> ancestor from a node
13
+ * @param node - The node to start searching from
14
+ */
15
+ export const findMarkElement = (node: Node | null): HTMLElement | null => {
16
+ return findFormattingAncestor(node, isMarkTag);
17
+ };
@@ -20,7 +20,12 @@ export class BlocksAPI extends Module {
20
20
  * @returns {Blocks}
21
21
  */
22
22
  public get methods(): Blocks {
23
+ const blocksAPI = this;
24
+
23
25
  return {
26
+ get isSyncingFromYjs(): boolean {
27
+ return blocksAPI.Blok.BlockManager.isSyncingFromYjs;
28
+ },
24
29
  clear: (): Promise<void> => this.clear(),
25
30
  render: (data: OutputData): Promise<void> => this.render(data),
26
31
  renderFromHTML: (data: string): Promise<void> => this.renderFromHTML(data),
@@ -41,6 +46,7 @@ export class BlocksAPI extends Module {
41
46
  setBlockParent: (blockId: string, parentId: string | null): void => this.setBlockParent(blockId, parentId),
42
47
  stopBlockMutationWatching: (index: number): void => this.stopBlockMutationWatching(index),
43
48
  splitBlock: this.splitBlock,
49
+ transact: (fn: () => void): void => this.transact(fn),
44
50
  };
45
51
  }
46
52
 
@@ -168,12 +174,12 @@ export class BlocksAPI extends Module {
168
174
  }
169
175
 
170
176
  /**
171
- * in case of last block deletion
172
- * Insert the new default empty block
177
+ * Note: default-block insertion when the store is empty is handled
178
+ * synchronously by removeBlock(block, addLastBlock=true).
179
+ * A redundant async check here would race with clear()/render()
180
+ * and could insert a spurious paragraph after the store has been
181
+ * repopulated by Renderer.
173
182
  */
174
- if (this.Blok.BlockManager.blocks.length === 0) {
175
- this.Blok.BlockManager.insert();
176
- }
177
183
 
178
184
  /**
179
185
  * After Block deletion currentBlock is updated
@@ -207,9 +213,14 @@ export class BlocksAPI extends Module {
207
213
  * So we need to disable modifications observer temporarily
208
214
  */
209
215
  this.Blok.ModificationsObserver.disable();
216
+ this.Blok.Renderer.markRenderStart();
210
217
 
211
- await this.Blok.BlockManager.clear();
212
- await this.Blok.Renderer.render(data.blocks);
218
+ try {
219
+ await this.Blok.BlockManager.clear();
220
+ await this.Blok.Renderer.render(data.blocks);
221
+ } finally {
222
+ this.Blok.Renderer.markRenderEnd();
223
+ }
213
224
 
214
225
  this.Blok.ModificationsObserver.enable();
215
226
  }
@@ -220,9 +231,15 @@ export class BlocksAPI extends Module {
220
231
  * @returns {Promise<void>}
221
232
  */
222
233
  public async renderFromHTML(data: string): Promise<void> {
223
- await this.Blok.BlockManager.clear();
234
+ this.Blok.Renderer.markRenderStart();
235
+
236
+ try {
237
+ await this.Blok.BlockManager.clear();
224
238
 
225
- return this.Blok.Paste.processText(data, true);
239
+ return this.Blok.Paste.processText(data, true);
240
+ } finally {
241
+ this.Blok.Renderer.markRenderEnd();
242
+ }
226
243
  }
227
244
 
228
245
  /**
@@ -446,6 +463,14 @@ export class BlocksAPI extends Module {
446
463
  return new BlockAPI(newBlock);
447
464
  };
448
465
 
466
+ /**
467
+ * Execute a function within a transaction, grouping all block operations
468
+ * into a single undo entry.
469
+ */
470
+ private transact(fn: () => void): void {
471
+ this.Blok.BlockManager.transactForTool(fn);
472
+ }
473
+
449
474
  /**
450
475
  * Validated block index and throws an error if it's invalid
451
476
  * @param index - index to validate
@@ -43,6 +43,33 @@ export class KeyboardNavigation extends BlockEventComposer {
43
43
  return ui.isRtl ?? false;
44
44
  }
45
45
 
46
+ /**
47
+ * Check if the current block is inside a table cell.
48
+ * Used to prevent closing the toolbar when the user navigates
49
+ * within a table — closing it makes the toolbar permanently
50
+ * disappear because the hover controller deduplicates by block id
51
+ * and all cells resolve to the same parent table block.
52
+ */
53
+ private get isCurrentBlockInsideTableCell(): boolean {
54
+ const currentBlock = this.Blok.BlockManager.currentBlock;
55
+
56
+ return Boolean(currentBlock?.holder?.closest('[data-blok-table-cell-blocks]'));
57
+ }
58
+
59
+ /**
60
+ * Fully close the toolbar if the current block is NOT inside a table cell.
61
+ * Used for destructive operations (Backspace, Delete, merge) where the
62
+ * toolbar should be dismissed — unlike arrow navigation where
63
+ * hideBlockActions() is preferred to allow reopening.
64
+ */
65
+ private closeToolbarIfNotInTableCell(): void {
66
+ if (this.isCurrentBlockInsideTableCell) {
67
+ return;
68
+ }
69
+
70
+ this.Blok.Toolbar.close();
71
+ }
72
+
46
73
  /**
47
74
  * Tab pressed inside a Block.
48
75
  * @param event - keydown event
@@ -105,17 +132,31 @@ export class KeyboardNavigation extends BlockEventComposer {
105
132
  return;
106
133
  }
107
134
 
108
- // Force new undo group so block creation is separate from previous typing
109
- this.Blok.YjsManager.stopCapturing();
110
-
111
- const blockToFocus = this.createBlockOnEnter(currentBlock);
112
-
113
- this.Blok.Caret.setToBlock(blockToFocus);
135
+ /**
136
+ * Capture caret position before block creation so undo can restore it.
137
+ * Must run before stopCapturing/block insertion since the keyboard capture
138
+ * handler fires AFTER handleEnter (document capture runs before redactor capture).
139
+ */
140
+ this.Blok.YjsManager.markCaretBeforeChange();
114
141
 
115
142
  /**
116
- * Show Toolbar
143
+ * Use transactForTool to keep the entire Enter operation in a single undo entry:
144
+ * 1. Calls stopCapturing() to separate from previous typing
145
+ * 2. Suppresses stopCapturing during block creation + caret movement
146
+ * (prevents currentBlockIndex setter from splitting the undo entry
147
+ * before async table cell content sync completes)
148
+ * 3. Calls stopCapturing() in rAF after all async syncs complete
117
149
  */
118
- this.Blok.Toolbar.moveAndOpen(blockToFocus);
150
+ this.Blok.BlockManager.transactForTool(() => {
151
+ const blockToFocus = this.createBlockOnEnter(currentBlock);
152
+
153
+ this.Blok.Caret.setToBlock(blockToFocus);
154
+
155
+ /**
156
+ * Show Toolbar
157
+ */
158
+ this.Blok.Toolbar.moveAndOpen(blockToFocus);
159
+ });
119
160
 
120
161
  event.preventDefault();
121
162
  }
@@ -132,27 +173,18 @@ export class KeyboardNavigation extends BlockEventComposer {
132
173
  */
133
174
  private createBlockOnEnter(currentBlock: Block): Block {
134
175
  // Case 1: Caret at start - insert block above
135
- if (currentBlock.currentInput !== undefined && isCaretAtStartOfInput(currentBlock.currentInput) && !currentBlock.hasMedia) {
176
+ if (currentBlock.currentInput !== undefined && isCaretAtStartOfInput(currentBlock.currentInput) && !currentBlock.hasMedia && (currentBlock.parentId === null || !currentBlock.isEmpty)) {
136
177
  this.Blok.BlockManager.insertDefaultBlockAtIndex(this.Blok.BlockManager.currentBlockIndex);
137
178
 
138
- // Force new undo group so typing in the new block is separate from block creation
139
- this.Blok.YjsManager.stopCapturing();
140
-
141
179
  return currentBlock;
142
180
  }
143
181
 
144
182
  // Case 2: Caret at end - insert block below
145
183
  if (currentBlock.currentInput && isCaretAtEndOfInput(currentBlock.currentInput)) {
146
- const newBlock = this.Blok.BlockManager.insertDefaultBlockAtIndex(this.Blok.BlockManager.currentBlockIndex + 1);
147
-
148
- // Force new undo group so typing in the new block is separate from block creation
149
- this.Blok.YjsManager.stopCapturing();
150
-
151
- return newBlock;
184
+ return this.Blok.BlockManager.insertDefaultBlockAtIndex(this.Blok.BlockManager.currentBlockIndex + 1);
152
185
  }
153
186
 
154
187
  // Case 3: Caret in middle - split block
155
- // Note: split() uses transact() internally, so it's already atomic - no stopCapturing needed
156
188
  return this.Blok.BlockManager.split();
157
189
  }
158
190
 
@@ -186,7 +218,8 @@ export class KeyboardNavigation extends BlockEventComposer {
186
218
  * All the cases below have custom behaviour, so we don't need a native one
187
219
  */
188
220
  event.preventDefault();
189
- this.Blok.Toolbar.close();
221
+
222
+ this.closeToolbarIfNotInTableCell();
190
223
 
191
224
  const isFirstInputFocused = currentBlock.currentInput === currentBlock.firstInput;
192
225
 
@@ -273,7 +306,8 @@ export class KeyboardNavigation extends BlockEventComposer {
273
306
  * All the cases below have custom behaviour, so we don't need a native one
274
307
  */
275
308
  event.preventDefault();
276
- this.Blok.Toolbar.close();
309
+
310
+ this.closeToolbarIfNotInTableCell();
277
311
 
278
312
  const isLastInputFocused = currentBlock.currentInput === currentBlock.lastInput;
279
313
 
@@ -316,7 +350,7 @@ export class KeyboardNavigation extends BlockEventComposer {
316
350
  const newCurrentBlock = BlockManager.currentBlock;
317
351
 
318
352
  newCurrentBlock && Caret.setToBlock(newCurrentBlock, Caret.positions.START);
319
- this.Blok.Toolbar.close();
353
+ this.closeToolbarIfNotInTableCell();
320
354
 
321
355
  return;
322
356
  }
@@ -340,7 +374,7 @@ export class KeyboardNavigation extends BlockEventComposer {
340
374
  * @param blockToMerge - what Block we want to merge
341
375
  */
342
376
  private mergeBlocks(targetBlock: Block, blockToMerge: Block): void {
343
- const { BlockManager, Toolbar } = this.Blok;
377
+ const { BlockManager } = this.Blok;
344
378
 
345
379
  if (targetBlock.lastInput === undefined) {
346
380
  return;
@@ -351,7 +385,7 @@ export class KeyboardNavigation extends BlockEventComposer {
351
385
  BlockManager
352
386
  .mergeBlocks(targetBlock, blockToMerge)
353
387
  .then(() => {
354
- Toolbar.close();
388
+ this.closeToolbarIfNotInTableCell();
355
389
  })
356
390
  .catch(() => {
357
391
  // Error handling for mergeBlocks
@@ -391,9 +425,10 @@ export class KeyboardNavigation extends BlockEventComposer {
391
425
  /**
392
426
  * Close Toolbar when user moves cursor, but keep toolbars open if the user
393
427
  * is extending selection with the Shift key so inline interactions remain available.
428
+ * Skip closing when inside a table cell — the toolbar belongs to the parent
429
+ * table block and the hover controller won't re-emit BlockHovered for it.
394
430
  */
395
- if (!event.shiftKey) {
396
- this.Blok.Toolbar.close();
431
+ if (!event.shiftKey && !this.isCurrentBlockInsideTableCell) {
397
432
  this.Blok.InlineToolbar.close();
398
433
  }
399
434
 
@@ -477,6 +512,11 @@ export class KeyboardNavigation extends BlockEventComposer {
477
512
  */
478
513
  event.preventDefault();
479
514
 
515
+ /**
516
+ * Reopen the toolbar at the new block position after navigation
517
+ */
518
+ this.Blok.Toolbar.moveAndOpen(this.Blok.BlockManager.currentBlock);
519
+
480
520
  return;
481
521
  }
482
522
 
@@ -526,15 +566,16 @@ export class KeyboardNavigation extends BlockEventComposer {
526
566
  return;
527
567
  }
528
568
 
529
- if (toolbarOpened) {
569
+ if (toolbarOpened && !this.isCurrentBlockInsideTableCell) {
530
570
  this.Blok.UI.closeAllToolbars();
531
571
  }
532
572
 
533
573
  /**
534
574
  * Close Toolbar when user moves cursor, but preserve it for Shift-based selection changes.
575
+ * Skip closing when inside a table cell — the toolbar belongs to the parent
576
+ * table block and the hover controller won't re-emit BlockHovered for it.
535
577
  */
536
- if (!event.shiftKey) {
537
- this.Blok.Toolbar.close();
578
+ if (!event.shiftKey && !this.isCurrentBlockInsideTableCell) {
538
579
  this.Blok.InlineToolbar.close();
539
580
  }
540
581
 
@@ -605,6 +646,11 @@ export class KeyboardNavigation extends BlockEventComposer {
605
646
  */
606
647
  event.preventDefault();
607
648
 
649
+ /**
650
+ * Reopen the toolbar at the new block position after navigation
651
+ */
652
+ this.Blok.Toolbar.moveAndOpen(this.Blok.BlockManager.currentBlock);
653
+
608
654
  return;
609
655
  }
610
656
 
@@ -1,5 +1,5 @@
1
1
  import type { Block } from '../../../block';
2
- import { HEADER_PATTERN, CHECKLIST_PATTERN, UNORDERED_LIST_PATTERN, ORDERED_LIST_PATTERN, HEADER_TOOL_NAME, LIST_TOOL_NAME } from '../constants';
2
+ import { HEADER_PATTERN, CHECKLIST_PATTERN, UNORDERED_LIST_PATTERN, ORDERED_LIST_PATTERN, TOGGLE_PATTERN, HEADER_TOOL_NAME, LIST_TOOL_NAME, TOGGLE_TOOL_NAME } from '../constants';
3
3
 
4
4
  import { BlockEventComposer } from './__base';
5
5
 
@@ -26,8 +26,9 @@ export class MarkdownShortcuts extends BlockEventComposer {
26
26
 
27
27
  const handledList = this.handleListShortcut();
28
28
  const handledHeader = this.handleHeaderShortcut();
29
+ const handledToggle = this.handleToggleShortcut();
29
30
 
30
- return handledList || handledHeader;
31
+ return handledList || handledHeader || handledToggle;
31
32
  }
32
33
 
33
34
  /**
@@ -207,6 +208,57 @@ export class MarkdownShortcuts extends BlockEventComposer {
207
208
  return true;
208
209
  }
209
210
 
211
+ /**
212
+ * Check if current block matches a toggle shortcut pattern ("> ") and convert it.
213
+ */
214
+ private handleToggleShortcut(): boolean {
215
+ const { BlockManager, Tools } = this.Blok;
216
+ const currentBlock = BlockManager.currentBlock;
217
+
218
+ if (!currentBlock) {
219
+ return false;
220
+ }
221
+
222
+ if (!currentBlock.tool.isDefault) {
223
+ return false;
224
+ }
225
+
226
+ const toggleTool = Tools.blockTools.get(TOGGLE_TOOL_NAME);
227
+
228
+ if (!toggleTool) {
229
+ return false;
230
+ }
231
+
232
+ const currentInput = currentBlock.currentInput;
233
+
234
+ if (!currentInput) {
235
+ return false;
236
+ }
237
+
238
+ const textContent = currentInput.textContent || '';
239
+ const match = TOGGLE_PATTERN.exec(textContent);
240
+
241
+ if (!match) {
242
+ return false;
243
+ }
244
+
245
+ this.Blok.YjsManager.stopCapturing();
246
+
247
+ const shortcutLength = 2; // "> "
248
+ const remainingHtml = this.extractRemainingHtml(currentInput, shortcutLength);
249
+ const caretOffset = this.getCaretOffset(currentInput) - shortcutLength;
250
+
251
+ const newBlock = BlockManager.replace(currentBlock, TOGGLE_TOOL_NAME, {
252
+ text: remainingHtml,
253
+ });
254
+
255
+ this.setCaretAfterConversion(newBlock, caretOffset);
256
+
257
+ this.Blok.YjsManager.stopCapturing();
258
+
259
+ return true;
260
+ }
261
+
210
262
  /**
211
263
  * Match default header shortcuts like "# ", "## ", etc.
212
264
  */
@@ -63,3 +63,15 @@ export const ORDERED_LIST_PATTERN = /^(\d+)[.)]\s([\s\S]*)$/;
63
63
  * Captures remaining content after the shortcut in group 2
64
64
  */
65
65
  export const HEADER_PATTERN = /^(#{1,6})\s([\s\S]*)$/;
66
+
67
+ /**
68
+ * Tool name for toggle blocks.
69
+ */
70
+ export const TOGGLE_TOOL_NAME = 'toggle';
71
+
72
+ /**
73
+ * Regex pattern for detecting toggle shortcuts.
74
+ * Matches ">" followed by a space at the start of text.
75
+ * Captures remaining content after the shortcut in group 1.
76
+ */
77
+ export const TOGGLE_PATTERN = /^>\s([\s\S]*)$/;
@@ -228,16 +228,13 @@ export class BlockEvents extends Module {
228
228
  }
229
229
 
230
230
  /**
231
- * When user type something:
232
- * - close Toolbar
233
- * - clear block highlighting
231
+ * When user types something, clear block highlighting.
232
+ * The toolbar stays visible — it should persist during typing.
234
233
  */
235
234
  if (!isPrintableKeyEvent(event)) {
236
235
  return;
237
236
  }
238
237
 
239
- this.Blok.Toolbar.close();
240
-
241
238
  /**
242
239
  * Allow to use shortcuts with selected blocks
243
240
  * @type {boolean}
@@ -305,6 +302,17 @@ export class BlockEvents extends Module {
305
302
  return;
306
303
  }
307
304
 
305
+ /**
306
+ * Eagerly update currentBlock from the event target.
307
+ * The debounced selectionchange handler (180ms) may not have fired yet
308
+ * if '/' was typed quickly after clicking into a different block (e.g. a table cell).
309
+ * Without this, currentBlockIndex is stale and the toolbox checks
310
+ * the wrong block for table-cell containment, failing to hide restricted tools.
311
+ */
312
+ if (event.target instanceof Node) {
313
+ this.Blok.BlockManager.setCurrentBlockByChildNode(event.target);
314
+ }
315
+
308
316
  const currentBlock = this.Blok.BlockManager.currentBlock;
309
317
  const canOpenToolbox = currentBlock?.isEmpty;
310
318
 
@@ -143,6 +143,14 @@ export class BlockManager extends Module {
143
143
  return this.repository.isBlokEmpty();
144
144
  }
145
145
 
146
+ /**
147
+ * Returns true when a Yjs sync operation (undo/redo) is in progress.
148
+ * Used by the Blocks API to expose sync state to tools.
149
+ */
150
+ public get isSyncingFromYjs(): boolean {
151
+ return this.yjsSync.isSyncingFromYjs;
152
+ }
153
+
146
154
  /**
147
155
  * Index of current working block
148
156
  * @type {number}
@@ -186,6 +194,12 @@ export class BlockManager extends Module {
186
194
  */
187
195
  private yjsSync!: BlockYjsSync;
188
196
 
197
+ /**
198
+ * Set of parent block IDs awaiting deferred Yjs sync.
199
+ * Batched via queueMicrotask to avoid multiple syncs during batch operations.
200
+ */
201
+ private parentsSyncScheduled = new Set<string>();
202
+
189
203
  /**
190
204
  * Operations handler for state changes
191
205
  */
@@ -261,8 +275,12 @@ export class BlockManager extends Module {
261
275
  this.bindBlockEvents.bind(this)
262
276
  );
263
277
 
264
- // Initialize hierarchy
265
- this.hierarchy = new BlockHierarchy(this.repository);
278
+ // Initialize hierarchy with callback to sync parent data to Yjs
279
+ this.hierarchy = new BlockHierarchy(this.repository, (parentId) => {
280
+ if (!this.yjsSync.isSyncingFromYjs) {
281
+ this.scheduleParentSync(parentId);
282
+ }
283
+ });
266
284
 
267
285
  // Initialize operations first (before yjsSync) to allow circular dependency resolution
268
286
  this.operations = new BlockOperations(
@@ -637,6 +655,30 @@ export class BlockManager extends Module {
637
655
  return result;
638
656
  }
639
657
 
658
+ /**
659
+ * Execute a function with stopCapturing suppressed.
660
+ * All block operations within fn are kept in the same undo group.
661
+ * Used by tools that perform multi-step structural operations
662
+ * (e.g., table add row = multiple block inserts).
663
+ */
664
+ public transactForTool(fn: () => void): void {
665
+ this.Blok.YjsManager.stopCapturing();
666
+
667
+ const prevSuppress = this.operations.suppressStopCapturing;
668
+
669
+ this.operations.suppressStopCapturing = true;
670
+
671
+ try {
672
+ fn();
673
+ } finally {
674
+ this.operations.suppressStopCapturing = prevSuppress;
675
+
676
+ requestAnimationFrame(() => {
677
+ this.Blok.YjsManager.stopCapturing();
678
+ });
679
+ }
680
+ }
681
+
640
682
  /**
641
683
  * Splits a block by updating the current block's data and inserting a new block.
642
684
  * Both operations are grouped into a single undo entry.
@@ -682,6 +724,16 @@ export class BlockManager extends Module {
682
724
  return this.repository.getBlockById(id);
683
725
  }
684
726
 
727
+ /**
728
+ * Walks up the parentId chain and returns the top-level (root) block.
729
+ * If the block has no parent, returns it as-is.
730
+ * @param block - the block to resolve
731
+ * @returns {Block} the root ancestor block
732
+ */
733
+ public resolveToRootBlock(block: Block): Block {
734
+ return this.repository.resolveToRootBlock(block);
735
+ }
736
+
685
737
  /**
686
738
  * Returns the depth (nesting level) of a block in the hierarchy.
687
739
  * @param block - the block to get depth for
@@ -928,6 +980,33 @@ export class BlockManager extends Module {
928
980
  return block;
929
981
  }
930
982
 
983
+ /**
984
+ * Schedule a deferred sync of a parent block's data to Yjs.
985
+ * Uses queueMicrotask to batch multiple parent changes (e.g. when initializing
986
+ * all cells in a new table row) into a single flush.
987
+ */
988
+ private scheduleParentSync(parentId: string): void {
989
+ if (this.parentsSyncScheduled.size === 0) {
990
+ queueMicrotask(() => this.flushParentSyncs());
991
+ }
992
+ this.parentsSyncScheduled.add(parentId);
993
+ }
994
+
995
+ /**
996
+ * Flush all scheduled parent syncs to Yjs.
997
+ * Called from the microtask scheduled by scheduleParentSync.
998
+ */
999
+ private flushParentSyncs(): void {
1000
+ for (const parentId of this.parentsSyncScheduled) {
1001
+ const parent = this.repository.getBlockById(parentId);
1002
+
1003
+ if (parent !== undefined) {
1004
+ void this.syncBlockDataToYjs(parent);
1005
+ }
1006
+ }
1007
+ this.parentsSyncScheduled.clear();
1008
+ }
1009
+
931
1010
  /**
932
1011
  * Sync block data to Yjs after DOM mutation
933
1012
  */
@@ -12,12 +12,15 @@ import type { BlockRepository } from './repository';
12
12
  */
13
13
  export class BlockHierarchy {
14
14
  private readonly repository: BlockRepository;
15
+ private readonly onParentChanged?: (parentId: string) => void;
15
16
 
16
17
  /**
17
18
  * @param repository - BlockRepository for looking up blocks by id
19
+ * @param onParentChanged - optional callback invoked after a block is assigned a non-null parent
18
20
  */
19
- constructor(repository: BlockRepository) {
21
+ constructor(repository: BlockRepository, onParentChanged?: (parentId: string) => void) {
20
22
  this.repository = repository;
23
+ this.onParentChanged = onParentChanged;
21
24
  }
22
25
 
23
26
  /**
@@ -73,6 +76,11 @@ export class BlockHierarchy {
73
76
 
74
77
  // Update visual indentation
75
78
  this.updateBlockIndentation(block);
79
+
80
+ // Notify listener so parent data can be synced (e.g. to Yjs)
81
+ if (newParentId !== null && this.onParentChanged !== undefined) {
82
+ this.onParentChanged(newParentId);
83
+ }
76
84
  }
77
85
 
78
86
  /**
@@ -80,9 +88,19 @@ export class BlockHierarchy {
80
88
  * @param block - the block to update indentation for
81
89
  */
82
90
  public updateBlockIndentation(block: Block): void {
91
+ const { holder } = block;
92
+
93
+ // Blocks inside table cells should not receive visual indentation.
94
+ // The parent-child relationship is semantic (data tracking), not visual.
95
+ if (holder.closest('[data-blok-table-cell-blocks]')) {
96
+ holder.style.marginLeft = '';
97
+ holder.setAttribute('data-blok-depth', '0');
98
+
99
+ return;
100
+ }
101
+
83
102
  const depth = this.getBlockDepth(block);
84
103
  const indentationPx = depth * 24; // 24px per level
85
- const { holder } = block;
86
104
 
87
105
  holder.style.marginLeft = indentationPx > 0 ? `${indentationPx}px` : '';
88
106
  holder.setAttribute('data-blok-depth', depth.toString());