@jackuait/blok 0.6.0-beta.0 → 0.6.0-beta.10

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 (286) hide show
  1. package/README.md +16 -169
  2. package/bin/blok.mjs +10 -0
  3. package/dist/blok.mjs +2 -2
  4. package/dist/chunks/{blok-Bu9S3SsR.mjs → blok-Buf0btS7.mjs} +2267 -2024
  5. package/dist/chunks/{i18next-loader-CKuXJ0Av.mjs → i18next-loader-CVf_ZfwA.mjs} +1 -1
  6. package/dist/chunks/{index-jtZaryNw.mjs → index-C6jsfLLp.mjs} +1 -1
  7. package/dist/chunks/{inline-tool-convert-CFjyrH30.mjs → inline-tool-convert-BKKEoOqB.mjs} +710 -570
  8. package/dist/chunks/{messages-C5b7hr_E.mjs → messages-1fC8IMyX.mjs} +16 -2
  9. package/dist/chunks/{messages-CQj2JU2j.mjs → messages-7QoX8DkW.mjs} +23 -9
  10. package/dist/{messages-LvFKBBPa.mjs → chunks/messages-7W4d0DwD.mjs} +15 -1
  11. package/dist/{messages-Bn253WWC.mjs → chunks/messages-9SihnaXQ.mjs} +14 -0
  12. package/dist/{messages-Bf6Y3_GI.mjs → chunks/messages-B1Aww8q7.mjs} +16 -2
  13. package/dist/{messages-pA5TvcAj.mjs → chunks/messages-BB5z9Uba.mjs} +14 -0
  14. package/dist/chunks/{messages-wdqp4610.mjs → messages-BC86qLvI.mjs} +17 -3
  15. package/dist/chunks/{messages-o24dK6CU.mjs → messages-BELRf6DU.mjs} +16 -2
  16. package/dist/chunks/{messages-CUZ1x1QD.mjs → messages-BFG6Wlgy.mjs} +16 -2
  17. package/dist/{messages-B5puUm7R.mjs → chunks/messages-BL0tXcDf.mjs} +15 -1
  18. package/dist/chunks/{messages-zS1AXZ0y.mjs → messages-BMXCuEKO.mjs} +19 -5
  19. package/dist/{messages-CyDU5lz9.mjs → chunks/messages-BMv4xwIr.mjs} +16 -2
  20. package/dist/chunks/{messages-BeUhMpsr.mjs → messages-BSbjsyHY.mjs} +25 -11
  21. package/dist/chunks/{messages-JGsXAReJ.mjs → messages-BU2nlrLK.mjs} +16 -2
  22. package/dist/chunks/{messages-srxrv8Yh.mjs → messages-BWF-zUpY.mjs} +17 -3
  23. package/dist/{messages-CXHd9SUK.mjs → chunks/messages-BYyy6Wqf.mjs} +14 -0
  24. package/dist/chunks/{messages-DOlC_Tty.mjs → messages-BdeLo0N9.mjs} +24 -10
  25. package/dist/chunks/{messages-B5jGUnOy.mjs → messages-Bmu_S7GM.mjs} +14 -0
  26. package/dist/chunks/{messages-BmKCChWZ.mjs → messages-BoJc_p1r.mjs} +14 -0
  27. package/dist/chunks/{messages-CvaqJFN-.mjs → messages-BogRq8lt.mjs} +15 -1
  28. package/dist/chunks/{messages-NP1myMGI.mjs → messages-BrPFGbM-.mjs} +14 -0
  29. package/dist/chunks/{messages-D00OjS2n.mjs → messages-C2htQ_3F.mjs} +24 -10
  30. package/dist/chunks/{messages-BiExzWJv.mjs → messages-C99mq906.mjs} +15 -1
  31. package/dist/chunks/{messages-CkFT2gle.mjs → messages-C9eaarcK.mjs} +20 -6
  32. package/dist/chunks/{messages-BrJHUxQL.mjs → messages-CJdUsQ-c.mjs} +15 -1
  33. package/dist/chunks/messages-CKI54h6O.mjs +62 -0
  34. package/dist/{messages-CrsJ1TEJ.mjs → chunks/messages-CLhcMlTc.mjs} +15 -1
  35. package/dist/{messages-CnvW8Slp.mjs → chunks/messages-CMkNSDTo.mjs} +17 -3
  36. package/dist/{messages-BlpqL8vG.mjs → chunks/messages-CQwpzUFp.mjs} +19 -5
  37. package/dist/chunks/{messages-Cu08aLS3.mjs → messages-CVw84KdI.mjs} +21 -7
  38. package/dist/chunks/{messages-B9Oba7sq.mjs → messages-CY8_RyFE.mjs} +15 -1
  39. package/dist/chunks/{messages-B5hdXZwA.mjs → messages-CZygwLwM.mjs} +15 -1
  40. package/dist/chunks/{messages-CVeWVKsV.mjs → messages-CnwibSvh.mjs} +14 -0
  41. package/dist/chunks/{messages-DbVquYKN.mjs → messages-CqWJcCbY.mjs} +14 -0
  42. package/dist/{messages-Dg92dXZ5.mjs → chunks/messages-CvGLfqmV.mjs} +14 -0
  43. package/dist/{messages-AHESHJm_.mjs → chunks/messages-CzTufCHu.mjs} +14 -0
  44. package/dist/chunks/{messages-Cj-t1bdy.mjs → messages-CznZadDf.mjs} +15 -1
  45. package/dist/chunks/{messages-DMQIHGRj.mjs → messages-D-ZtY5v0.mjs} +14 -0
  46. package/dist/chunks/{messages-rRSHQDCX.mjs → messages-D1Hv8XGo.mjs} +14 -0
  47. package/dist/chunks/{messages-0tDXLuyH.mjs → messages-D5C3J9qr.mjs} +15 -1
  48. package/dist/chunks/{messages-RvMHb2Ht.mjs → messages-D5iv1Kox.mjs} +16 -2
  49. package/dist/{messages-zSzDzXej.mjs → chunks/messages-DBRw-7Zc.mjs} +16 -2
  50. package/dist/chunks/{messages-CeCjVKMW.mjs → messages-DBn76jVV.mjs} +16 -2
  51. package/dist/chunks/{messages-C7I_AVH2.mjs → messages-DJDG55Vq.mjs} +16 -2
  52. package/dist/{messages-DDLgIPDF.mjs → chunks/messages-DLfR5bMd.mjs} +16 -2
  53. package/dist/{messages-CbhuIWRJ.mjs → chunks/messages-DT4dP5uK.mjs} +15 -1
  54. package/dist/chunks/{messages-B66ZSDCJ.mjs → messages-DhLKYm2j.mjs} +15 -1
  55. package/dist/{messages-BPqWKx5Z.mjs → chunks/messages-Diu6jAaR.mjs} +17 -3
  56. package/dist/{messages-BA0rcTCY.mjs → chunks/messages-DnIhyAJk.mjs} +18 -4
  57. package/dist/chunks/{messages-DV6shA9b.mjs → messages-DnXLrlHh.mjs} +14 -0
  58. package/dist/chunks/{messages-DcKOuncK.mjs → messages-DprmQg6V.mjs} +16 -2
  59. package/dist/{messages-CJoBtXU6.mjs → chunks/messages-DqM1LFg5.mjs} +14 -0
  60. package/dist/chunks/{messages-Cyi2AMmz.mjs → messages-DvFLX36Q.mjs} +25 -11
  61. package/dist/chunks/{messages-DnbbyJT3.mjs → messages-Dz9L52ol.mjs} +16 -2
  62. package/dist/chunks/{messages-GC2PhgV3.mjs → messages-Dzwxv9v1.mjs} +23 -9
  63. package/dist/chunks/{messages-Q4kc_ZtL.mjs → messages-JELdtT6E.mjs} +15 -1
  64. package/dist/{messages-DY94ykcE.mjs → chunks/messages-LPVfA-8K.mjs} +14 -0
  65. package/dist/{messages-Cr-RJ7YB.mjs → chunks/messages-O5tQus_0.mjs} +14 -0
  66. package/dist/chunks/{messages-CbMyJSzS.mjs → messages-Q7AO_FLv.mjs} +17 -3
  67. package/dist/chunks/{messages-2_xedlYw.mjs → messages-R3hUSvr3.mjs} +15 -1
  68. package/dist/{messages-CUy1vn-b.mjs → chunks/messages-Xq8UmkVs.mjs} +14 -0
  69. package/dist/chunks/{messages-BBJgd5jG.mjs → messages-Z9nEU2xK.mjs} +16 -2
  70. package/dist/chunks/{messages-Cm9aLHeX.mjs → messages-_ErNTNhk.mjs} +15 -1
  71. package/dist/chunks/{messages-JZUhXTuV.mjs → messages-_ncGrKHh.mjs} +16 -2
  72. package/dist/chunks/{messages-ftMcCEuO.mjs → messages-kep5wtm4.mjs} +15 -1
  73. package/dist/chunks/{messages-DteYq0rv.mjs → messages-uKX8WBaD.mjs} +16 -2
  74. package/dist/chunks/{messages-Bdv-IkfG.mjs → messages-w7v1GNaE.mjs} +15 -1
  75. package/dist/cli.mjs +50 -0
  76. package/dist/full.mjs +15 -15
  77. package/dist/locales.mjs +102 -88
  78. package/dist/{messages-C5b7hr_E.mjs → messages-1fC8IMyX.mjs} +16 -2
  79. package/dist/{messages-CQj2JU2j.mjs → messages-7QoX8DkW.mjs} +23 -9
  80. package/dist/{chunks/messages-LvFKBBPa.mjs → messages-7W4d0DwD.mjs} +15 -1
  81. package/dist/{chunks/messages-Bn253WWC.mjs → messages-9SihnaXQ.mjs} +14 -0
  82. package/dist/{chunks/messages-Bf6Y3_GI.mjs → messages-B1Aww8q7.mjs} +16 -2
  83. package/dist/{chunks/messages-pA5TvcAj.mjs → messages-BB5z9Uba.mjs} +14 -0
  84. package/dist/{messages-wdqp4610.mjs → messages-BC86qLvI.mjs} +17 -3
  85. package/dist/{messages-o24dK6CU.mjs → messages-BELRf6DU.mjs} +16 -2
  86. package/dist/{messages-CUZ1x1QD.mjs → messages-BFG6Wlgy.mjs} +16 -2
  87. package/dist/{chunks/messages-B5puUm7R.mjs → messages-BL0tXcDf.mjs} +15 -1
  88. package/dist/{messages-zS1AXZ0y.mjs → messages-BMXCuEKO.mjs} +19 -5
  89. package/dist/{chunks/messages-CyDU5lz9.mjs → messages-BMv4xwIr.mjs} +16 -2
  90. package/dist/{messages-BeUhMpsr.mjs → messages-BSbjsyHY.mjs} +25 -11
  91. package/dist/{messages-JGsXAReJ.mjs → messages-BU2nlrLK.mjs} +16 -2
  92. package/dist/{messages-srxrv8Yh.mjs → messages-BWF-zUpY.mjs} +17 -3
  93. package/dist/{chunks/messages-CXHd9SUK.mjs → messages-BYyy6Wqf.mjs} +14 -0
  94. package/dist/{messages-DOlC_Tty.mjs → messages-BdeLo0N9.mjs} +24 -10
  95. package/dist/{messages-B5jGUnOy.mjs → messages-Bmu_S7GM.mjs} +14 -0
  96. package/dist/{messages-BmKCChWZ.mjs → messages-BoJc_p1r.mjs} +14 -0
  97. package/dist/{messages-CvaqJFN-.mjs → messages-BogRq8lt.mjs} +15 -1
  98. package/dist/{messages-NP1myMGI.mjs → messages-BrPFGbM-.mjs} +14 -0
  99. package/dist/{messages-D00OjS2n.mjs → messages-C2htQ_3F.mjs} +24 -10
  100. package/dist/{messages-BiExzWJv.mjs → messages-C99mq906.mjs} +15 -1
  101. package/dist/{messages-CkFT2gle.mjs → messages-C9eaarcK.mjs} +20 -6
  102. package/dist/{messages-BrJHUxQL.mjs → messages-CJdUsQ-c.mjs} +15 -1
  103. package/dist/messages-CKI54h6O.mjs +62 -0
  104. package/dist/{chunks/messages-CrsJ1TEJ.mjs → messages-CLhcMlTc.mjs} +15 -1
  105. package/dist/{chunks/messages-CnvW8Slp.mjs → messages-CMkNSDTo.mjs} +17 -3
  106. package/dist/{chunks/messages-BlpqL8vG.mjs → messages-CQwpzUFp.mjs} +19 -5
  107. package/dist/{messages-Cu08aLS3.mjs → messages-CVw84KdI.mjs} +21 -7
  108. package/dist/{messages-B9Oba7sq.mjs → messages-CY8_RyFE.mjs} +15 -1
  109. package/dist/{messages-B5hdXZwA.mjs → messages-CZygwLwM.mjs} +15 -1
  110. package/dist/{messages-CVeWVKsV.mjs → messages-CnwibSvh.mjs} +14 -0
  111. package/dist/{messages-DbVquYKN.mjs → messages-CqWJcCbY.mjs} +14 -0
  112. package/dist/{chunks/messages-Dg92dXZ5.mjs → messages-CvGLfqmV.mjs} +14 -0
  113. package/dist/{chunks/messages-AHESHJm_.mjs → messages-CzTufCHu.mjs} +14 -0
  114. package/dist/{messages-Cj-t1bdy.mjs → messages-CznZadDf.mjs} +15 -1
  115. package/dist/{messages-DMQIHGRj.mjs → messages-D-ZtY5v0.mjs} +14 -0
  116. package/dist/{messages-rRSHQDCX.mjs → messages-D1Hv8XGo.mjs} +14 -0
  117. package/dist/{messages-0tDXLuyH.mjs → messages-D5C3J9qr.mjs} +15 -1
  118. package/dist/{messages-RvMHb2Ht.mjs → messages-D5iv1Kox.mjs} +16 -2
  119. package/dist/{chunks/messages-zSzDzXej.mjs → messages-DBRw-7Zc.mjs} +16 -2
  120. package/dist/{messages-CeCjVKMW.mjs → messages-DBn76jVV.mjs} +16 -2
  121. package/dist/{messages-C7I_AVH2.mjs → messages-DJDG55Vq.mjs} +16 -2
  122. package/dist/{chunks/messages-DDLgIPDF.mjs → messages-DLfR5bMd.mjs} +16 -2
  123. package/dist/{chunks/messages-CbhuIWRJ.mjs → messages-DT4dP5uK.mjs} +15 -1
  124. package/dist/{messages-B66ZSDCJ.mjs → messages-DhLKYm2j.mjs} +15 -1
  125. package/dist/{chunks/messages-BPqWKx5Z.mjs → messages-Diu6jAaR.mjs} +17 -3
  126. package/dist/{chunks/messages-BA0rcTCY.mjs → messages-DnIhyAJk.mjs} +18 -4
  127. package/dist/{messages-DV6shA9b.mjs → messages-DnXLrlHh.mjs} +14 -0
  128. package/dist/{messages-DcKOuncK.mjs → messages-DprmQg6V.mjs} +16 -2
  129. package/dist/{chunks/messages-CJoBtXU6.mjs → messages-DqM1LFg5.mjs} +14 -0
  130. package/dist/{messages-Cyi2AMmz.mjs → messages-DvFLX36Q.mjs} +25 -11
  131. package/dist/{messages-DnbbyJT3.mjs → messages-Dz9L52ol.mjs} +16 -2
  132. package/dist/{messages-GC2PhgV3.mjs → messages-Dzwxv9v1.mjs} +23 -9
  133. package/dist/{messages-Q4kc_ZtL.mjs → messages-JELdtT6E.mjs} +15 -1
  134. package/dist/{chunks/messages-DY94ykcE.mjs → messages-LPVfA-8K.mjs} +14 -0
  135. package/dist/{chunks/messages-Cr-RJ7YB.mjs → messages-O5tQus_0.mjs} +14 -0
  136. package/dist/{messages-CbMyJSzS.mjs → messages-Q7AO_FLv.mjs} +17 -3
  137. package/dist/{messages-2_xedlYw.mjs → messages-R3hUSvr3.mjs} +15 -1
  138. package/dist/{chunks/messages-CUy1vn-b.mjs → messages-Xq8UmkVs.mjs} +14 -0
  139. package/dist/{messages-BBJgd5jG.mjs → messages-Z9nEU2xK.mjs} +16 -2
  140. package/dist/{messages-Cm9aLHeX.mjs → messages-_ErNTNhk.mjs} +15 -1
  141. package/dist/{messages-JZUhXTuV.mjs → messages-_ncGrKHh.mjs} +16 -2
  142. package/dist/{messages-ftMcCEuO.mjs → messages-kep5wtm4.mjs} +15 -1
  143. package/dist/{messages-DteYq0rv.mjs → messages-uKX8WBaD.mjs} +16 -2
  144. package/dist/{messages-Bdv-IkfG.mjs → messages-w7v1GNaE.mjs} +15 -1
  145. package/dist/tools.mjs +2005 -1267
  146. package/dist/vendor.LICENSE.txt +1 -1
  147. package/package.json +15 -14
  148. package/src/cli/commands/migration.ts +16 -0
  149. package/src/cli/commands/migrationContent.ts +6 -0
  150. package/src/cli/index.ts +47 -0
  151. package/src/cli/utils/output.ts +10 -0
  152. package/src/components/block-tunes/block-tune-delete.ts +3 -2
  153. package/src/components/blocks.ts +23 -6
  154. package/src/components/constants/data-attributes.ts +2 -0
  155. package/src/components/i18n/locales/am/messages.json +15 -1
  156. package/src/components/i18n/locales/ar/messages.json +14 -0
  157. package/src/components/i18n/locales/az/messages.json +14 -0
  158. package/src/components/i18n/locales/bg/messages.json +14 -0
  159. package/src/components/i18n/locales/bn/messages.json +25 -11
  160. package/src/components/i18n/locales/bs/messages.json +15 -1
  161. package/src/components/i18n/locales/cs/messages.json +14 -0
  162. package/src/components/i18n/locales/da/messages.json +14 -0
  163. package/src/components/i18n/locales/de/messages.json +14 -0
  164. package/src/components/i18n/locales/dv/messages.json +15 -1
  165. package/src/components/i18n/locales/el/messages.json +15 -1
  166. package/src/components/i18n/locales/en/messages.json +14 -0
  167. package/src/components/i18n/locales/es/messages.json +14 -0
  168. package/src/components/i18n/locales/et/messages.json +14 -0
  169. package/src/components/i18n/locales/fa/messages.json +15 -1
  170. package/src/components/i18n/locales/fi/messages.json +15 -1
  171. package/src/components/i18n/locales/fil/messages.json +20 -6
  172. package/src/components/i18n/locales/fr/messages.json +15 -1
  173. package/src/components/i18n/locales/gu/messages.json +15 -1
  174. package/src/components/i18n/locales/he/messages.json +14 -0
  175. package/src/components/i18n/locales/hi/messages.json +23 -9
  176. package/src/components/i18n/locales/hr/messages.json +14 -0
  177. package/src/components/i18n/locales/hu/messages.json +14 -0
  178. package/src/components/i18n/locales/hy/messages.json +16 -2
  179. package/src/components/i18n/locales/id/messages.json +19 -5
  180. package/src/components/i18n/locales/it/messages.json +14 -0
  181. package/src/components/i18n/locales/ja/messages.json +14 -0
  182. package/src/components/i18n/locales/ka/messages.json +15 -1
  183. package/src/components/i18n/locales/km/messages.json +16 -2
  184. package/src/components/i18n/locales/kn/messages.json +16 -2
  185. package/src/components/i18n/locales/ko/messages.json +14 -0
  186. package/src/components/i18n/locales/ku/messages.json +16 -2
  187. package/src/components/i18n/locales/lo/messages.json +15 -1
  188. package/src/components/i18n/locales/lt/messages.json +15 -1
  189. package/src/components/i18n/locales/lv/messages.json +15 -1
  190. package/src/components/i18n/locales/mk/messages.json +16 -2
  191. package/src/components/i18n/locales/ml/messages.json +16 -2
  192. package/src/components/i18n/locales/mn/messages.json +16 -2
  193. package/src/components/i18n/locales/mr/messages.json +24 -10
  194. package/src/components/i18n/locales/ms/messages.json +17 -3
  195. package/src/components/i18n/locales/my/messages.json +16 -2
  196. package/src/components/i18n/locales/ne/messages.json +24 -10
  197. package/src/components/i18n/locales/nl/messages.json +15 -1
  198. package/src/components/i18n/locales/no/messages.json +16 -2
  199. package/src/components/i18n/locales/pa/messages.json +15 -1
  200. package/src/components/i18n/locales/pl/messages.json +14 -0
  201. package/src/components/i18n/locales/ps/messages.json +17 -3
  202. package/src/components/i18n/locales/pt/messages.json +14 -0
  203. package/src/components/i18n/locales/ro/messages.json +15 -1
  204. package/src/components/i18n/locales/ru/messages.json +14 -0
  205. package/src/components/i18n/locales/sd/messages.json +16 -2
  206. package/src/components/i18n/locales/si/messages.json +23 -9
  207. package/src/components/i18n/locales/sk/messages.json +15 -1
  208. package/src/components/i18n/locales/sl/messages.json +16 -2
  209. package/src/components/i18n/locales/sq/messages.json +16 -2
  210. package/src/components/i18n/locales/sr/messages.json +16 -2
  211. package/src/components/i18n/locales/sv/messages.json +16 -2
  212. package/src/components/i18n/locales/sw/messages.json +16 -2
  213. package/src/components/i18n/locales/ta/messages.json +21 -7
  214. package/src/components/i18n/locales/te/messages.json +40 -26
  215. package/src/components/i18n/locales/th/messages.json +19 -5
  216. package/src/components/i18n/locales/tr/messages.json +15 -1
  217. package/src/components/i18n/locales/ug/messages.json +16 -2
  218. package/src/components/i18n/locales/uk/messages.json +15 -1
  219. package/src/components/i18n/locales/ur/messages.json +15 -1
  220. package/src/components/i18n/locales/vi/messages.json +25 -11
  221. package/src/components/i18n/locales/yi/messages.json +16 -2
  222. package/src/components/i18n/locales/zh/messages.json +15 -1
  223. package/src/components/icons/index.ts +104 -83
  224. package/src/components/modules/api/blocks.ts +35 -2
  225. package/src/components/modules/api/history.ts +64 -0
  226. package/src/components/modules/api/index.ts +2 -0
  227. package/src/components/modules/api/readonly.ts +11 -1
  228. package/src/components/modules/blockEvents/composers/markdownShortcuts.ts +12 -1
  229. package/src/components/modules/blockManager/blockManager.ts +7 -0
  230. package/src/components/modules/blockManager/operations.ts +3 -2
  231. package/src/components/modules/blockManager/types.ts +3 -1
  232. package/src/components/modules/blockManager/yjs-sync.ts +12 -2
  233. package/src/components/modules/index.ts +3 -0
  234. package/src/components/modules/normalizeInlineImages.ts +263 -0
  235. package/src/components/modules/readonly.ts +11 -0
  236. package/src/components/modules/rectangleSelection.ts +19 -3
  237. package/src/components/modules/saver.ts +7 -3
  238. package/src/components/modules/toolbar/blockSettings.ts +3 -3
  239. package/src/components/modules/toolbar/index.ts +72 -14
  240. package/src/components/modules/toolbar/plus-button.ts +24 -3
  241. package/src/components/modules/toolbar/settings-toggler.ts +3 -5
  242. package/src/components/modules/ui.ts +46 -68
  243. package/src/components/modules/uiControllers/controllers/blockHover.ts +49 -61
  244. package/src/components/modules/uiControllers/controllers/keyboard.ts +17 -11
  245. package/src/components/modules/uiControllers/handlers/click.ts +0 -12
  246. package/src/components/modules/yjs/index.ts +23 -0
  247. package/src/components/ui/toolbox.ts +41 -6
  248. package/src/components/utils/placeholder.ts +16 -0
  249. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +2 -1
  250. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +6 -1
  251. package/src/components/utils/popover/index.ts +1 -0
  252. package/src/components/utils/popover/popover-abstract.ts +11 -1
  253. package/src/components/utils/popover/popover-desktop.ts +27 -8
  254. package/src/components/utils/popover/popover-registry.ts +188 -0
  255. package/src/components/utils/sanitizer.ts +23 -2
  256. package/src/components/utils/tooltip.ts +2 -24
  257. package/src/styles/main.css +22 -0
  258. package/src/tools/paragraph/index.ts +12 -4
  259. package/src/tools/table/data-normalizer.ts +1 -0
  260. package/src/tools/table/index.ts +283 -346
  261. package/src/tools/table/table-add-controls.ts +353 -47
  262. package/src/tools/table/table-cell-blocks.ts +95 -7
  263. package/src/tools/table/table-cell-selection.ts +648 -0
  264. package/src/tools/table/table-core.ts +21 -32
  265. package/src/tools/table/table-grip-visuals.ts +96 -0
  266. package/src/tools/table/table-heading-toggle.ts +127 -0
  267. package/src/tools/table/table-operations.ts +475 -0
  268. package/src/tools/table/table-resize.ts +27 -6
  269. package/src/tools/table/table-restrictions.ts +64 -0
  270. package/src/tools/table/table-row-col-action-handler.ts +190 -0
  271. package/src/tools/table/table-row-col-controls.ts +265 -211
  272. package/src/tools/table/table-row-col-drag.ts +4 -4
  273. package/src/tools/table/table-row-col-popover.ts +225 -0
  274. package/src/tools/table/types.ts +4 -0
  275. package/src/types-internal/blok-modules.d.ts +2 -0
  276. package/types/api/blocks.d.ts +8 -0
  277. package/types/api/history.d.ts +33 -0
  278. package/types/api/index.d.ts +1 -0
  279. package/types/api/readonly.d.ts +12 -2
  280. package/types/index.d.ts +10 -0
  281. package/types/tools/table.d.ts +67 -0
  282. package/types/tools-entry.d.ts +4 -0
  283. package/types/utils/popover/popover-item.d.ts +6 -0
  284. package/types/utils/popover/popover.d.ts +7 -0
  285. package/dist/chunks/messages-CySyfkMU.mjs +0 -48
  286. package/dist/messages-CySyfkMU.mjs +0 -48
@@ -90,10 +90,10 @@ export class PlusButtonHandler {
90
90
  const plusButton = $.make('div', [
91
91
  twJoin(
92
92
  // Base toolbox-button styles
93
- 'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
93
+ 'text-text-secondary cursor-pointer w-6 h-6 rounded-[5px] inline-flex justify-center items-center select-none',
94
94
  'shrink-0',
95
95
  // SVG sizing
96
- '[&_svg]:h-6 [&_svg]:w-6',
96
+ '[&_svg]:h-[22px] [&_svg]:w-[22px]',
97
97
  // Hover (can-hover)
98
98
  'can-hover:hover:bg-bg-light',
99
99
  // Keep hover background when toolbox is open
@@ -173,12 +173,33 @@ export class PlusButtonHandler {
173
173
  const hoveredBlockIndex = hoveredBlock !== null
174
174
  ? BlockManager.getBlockIndex(hoveredBlock)
175
175
  : BlockManager.currentBlockIndex;
176
- const insertIndex = insertAbove ? hoveredBlockIndex : hoveredBlockIndex + 1;
176
+ const baseInsertIndex = insertAbove ? hoveredBlockIndex : hoveredBlockIndex + 1;
177
+
178
+ // When inserting below, skip past any blocks nested inside another block's
179
+ // DOM (e.g. paragraph blocks inside table cells). The block array may
180
+ // interleave nested blocks from multiple parents, so check whether each
181
+ // block's holder lives inside any block-wrapper ancestor rather than only
182
+ // the hovered block's holder.
183
+ const blocksAfterInsert = BlockManager.blocks.slice(baseInsertIndex);
184
+ const isNested = (block: Block): boolean =>
185
+ block.holder.parentElement?.closest('[data-blok-testid="block-wrapper"]') !== null;
186
+ const firstNonNestedOffset = !insertAbove && hoveredBlock && blocksAfterInsert.length > 0
187
+ ? blocksAfterInsert.findIndex((block) => !isNested(block))
188
+ : 0;
189
+ const insertIndex = baseInsertIndex + (firstNonNestedOffset === -1 ? blocksAfterInsert.length : firstNonNestedOffset);
177
190
 
178
191
  const targetBlock = isEmptyParagraph || startsWithSlash
179
192
  ? hoveredBlock
180
193
  : BlockManager.insertDefaultBlockAtIndex(insertIndex, true);
181
194
 
195
+ // The DOM insertion may place the new block's holder inside a nested
196
+ // container (e.g. a table cell) because the previous block in the array
197
+ // is inside another block's DOM. Move the holder to be a sibling after
198
+ // the hovered block so it becomes a top-level block.
199
+ if (targetBlock !== hoveredBlock && isNested(targetBlock)) {
200
+ hoveredBlock?.holder.after(targetBlock.holder);
201
+ }
202
+
182
203
  // Insert "/" or position caret after existing one
183
204
  if (startsWithSlash) {
184
205
  Caret.setToBlock(targetBlock, Caret.positions.DEFAULT, 1);
@@ -113,9 +113,9 @@ export class SettingsTogglerHandler {
113
113
  const settingsToggler = $.make('span', [
114
114
  twJoin(
115
115
  // Base toolbox-button styles
116
- 'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
116
+ 'text-text-secondary cursor-pointer w-[22px] h-6 rounded-[5px] inline-flex justify-center items-center select-none',
117
117
  // SVG sizing
118
- '[&_svg]:h-6 [&_svg]:w-6',
118
+ '[&_svg]:h-[22px] [&_svg]:w-[22px]',
119
119
  // Active state
120
120
  'active:cursor-grabbing',
121
121
  // Hover (can-hover)
@@ -127,9 +127,7 @@ export class SettingsTogglerHandler {
127
127
  'group-data-[blok-block-settings-opened=true]:can-hover:hover:cursor-pointer',
128
128
  // Mobile styles (static positioning with overlay-pane appearance)
129
129
  'mobile:bg-white mobile:border mobile:border-[#e8e8eb] mobile:shadow-overlay-pane mobile:rounded-[6px] mobile:z-[2]',
130
- 'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile',
131
- // Not-mobile styles
132
- 'not-mobile:w-6'
130
+ 'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile'
133
131
  ),
134
132
  'group-data-[blok-dragging=true]:cursor-grabbing',
135
133
  ], {
@@ -30,6 +30,7 @@ interface UINodes extends Record<string, unknown> {
30
30
  holder: HTMLElement;
31
31
  wrapper: HTMLElement;
32
32
  redactor: HTMLElement;
33
+ bottomZone: HTMLElement;
33
34
  }
34
35
 
35
36
  /**
@@ -191,12 +192,11 @@ export class UI extends Module<UINodes> {
191
192
  this.selectionController.setWrapperElement(this.nodes.wrapper);
192
193
 
193
194
  /**
194
- * Block hover controller needs content rect getter
195
+ * Block hover controller detects hover over blocks and finds nearest block
195
196
  */
196
197
  this.blockHoverController = new BlockHoverController({
197
198
  config: this.config,
198
199
  eventsDispatcher: this.eventsDispatcher,
199
- contentRectGetter: () => this.contentRect,
200
200
  });
201
201
  this.blockHoverController.state = this.Blok;
202
202
 
@@ -452,11 +452,15 @@ export class UI extends Module<UINodes> {
452
452
  }
453
453
 
454
454
  /**
455
- * Set customizable bottom zone height
455
+ * Create dedicated bottom zone element
456
456
  */
457
- this.nodes.redactor.style.paddingBottom = this.config.minHeight + 'px';
457
+ this.nodes.bottomZone = $.make('div', ['cursor-text']);
458
+ this.nodes.bottomZone.setAttribute('data-blok-bottom-zone', '');
459
+ this.nodes.bottomZone.setAttribute('data-blok-testid', 'bottom-zone');
460
+ this.nodes.bottomZone.style.minHeight = this.config.minHeight + 'px';
458
461
 
459
462
  this.nodes.wrapper.appendChild(this.nodes.redactor);
463
+ this.nodes.wrapper.appendChild(this.nodes.bottomZone);
460
464
  this.nodes.holder.appendChild(this.nodes.wrapper);
461
465
 
462
466
  this.bindReadOnlyInsensitiveListeners();
@@ -541,7 +545,16 @@ export class UI extends Module<UINodes> {
541
545
  */
542
546
  private bindReadOnlySensitiveListeners(): void {
543
547
  /**
544
- * Redactor click handler for bottom zone clicks
548
+ * Bottom zone click handler creates new block when clicking below last block
549
+ */
550
+ this.readOnlyMutableListeners.on(this.nodes.bottomZone, 'click', (event: Event) => {
551
+ if (event instanceof MouseEvent) {
552
+ this.bottomZoneClicked(event);
553
+ }
554
+ }, false);
555
+
556
+ /**
557
+ * Redactor click handler for Ctrl+click anchor navigation
545
558
  */
546
559
  this.readOnlyMutableListeners.on(this.nodes.redactor, 'click', (event: Event) => {
547
560
  if (event instanceof MouseEvent) {
@@ -610,98 +623,63 @@ export class UI extends Module<UINodes> {
610
623
  }
611
624
 
612
625
  /**
613
- * All clicks on the redactor zone
614
- * @param {MouseEvent} event - click event
615
- * @description
616
- * - By clicks on the Blok's bottom zone:
617
- * - if last Block is empty, set a Caret to this
618
- * - otherwise, add a new empty Block and set a Caret to that
626
+ * Handle click on the bottom zone element below the last block.
627
+ * Creates a new default block if needed, focuses the last block, and opens the toolbar.
619
628
  */
620
- private redactorClicked(event: MouseEvent): void {
629
+ private bottomZoneClicked(event: MouseEvent): void {
621
630
  if (!Selection.isCollapsed) {
622
631
  return;
623
632
  }
624
633
 
625
- /**
626
- * case when user clicks on anchor element
627
- * if it is clicked via ctrl key, then we open new window with url
628
- */
629
- const element = event.target as Element;
630
- const ctrlKey = event.metaKey || event.ctrlKey;
631
- const shouldOpenAnchorInNewTab = $.isAnchor(element) && ctrlKey;
634
+ const { BlockSelection, BlockManager, Caret, Toolbar } = this.Blok;
632
635
 
633
- if (!shouldOpenAnchorInNewTab) {
634
- this.processBottomZoneClick(event);
636
+ if (BlockSelection.anyBlockSelected) {
637
+ return;
638
+ }
635
639
 
640
+ if (!BlockManager.lastBlock) {
636
641
  return;
637
642
  }
638
643
 
639
644
  event.stopImmediatePropagation();
640
645
  event.stopPropagation();
641
646
 
642
- const href = element.getAttribute('href');
643
-
644
- if (!href) {
645
- return;
647
+ /**
648
+ * Insert a default-block at the bottom if:
649
+ * - last-block is not a default-block (Text)
650
+ * - Or, default-block is not empty
651
+ */
652
+ if (!BlockManager.lastBlock.tool.isDefault || !BlockManager.lastBlock.isEmpty) {
653
+ BlockManager.insertAtEnd();
646
654
  }
647
655
 
648
- const validUrl = getValidUrl(href);
649
-
650
- openTab(validUrl);
656
+ Caret.setToTheLastBlock();
657
+ Toolbar.moveAndOpen(BlockManager.lastBlock);
651
658
  }
652
659
 
653
660
  /**
654
- * Check if user clicks on the Blok's bottom zone:
655
- * - set caret to the last block
656
- * - or add new empty block
657
- * @param event - click event
661
+ * Handle Ctrl+click on anchor elements to open in new tab
658
662
  */
659
- private processBottomZoneClick(event: MouseEvent): void {
660
- const lastBlock = this.Blok.BlockManager.getBlockByIndex(-1);
661
-
662
- if (lastBlock === undefined) {
663
- return;
664
- }
665
-
666
- const lastBlockBottomCoord = $.offset(lastBlock.holder).bottom;
667
- const clickedCoord = event.pageY;
668
- const { BlockSelection } = this.Blok;
669
- const isClickedBottom = event.target instanceof Element &&
670
- event.target.isEqualNode(this.nodes.redactor) &&
671
- /**
672
- * If there is cross block selection started, target will be equal to redactor so we need additional check
673
- */
674
- !BlockSelection.anyBlockSelected &&
675
-
676
- /**
677
- * Prevent caret jumping (to last block) when clicking between blocks
678
- */
679
- lastBlockBottomCoord < clickedCoord;
663
+ private redactorClicked(event: MouseEvent): void {
664
+ const element = event.target as Element;
665
+ const ctrlKey = event.metaKey || event.ctrlKey;
680
666
 
681
- if (!isClickedBottom) {
667
+ if (!$.isAnchor(element) || !ctrlKey) {
682
668
  return;
683
669
  }
684
670
 
685
671
  event.stopImmediatePropagation();
686
672
  event.stopPropagation();
687
673
 
688
- const { BlockManager, Caret, Toolbar } = this.Blok;
674
+ const href = element.getAttribute('href');
689
675
 
690
- /**
691
- * Insert a default-block at the bottom if:
692
- * - last-block is not a default-block (Text)
693
- * to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table)
694
- * - Or, default-block is not empty
695
- */
696
- if (!BlockManager.lastBlock?.tool.isDefault || !BlockManager.lastBlock?.isEmpty) {
697
- BlockManager.insertAtEnd();
676
+ if (!href) {
677
+ return;
698
678
  }
699
679
 
700
- /**
701
- * Set the caret and toolbar to empty Block
702
- */
703
- Caret.setToTheLastBlock();
704
- Toolbar.moveAndOpen(BlockManager.lastBlock);
680
+ const validUrl = getValidUrl(href);
681
+
682
+ openTab(validUrl);
705
683
  }
706
684
 
707
685
  /**
@@ -5,20 +5,15 @@ import { throttle } from '../../../utils';
5
5
  import { Controller } from './_base';
6
6
 
7
7
  /**
8
- * BlockHoverController detects when user hovers over blocks, including extended hover zone.
8
+ * BlockHoverController detects when user hovers over blocks or finds nearest block.
9
9
  *
10
10
  * Responsibilities:
11
11
  * - Listen to mousemove events (throttled)
12
- * - Find block by element hit or extended zone
12
+ * - Find block by element hit or nearest by Y distance
13
13
  * - Emit BlockHovered events
14
14
  * - Track last hovered block to avoid duplicate events
15
15
  */
16
16
  export class BlockHoverController extends Controller {
17
- /**
18
- * Getter function for content rect
19
- */
20
- private contentRectGetter: () => DOMRect;
21
-
22
17
  /**
23
18
  * Used to not emit the same block multiple times to the 'block-hovered' event on every mousemove.
24
19
  * Stores block ID to ensure consistent comparison regardless of how the block was detected.
@@ -42,10 +37,8 @@ export class BlockHoverController extends Controller {
42
37
  constructor(options: {
43
38
  config: Controller['config'];
44
39
  eventsDispatcher: Controller['eventsDispatcher'];
45
- contentRectGetter: () => DOMRect;
46
40
  }) {
47
41
  super(options);
48
- this.contentRectGetter = options.contentRectGetter;
49
42
  }
50
43
 
51
44
  /**
@@ -71,38 +64,23 @@ export class BlockHoverController extends Controller {
71
64
  return;
72
65
  }
73
66
 
74
- const hoveredBlockElement = (event.target as Element | null)?.closest('[data-blok-testid="block-wrapper"]');
67
+ const closestBlockWrapper = (event.target as Element | null)?.closest('[data-blok-testid="block-wrapper"]');
75
68
 
76
69
  /**
77
- * If no block element found directly, try the extended hover zone
70
+ * If the hovered block is inside a table cell, resolve to the table block instead.
71
+ * Without this, the toolbar hides itself for nested cell blocks and the table's
72
+ * block tune settings become inaccessible.
78
73
  */
79
- const zoneBlock = !hoveredBlockElement
80
- ? this.findBlockInHoverZone(event.clientX, event.clientY)
81
- : null;
82
-
83
- if (zoneBlock !== null && this.blockHoveredState.lastHoveredBlockId !== zoneBlock.id) {
84
- /**
85
- * Emit the event but DON'T set lastHoveredBlockId for hover zone events.
86
- * This allows the event to be emitted again when the mouse enters the actual block element,
87
- * which is important for proper toolbar positioning after cross-block selection.
88
- */
89
- this.eventsDispatcher.emit(BlockHovered, {
90
- block: zoneBlock,
91
- target: zoneBlock.holder,
92
- });
93
- }
94
-
95
- if (zoneBlock !== null) {
96
- return;
97
- }
74
+ const hoveredBlockElement = closestBlockWrapper?.closest('[data-blok-table-cell-blocks]')
75
+ ? closestBlockWrapper.closest('[data-blok-table-cell-blocks]')?.closest('[data-blok-testid="block-wrapper"]') ?? null
76
+ : closestBlockWrapper;
98
77
 
78
+ /**
79
+ * If no block element found directly, find the nearest block by Y distance
80
+ */
99
81
  if (!hoveredBlockElement) {
100
- /**
101
- * When no block is found (mouse left the editor area), reset the hover state.
102
- * This allows hover events to be emitted again when re-entering a block,
103
- * which is important after cross-block selection completes.
104
- */
105
- this.blockHoveredState.lastHoveredBlockId = null;
82
+ this.emitNearestBlockHovered(event.clientY);
83
+
106
84
  return;
107
85
  }
108
86
 
@@ -134,9 +112,8 @@ export class BlockHoverController extends Controller {
134
112
  );
135
113
 
136
114
  /**
137
- * Listen on document to detect hover in the extended zone
138
- * which is outside the wrapper's bounds.
139
- * We filter events to only process those over the editor or in the hover zone.
115
+ * Listen on document to detect hover anywhere on the page.
116
+ * When cursor is not directly on a block, finds the nearest block by Y distance.
140
117
  */
141
118
  this.readOnlyMutableListeners.on(document, 'mousemove', (event: Event) => {
142
119
  throttledHandleBlockHovered(event);
@@ -146,38 +123,49 @@ export class BlockHoverController extends Controller {
146
123
  }
147
124
 
148
125
  /**
149
- * Finds a block by vertical position when cursor is in the hover zone.
150
- * The hover zone extends indefinitely on both sides of the content (left and right),
151
- * allowing the toolbar to follow hover anywhere outside the content area horizontally.
152
- * @param clientX - Cursor X position
126
+ * Finds and emits a BlockHovered event for the nearest block by Y distance.
127
+ * Deduplicates by lastHoveredBlockId to avoid redundant events.
153
128
  * @param clientY - Cursor Y position
154
- * @returns Block at the vertical position, or null if not in hover zone or no block found
155
129
  */
156
- private findBlockInHoverZone(clientX: number, clientY: number): Block | null {
157
- const contentRect = this.contentRectGetter();
130
+ private emitNearestBlockHovered(clientY: number): void {
131
+ const nearestBlock = this.findNearestBlock(clientY);
158
132
 
159
- /**
160
- * Check if cursor is outside the content area horizontally (either left OR right side).
161
- * The zone extends indefinitely on both sides, not limited to HOVER_ZONE_SIZE.
162
- */
163
- const isInHoverZone = clientX < contentRect.left || clientX > contentRect.right;
133
+ if (nearestBlock === null || this.blockHoveredState.lastHoveredBlockId === nearestBlock.id) {
134
+ return;
135
+ }
164
136
 
165
- if (!isInHoverZone) {
137
+ this.blockHoveredState.lastHoveredBlockId = nearestBlock.id;
138
+
139
+ this.eventsDispatcher.emit(BlockHovered, {
140
+ block: nearestBlock,
141
+ target: nearestBlock.holder,
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Finds the nearest block by vertical distance to cursor position.
147
+ * Returns the block whose vertical center is closest to the cursor Y position.
148
+ * If cursor is above all blocks, returns the first block.
149
+ * If cursor is below all blocks, returns the last block.
150
+ * @param clientY - Cursor Y position
151
+ * @returns Nearest block, or null if no blocks exist
152
+ */
153
+ private findNearestBlock(clientY: number): Block | null {
154
+ const blocks = this.Blok.BlockManager.blocks;
155
+
156
+ if (blocks.length === 0) {
166
157
  return null;
167
158
  }
168
159
 
169
- /**
170
- * Find block by Y position
171
- */
172
- for (const block of this.Blok.BlockManager.blocks) {
160
+ const result = blocks.reduce<{ block: Block; distance: number }>((nearest, block) => {
173
161
  const rect = block.holder.getBoundingClientRect();
162
+ const centerY = (rect.top + rect.bottom) / 2;
163
+ const distance = Math.abs(clientY - centerY);
174
164
 
175
- if (clientY >= rect.top && clientY <= rect.bottom) {
176
- return block;
177
- }
178
- }
165
+ return distance < nearest.distance ? { block, distance } : nearest;
166
+ }, { block: blocks[0], distance: Infinity });
179
167
 
180
- return null;
168
+ return result.block;
181
169
  }
182
170
 
183
171
  /**
@@ -1,4 +1,5 @@
1
1
  import { SelectionUtils as Selection } from '../../../selection/index';
2
+ import { PopoverRegistry } from '../../../utils/popover/popover-registry';
2
3
  import { KEYS_REQUIRING_CARET_CAPTURE } from '../constants';
3
4
 
4
5
  import { Controller } from './_base';
@@ -258,28 +259,33 @@ export class KeyboardController extends Controller {
258
259
  }
259
260
 
260
261
  /**
261
- * Close BlockSettings first if it's open, regardless of selection state.
262
- * This prevents navigation mode from being enabled when the user closes block tunes with Escape.
262
+ * Toolbox needs specific Escape handling for caret restoration,
263
+ * so check it before the registry
263
264
  */
264
- if (this.Blok.BlockSettings.opened) {
265
- this.Blok.BlockSettings.close();
265
+ if (this.Blok.Toolbar.toolbox.opened) {
266
+ this.Blok.Toolbar.toolbox.close();
267
+ this.Blok.BlockManager.currentBlock &&
268
+ this.Blok.Caret.setToBlock(this.Blok.BlockManager.currentBlock, this.Blok.Caret.positions.END);
266
269
 
267
270
  return;
268
271
  }
269
272
 
270
273
  /**
271
- * Clear blocks selection by ESC (but not when entering navigation mode)
274
+ * Close any open popover via registry (BlockSettings, table grips, future popovers).
275
+ * Must come before block selection clearing to prevent navigation mode
276
+ * from being enabled when closing block settings.
272
277
  */
273
- if (this.Blok.BlockSelection.anyBlockSelected) {
274
- this.Blok.BlockSelection.clearSelection(event);
278
+ if (PopoverRegistry.instance.hasOpenPopovers()) {
279
+ PopoverRegistry.instance.closeTopmost();
275
280
 
276
281
  return;
277
282
  }
278
283
 
279
- if (this.Blok.Toolbar.toolbox.opened) {
280
- this.Blok.Toolbar.toolbox.close();
281
- this.Blok.BlockManager.currentBlock &&
282
- this.Blok.Caret.setToBlock(this.Blok.BlockManager.currentBlock, this.Blok.Caret.positions.END);
284
+ /**
285
+ * Clear blocks selection by ESC (but not when entering navigation mode)
286
+ */
287
+ if (this.Blok.BlockSelection.anyBlockSelected) {
288
+ this.Blok.BlockSelection.clearSelection(event);
283
289
 
284
290
  return;
285
291
  }
@@ -68,8 +68,6 @@ export const analyzeClickContext = (
68
68
  *
69
69
  * Responsibilities:
70
70
  * - Clear current block when clicking outside editor
71
- * - Close BlockSettings when appropriate
72
- * - Move toolbar when clicking in redactor after closing settings
73
71
  * - Clear block selection
74
72
  * - Close inline toolbar
75
73
  *
@@ -108,16 +106,6 @@ export const createDocumentClickedHandler = (
108
106
  Blok.BlockManager.unsetCurrentBlock();
109
107
  }
110
108
 
111
- const shouldCloseBlockSettings = Blok.BlockSettings.opened && !context.doNotProcess;
112
- if (shouldCloseBlockSettings) {
113
- Blok.BlockSettings.close();
114
- }
115
-
116
- if (shouldCloseBlockSettings && context.clickedInsideRedactor) {
117
- const clickedBlock = Blok.BlockManager.getBlockByChildNode(context.target);
118
- Blok.Toolbar.moveAndOpen(clickedBlock);
119
- }
120
-
121
109
  /**
122
110
  * Clear Selection if user clicked somewhere
123
111
  * But preserve selection when clicking on block settings toggler or inside block settings
@@ -189,6 +189,29 @@ export class YjsManager extends Module {
189
189
  this.undoHistory.redo();
190
190
  }
191
191
 
192
+ /**
193
+ * Check if undo is available.
194
+ * @returns true if undo is available
195
+ */
196
+ public canUndo(): boolean {
197
+ return this.undoHistory.canUndo();
198
+ }
199
+
200
+ /**
201
+ * Check if redo is available.
202
+ * @returns true if redo is available
203
+ */
204
+ public canRedo(): boolean {
205
+ return this.undoHistory.canRedo();
206
+ }
207
+
208
+ /**
209
+ * Clear all history.
210
+ */
211
+ public clear(): void {
212
+ this.undoHistory.clear();
213
+ }
214
+
192
215
  /**
193
216
  * Stop capturing changes into current undo group.
194
217
  * Call this to force next change into a new undo entry.
@@ -1,5 +1,7 @@
1
+ import { RESTRICTED_TOOLS } from '../../tools/table/table-restrictions';
1
2
  import { Dom } from '../dom';
2
3
  import { BlokMobileLayoutToggled } from '../events';
4
+ import { SelectionUtils } from '../selection';
3
5
  import type { BlockToolAdapter } from '../tools/block';
4
6
  import type { ToolsCollection } from '../tools/collection';
5
7
  import { beautifyShortcut, capitalize, isMobileScreen } from '../utils';
@@ -155,7 +157,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
155
157
 
156
158
  /**
157
159
  * Whether the toolbox was opened inside a table cell.
158
- * Used to restore the table item visibility on close.
160
+ * Used to restore restricted tool visibility on close.
159
161
  */
160
162
  private isInsideTableCell = false;
161
163
 
@@ -273,17 +275,28 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
273
275
  const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
274
276
 
275
277
  /**
276
- * Hide the table tool when the caret is inside a table cell
277
- * to prevent nested tables.
278
+ * Hide restricted tools (headers, tables) when the caret is inside a table cell.
278
279
  */
279
280
  this.isInsideTableCell = currentBlock !== undefined
280
281
  && currentBlock.holder.closest('[data-blok-table-cell-blocks]') !== null;
281
282
 
282
283
  if (this.isInsideTableCell) {
283
- this.popover?.toggleItemHiddenByName('table', true);
284
+ this.toggleRestrictedToolsHidden(true);
284
285
  }
285
286
 
286
287
  this.popover?.show();
288
+
289
+ /**
290
+ * When opening toolbox inside a table cell, position it at the caret
291
+ * instead of at the trigger element (which is outside the table).
292
+ * Must be called after show() so the popover is in the DOM.
293
+ */
294
+ if (this.isInsideTableCell && this.popover instanceof PopoverDesktop) {
295
+ const caretRect = SelectionUtils.rect;
296
+
297
+ this.popover.updatePosition(caretRect);
298
+ }
299
+
287
300
  this.opened = true;
288
301
  this.emit(ToolboxEvent.Opened);
289
302
  this.startListeningToBlockInput();
@@ -294,7 +307,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
294
307
  */
295
308
  public close(): void {
296
309
  if (this.isInsideTableCell) {
297
- this.popover?.toggleItemHiddenByName('table', false);
310
+ this.toggleRestrictedToolsHidden(false);
298
311
  this.isInsideTableCell = false;
299
312
  }
300
313
 
@@ -364,7 +377,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
364
377
  */
365
378
  private onPopoverClose = (): void => {
366
379
  if (this.isInsideTableCell) {
367
- this.popover?.toggleItemHiddenByName('table', false);
380
+ this.toggleRestrictedToolsHidden(false);
368
381
  this.isInsideTableCell = false;
369
382
  }
370
383
 
@@ -373,6 +386,28 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
373
386
  this.emit(ToolboxEvent.Closed);
374
387
  };
375
388
 
389
+ /**
390
+ * Toggles hidden state for all popover items belonging to restricted tools.
391
+ * Handles tools like header that have multiple entries with custom names (header-1, header-2, etc.)
392
+ * by matching item names that equal or start with a restricted tool name.
393
+ */
394
+ private toggleRestrictedToolsHidden(isHidden: boolean): void {
395
+ for (const item of this.toolboxItemsToBeDisplayed) {
396
+ if (!('name' in item) || item.name === undefined) {
397
+ continue;
398
+ }
399
+
400
+ const { name } = item;
401
+ const isRestricted = RESTRICTED_TOOLS.some(
402
+ restricted => name === restricted || name.startsWith(`${restricted}-`)
403
+ );
404
+
405
+ if (isRestricted) {
406
+ this.popover?.toggleItemHiddenByName(name, isHidden);
407
+ }
408
+ }
409
+ }
410
+
376
411
  /**
377
412
  * Returns list of tools that enables the Toolbox (by specifying the 'toolbox' getter)
378
413
  */
@@ -56,6 +56,22 @@ export const PLACEHOLDER_FOCUS_ONLY_CLASSES: string[] = [
56
56
  '[&[data-empty=true]:focus]:before:content-[attr(data-blok-placeholder-active)]',
57
57
  ];
58
58
 
59
+ /**
60
+ * Placeholder classes that show when the editor is empty (ancestor has data-blok-empty="true").
61
+ * Shows placeholder without requiring focus — used for initial empty-state hint.
62
+ * Uses data-blok-placeholder-active attribute for the placeholder text.
63
+ */
64
+ export const PLACEHOLDER_EMPTY_EDITOR_CLASSES: string[] = [
65
+ '[[data-blok-empty=true]_&]:empty:before:pointer-events-none',
66
+ '[[data-blok-empty=true]_&]:empty:before:text-gray-text',
67
+ '[[data-blok-empty=true]_&]:empty:before:cursor-text',
68
+ '[[data-blok-empty=true]_&]:empty:before:content-[attr(data-blok-placeholder-active)]',
69
+ '[[data-blok-empty=true]_&[data-empty=true]]:before:pointer-events-none',
70
+ '[[data-blok-empty=true]_&[data-empty=true]]:before:text-gray-text',
71
+ '[[data-blok-empty=true]_&[data-empty=true]]:before:cursor-text',
72
+ '[[data-blok-empty=true]_&[data-empty=true]]:before:content-[attr(data-blok-placeholder-active)]',
73
+ ];
74
+
59
75
  /**
60
76
  * Check if an element's content is empty
61
77
  *