@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
@@ -0,0 +1,623 @@
1
+ import type { CellContent, LegacyCellContent, TableData } from './types';
2
+ import { isCellWithBlocks } from './types';
3
+
4
+ /**
5
+ * Validate that a string is a safe CSS color value.
6
+ *
7
+ * Accepts:
8
+ * - 3/4/6/8-digit hex: #rgb, #rgba, #rrggbb, #rrggbbaa
9
+ * - rgb/rgba: rgb(r, g, b) / rgba(r, g, b, a)
10
+ * - hsl/hsla: hsl(h, s%, l%) / hsla(h, s%, l%, a)
11
+ * - The keyword "transparent"
12
+ */
13
+ const isValidCssColor = (value: string): boolean => {
14
+ // Hex: #rgb, #rgba, #rrggbb, #rrggbbaa
15
+ if (/^#[0-9a-f]{3,4}$/i.test(value) || /^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(value)) {
16
+ return true;
17
+ }
18
+
19
+ // rgb/rgba
20
+ if (/^rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(,\s*[\d.]+\s*)?\)$/i.test(value)) {
21
+ return true;
22
+ }
23
+
24
+ // hsl/hsla
25
+ if (/^hsla?\(\s*[\d.]+\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*(,\s*[\d.]+\s*)?\)$/i.test(value)) {
26
+ return true;
27
+ }
28
+
29
+ // Keywords
30
+ if (value === 'transparent') {
31
+ return true;
32
+ }
33
+
34
+ return false;
35
+ };
36
+
37
+ /**
38
+ * Pure data model for table state.
39
+ *
40
+ * Holds the 2D grid of cells, column widths, and metadata flags.
41
+ * Provides O(1) block-to-cell reverse lookup via an internal map.
42
+ *
43
+ * No DOM, no API — just data.
44
+ */
45
+ export class TableModel {
46
+ private contentGrid: CellContent[][];
47
+ private withHeadingsValue: boolean;
48
+ private withHeadingColumnValue: boolean;
49
+ private stretchedValue: boolean;
50
+ private colWidthsValue: number[] | undefined;
51
+ private initialColWidthValue: number | undefined;
52
+
53
+ /** O(1) reverse lookup: blockId -> { row, col } */
54
+ private blockCellMap: Map<string, { row: number; col: number }>;
55
+
56
+ constructor(data?: Partial<TableData>) {
57
+ this.withHeadingsValue = data?.withHeadings ?? false;
58
+ this.withHeadingColumnValue = data?.withHeadingColumn ?? false;
59
+ this.stretchedValue = data?.stretched ?? false;
60
+ this.colWidthsValue = data?.colWidths ? [...data.colWidths] : undefined;
61
+ this.initialColWidthValue = data?.initialColWidth;
62
+
63
+ this.contentGrid = this.normalizeContent(data?.content);
64
+ this.blockCellMap = new Map();
65
+ this.rebuildBlockCellMap();
66
+ }
67
+
68
+ // ─── Dimension getters ──────────────────────────────────────────
69
+
70
+ get rows(): number {
71
+ return this.contentGrid.length;
72
+ }
73
+
74
+ get cols(): number {
75
+ return this.contentGrid.length > 0 ? this.contentGrid[0].length : 0;
76
+ }
77
+
78
+ // ─── Metadata getters ────────────────────────────────────────────
79
+
80
+ get withHeadings(): boolean {
81
+ return this.withHeadingsValue;
82
+ }
83
+
84
+ get withHeadingColumn(): boolean {
85
+ return this.withHeadingColumnValue;
86
+ }
87
+
88
+ get stretched(): boolean {
89
+ return this.stretchedValue;
90
+ }
91
+
92
+ get colWidths(): number[] | undefined {
93
+ return this.colWidthsValue ? [...this.colWidthsValue] : undefined;
94
+ }
95
+
96
+ get initialColWidth(): number | undefined {
97
+ return this.initialColWidthValue;
98
+ }
99
+
100
+ // ─── Snapshot ───────────────────────────────────────────────────
101
+
102
+ /**
103
+ * Return a deep copy of the current state. Mutations to the returned
104
+ * object do not affect the model.
105
+ */
106
+ snapshot(): TableData {
107
+ const base: TableData = {
108
+ withHeadings: this.withHeadingsValue,
109
+ withHeadingColumn: this.withHeadingColumnValue,
110
+ stretched: this.stretchedValue,
111
+ content: this.contentGrid.map(row =>
112
+ row.map(c => {
113
+ const cell: CellContent = { blocks: [...c.blocks] };
114
+
115
+ if (c.color !== undefined) {
116
+ cell.color = c.color;
117
+ }
118
+
119
+ if (c.textColor !== undefined) {
120
+ cell.textColor = c.textColor;
121
+ }
122
+
123
+ return cell;
124
+ })
125
+ ),
126
+ };
127
+
128
+ if (this.colWidthsValue !== undefined) {
129
+ base.colWidths = [...this.colWidthsValue];
130
+ }
131
+
132
+ if (this.initialColWidthValue !== undefined) {
133
+ base.initialColWidth = this.initialColWidthValue;
134
+ }
135
+
136
+ return base;
137
+ }
138
+
139
+ // ─── Cell operations ────────────────────────────────────────────
140
+
141
+ /**
142
+ * O(1) lookup of the cell containing a given block.
143
+ */
144
+ findCellForBlock(blockId: string): { row: number; col: number } | null {
145
+ return this.blockCellMap.get(blockId) ?? null;
146
+ }
147
+
148
+ /**
149
+ * Append a block to the given cell.
150
+ */
151
+ addBlockToCell(row: number, col: number, blockId: string): void {
152
+ if (!this.isInBounds(row, col)) {
153
+ return;
154
+ }
155
+
156
+ // Enforce invariant 5: no block in more than one cell
157
+ const existing = this.blockCellMap.get(blockId);
158
+
159
+ if (existing) {
160
+ const oldCell = this.contentGrid[existing.row][existing.col];
161
+
162
+ oldCell.blocks = oldCell.blocks.filter(id => id !== blockId);
163
+ }
164
+
165
+ this.contentGrid[row][col].blocks.push(blockId);
166
+ this.blockCellMap.set(blockId, { row, col });
167
+ }
168
+
169
+ /**
170
+ * Remove a specific block from the given cell.
171
+ */
172
+ removeBlockFromCell(row: number, col: number, blockId: string): void {
173
+ if (!this.isInBounds(row, col)) {
174
+ return;
175
+ }
176
+
177
+ const cell = this.contentGrid[row][col];
178
+ const idx = cell.blocks.indexOf(blockId);
179
+
180
+ if (idx === -1) {
181
+ return;
182
+ }
183
+
184
+ cell.blocks.splice(idx, 1);
185
+ this.blockCellMap.delete(blockId);
186
+ }
187
+
188
+ /**
189
+ * Replace all blocks in a cell at once.
190
+ */
191
+ setCellBlocks(row: number, col: number, blockIds: string[]): void {
192
+ if (!this.isInBounds(row, col)) {
193
+ return;
194
+ }
195
+
196
+ // Remove old entries from map
197
+ for (const oldId of this.contentGrid[row][col].blocks) {
198
+ this.blockCellMap.delete(oldId);
199
+ }
200
+
201
+ this.contentGrid[row][col].blocks = [...blockIds];
202
+
203
+ // Add new entries to map
204
+ for (const id of blockIds) {
205
+ this.blockCellMap.set(id, { row, col });
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Get a copy of block IDs in a cell.
211
+ */
212
+ getCellBlocks(row: number, col: number): string[] {
213
+ if (!this.isInBounds(row, col)) {
214
+ return [];
215
+ }
216
+
217
+ return [...this.contentGrid[row][col].blocks];
218
+ }
219
+
220
+ /**
221
+ * Set the background color for a cell. Pass undefined to remove.
222
+ */
223
+ setCellColor(row: number, col: number, color: string | undefined): void {
224
+ if (!this.isInBounds(row, col)) {
225
+ return;
226
+ }
227
+
228
+ if (color === undefined) {
229
+ delete this.contentGrid[row][col].color;
230
+ } else if (isValidCssColor(color)) {
231
+ this.contentGrid[row][col].color = color;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Get the background color for a cell, or undefined if none set.
237
+ */
238
+ getCellColor(row: number, col: number): string | undefined {
239
+ if (!this.isInBounds(row, col)) {
240
+ return undefined;
241
+ }
242
+
243
+ return this.contentGrid[row][col].color;
244
+ }
245
+
246
+ /**
247
+ * Set the text color for a cell. Pass undefined to remove.
248
+ */
249
+ setCellTextColor(row: number, col: number, color: string | undefined): void {
250
+ if (!this.isInBounds(row, col)) {
251
+ return;
252
+ }
253
+
254
+ if (color === undefined) {
255
+ delete this.contentGrid[row][col].textColor;
256
+ } else if (isValidCssColor(color)) {
257
+ this.contentGrid[row][col].textColor = color;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Get the text color for a cell, or undefined if none set.
263
+ */
264
+ getCellTextColor(row: number, col: number): string | undefined {
265
+ if (!this.isInBounds(row, col)) {
266
+ return undefined;
267
+ }
268
+
269
+ return this.contentGrid[row][col].textColor;
270
+ }
271
+
272
+ // ─── Row operations ─────────────────────────────────────────────
273
+
274
+ /**
275
+ * Insert an empty row. Returns instruction for the caller.
276
+ */
277
+ addRow(index?: number): { type: 'add-row'; index: number; cellsToPopulate: number } {
278
+ const clampedIndex = index === undefined
279
+ ? this.contentGrid.length
280
+ : Math.min(Math.max(0, index), this.contentGrid.length);
281
+
282
+ const colCount = this.cols;
283
+ const newRow: CellContent[] = Array.from({ length: colCount }, () => ({ blocks: [] }));
284
+
285
+ this.contentGrid.splice(clampedIndex, 0, newRow);
286
+ this.rebuildBlockCellMap();
287
+
288
+ return {
289
+ type: 'add-row',
290
+ index: clampedIndex,
291
+ cellsToPopulate: colCount,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Remove a row. Returns the block IDs that were in it for cleanup.
297
+ */
298
+ deleteRow(index: number): { type: 'delete-row'; index: number; blocksToDelete: string[] } {
299
+ if (!this.isRowInBounds(index)) {
300
+ return { type: 'delete-row', index, blocksToDelete: [] };
301
+ }
302
+
303
+ const blocksToDelete = this.collectBlocksInRow(index);
304
+
305
+ this.contentGrid.splice(index, 1);
306
+
307
+ for (const id of blocksToDelete) {
308
+ this.blockCellMap.delete(id);
309
+ }
310
+
311
+ this.rebuildBlockCellMap();
312
+
313
+ return { type: 'delete-row', index, blocksToDelete };
314
+ }
315
+
316
+ /**
317
+ * Reorder a row. Returns instruction for the caller.
318
+ */
319
+ moveRow(from: number, to: number): { type: 'move-row'; index: number; toIndex: number } {
320
+ if (!this.isRowInBounds(from) || !this.isRowInBounds(to) || from === to) {
321
+ return { type: 'move-row', index: from, toIndex: to };
322
+ }
323
+
324
+ const [moved] = this.contentGrid.splice(from, 1);
325
+
326
+ this.contentGrid.splice(to, 0, moved);
327
+ this.rebuildBlockCellMap();
328
+
329
+ return { type: 'move-row', index: from, toIndex: to };
330
+ }
331
+
332
+ // ─── Column operations ──────────────────────────────────────────
333
+
334
+ /**
335
+ * Insert an empty column. Returns instruction with cells to populate.
336
+ */
337
+ addColumn(index?: number, width?: number): {
338
+ type: 'add-column';
339
+ index: number;
340
+ cellsToPopulate: Array<{ row: number; col: number }>;
341
+ } {
342
+ const clampedIndex = index === undefined
343
+ ? this.cols
344
+ : Math.min(Math.max(0, index), this.cols);
345
+
346
+ const cellsToPopulate: Array<{ row: number; col: number }> = [];
347
+
348
+ this.contentGrid.forEach((row, r) => {
349
+ row.splice(clampedIndex, 0, { blocks: [] });
350
+ cellsToPopulate.push({ row: r, col: clampedIndex });
351
+ });
352
+
353
+ if (this.colWidthsValue !== undefined) {
354
+ const insertWidth = width ?? 0;
355
+
356
+ this.colWidthsValue.splice(clampedIndex, 0, insertWidth);
357
+ }
358
+
359
+ this.rebuildBlockCellMap();
360
+
361
+ return {
362
+ type: 'add-column',
363
+ index: clampedIndex,
364
+ cellsToPopulate,
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Remove a column. Returns block IDs for cleanup.
370
+ */
371
+ deleteColumn(index: number): { type: 'delete-column'; index: number; blocksToDelete: string[] } {
372
+ if (!this.isColInBounds(index)) {
373
+ return { type: 'delete-column', index, blocksToDelete: [] };
374
+ }
375
+
376
+ const blocksToDelete = this.collectBlocksInColumn(index);
377
+
378
+ for (const row of this.contentGrid) {
379
+ row.splice(index, 1);
380
+ }
381
+
382
+ for (const id of blocksToDelete) {
383
+ this.blockCellMap.delete(id);
384
+ }
385
+
386
+ if (this.colWidthsValue !== undefined) {
387
+ this.colWidthsValue.splice(index, 1);
388
+ }
389
+
390
+ if (this.colWidthsValue?.length === 0) {
391
+ this.colWidthsValue = undefined;
392
+ }
393
+
394
+ this.rebuildBlockCellMap();
395
+
396
+ return { type: 'delete-column', index, blocksToDelete };
397
+ }
398
+
399
+ /**
400
+ * Reorder a column. Returns instruction for the caller.
401
+ */
402
+ moveColumn(from: number, to: number): { type: 'move-column'; index: number; toIndex: number } {
403
+ if (!this.isColInBounds(from) || !this.isColInBounds(to) || from === to) {
404
+ return { type: 'move-column', index: from, toIndex: to };
405
+ }
406
+
407
+ for (const row of this.contentGrid) {
408
+ const [moved] = row.splice(from, 1);
409
+
410
+ row.splice(to, 0, moved);
411
+ }
412
+
413
+ if (this.colWidthsValue !== undefined) {
414
+ const [movedWidth] = this.colWidthsValue.splice(from, 1);
415
+
416
+ this.colWidthsValue.splice(to, 0, movedWidth);
417
+ }
418
+
419
+ this.rebuildBlockCellMap();
420
+
421
+ return { type: 'move-column', index: from, toIndex: to };
422
+ }
423
+
424
+ // ─── replaceAll ─────────────────────────────────────────────────
425
+
426
+ /**
427
+ * Replace the entire model state. Used for setData / undo / redo.
428
+ */
429
+ replaceAll(data: TableData): void {
430
+ this.withHeadingsValue = data.withHeadings;
431
+ this.withHeadingColumnValue = data.withHeadingColumn;
432
+ this.stretchedValue = data.stretched ?? false;
433
+ this.colWidthsValue = data.colWidths ? [...data.colWidths] : undefined;
434
+ this.initialColWidthValue = data.initialColWidth;
435
+
436
+ this.contentGrid = this.normalizeContent(data.content);
437
+ this.blockCellMap.clear();
438
+ this.rebuildBlockCellMap();
439
+ }
440
+
441
+ // ─── Metadata setters ──────────────────────────────────────────
442
+
443
+ setWithHeadings(value: boolean): void {
444
+ this.withHeadingsValue = value;
445
+ }
446
+
447
+ setWithHeadingColumn(value: boolean): void {
448
+ this.withHeadingColumnValue = value;
449
+ }
450
+
451
+ setStretched(value: boolean): void {
452
+ this.stretchedValue = value;
453
+ }
454
+
455
+ setColWidths(widths: number[] | undefined): void {
456
+ this.colWidthsValue = widths ? [...widths] : undefined;
457
+ }
458
+
459
+ setInitialColWidth(value: number | undefined): void {
460
+ this.initialColWidthValue = value;
461
+ }
462
+
463
+ // ─── Invariant validation ───────────────────────────────────────
464
+
465
+ /**
466
+ * Validate all model invariants. Throws if any invariant is violated.
467
+ * Useful for debugging and test assertions.
468
+ *
469
+ * Invariants checked:
470
+ * 1. Rectangular grid: all rows have the same length
471
+ * 2. colWidths sync: colWidths length matches column count when defined
472
+ * 3. blockCellMap consistency: map matches grid contents
473
+ * 4. No duplicate blocks: each block ID appears in exactly one cell
474
+ */
475
+ validateInvariants(): void {
476
+ // Invariant 1: Rectangular grid
477
+ if (this.contentGrid.length > 0) {
478
+ const expectedCols = this.contentGrid[0].length;
479
+
480
+ this.contentGrid.forEach((row, r) => {
481
+ if (row.length !== expectedCols) {
482
+ throw new Error(
483
+ `Invariant violation: row ${r} has ${row.length} columns, expected ${expectedCols}`
484
+ );
485
+ }
486
+ });
487
+ }
488
+
489
+ // Invariant 2: colWidths sync
490
+ if (
491
+ this.colWidthsValue !== undefined &&
492
+ this.contentGrid.length > 0 &&
493
+ this.colWidthsValue.length !== this.contentGrid[0].length
494
+ ) {
495
+ throw new Error(
496
+ `Invariant violation: colWidths has ${this.colWidthsValue.length} entries but grid has ${this.contentGrid[0].length} columns`
497
+ );
498
+ }
499
+
500
+ // Invariant 3 + 4: blockCellMap consistency and no duplicates
501
+ const seenBlocks = new Set<string>();
502
+
503
+ this.contentGrid.forEach((row, r) => {
504
+ row.forEach((cell, c) => {
505
+ for (const blockId of cell.blocks) {
506
+ if (seenBlocks.has(blockId)) {
507
+ throw new Error(
508
+ `Invariant violation: block "${blockId}" appears in multiple cells`
509
+ );
510
+ }
511
+ seenBlocks.add(blockId);
512
+
513
+ const mapped = this.blockCellMap.get(blockId);
514
+
515
+ if (!mapped) {
516
+ throw new Error(
517
+ `Invariant violation: block "${blockId}" at [${r},${c}] not in blockCellMap`
518
+ );
519
+ }
520
+ if (mapped.row !== r || mapped.col !== c) {
521
+ throw new Error(
522
+ `Invariant violation: block "${blockId}" at [${r},${c}] mapped to [${mapped.row},${mapped.col}]`
523
+ );
524
+ }
525
+ }
526
+ });
527
+ });
528
+
529
+ // Check map doesn't have extra entries
530
+ if (this.blockCellMap.size !== seenBlocks.size) {
531
+ throw new Error(
532
+ `Invariant violation: blockCellMap has ${this.blockCellMap.size} entries but grid has ${seenBlocks.size} blocks`
533
+ );
534
+ }
535
+ }
536
+
537
+ // ─── Private helpers ────────────────────────────────────────────
538
+
539
+ /**
540
+ * Normalize legacy content (strings) into CellContent objects.
541
+ */
542
+ private normalizeContent(content?: LegacyCellContent[][]): CellContent[][] {
543
+ if (!content || !Array.isArray(content)) {
544
+ return [];
545
+ }
546
+
547
+ return content.map(row =>
548
+ (row ?? []).map(c => this.normalizeCell(c))
549
+ );
550
+ }
551
+
552
+ /**
553
+ * Normalize a single cell: legacy strings become empty blocks arrays.
554
+ */
555
+ private normalizeCell(cell: LegacyCellContent): CellContent {
556
+ if (isCellWithBlocks(cell)) {
557
+ const normalized: CellContent = { blocks: [...cell.blocks] };
558
+
559
+ if (cell.color !== undefined && isValidCssColor(cell.color)) {
560
+ normalized.color = cell.color;
561
+ }
562
+
563
+ if (cell.textColor !== undefined && isValidCssColor(cell.textColor)) {
564
+ normalized.textColor = cell.textColor;
565
+ }
566
+
567
+ return normalized;
568
+ }
569
+
570
+ return { blocks: [] };
571
+ }
572
+
573
+ /**
574
+ * Rebuild the blockCellMap from the content grid.
575
+ * Called after any structural operation that shifts row/col indices.
576
+ */
577
+ private rebuildBlockCellMap(): void {
578
+ this.blockCellMap.clear();
579
+
580
+ this.contentGrid.forEach((row, r) => {
581
+ row.forEach((cell, c) => {
582
+ for (const blockId of cell.blocks) {
583
+ this.blockCellMap.set(blockId, { row: r, col: c });
584
+ }
585
+ });
586
+ });
587
+ }
588
+
589
+ private isInBounds(row: number, col: number): boolean {
590
+ return row >= 0 && row < this.contentGrid.length &&
591
+ col >= 0 && (this.contentGrid.length === 0 || col < this.contentGrid[row].length);
592
+ }
593
+
594
+ private isRowInBounds(index: number): boolean {
595
+ return index >= 0 && index < this.contentGrid.length;
596
+ }
597
+
598
+ private isColInBounds(index: number): boolean {
599
+ return index >= 0 && this.contentGrid.length > 0 && index < this.contentGrid[0].length;
600
+ }
601
+
602
+ private collectBlocksInRow(rowIndex: number): string[] {
603
+ const blocks: string[] = [];
604
+
605
+ for (const cell of this.contentGrid[rowIndex]) {
606
+ blocks.push(...cell.blocks);
607
+ }
608
+
609
+ return blocks;
610
+ }
611
+
612
+ private collectBlocksInColumn(colIndex: number): string[] {
613
+ const blocks: string[] = [];
614
+
615
+ for (const row of this.contentGrid) {
616
+ if (colIndex < row.length) {
617
+ blocks.push(...row[colIndex].blocks);
618
+ }
619
+ }
620
+
621
+ return blocks;
622
+ }
623
+ }