@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,1662 @@
1
+ import type { Meta, StoryObj } from '@storybook/html-vite';
2
+ import { waitFor, expect } from 'storybook/test';
3
+
4
+ import { Table } from '../tools';
5
+
6
+ import { createEditorContainer, defaultTools, simulateClick, waitForToolbar } from './helpers';
7
+ import type { EditorFactoryOptions } from './helpers';
8
+
9
+ import type { OutputData, ToolSettings } from '@/types';
10
+
11
+ // ── Constants ────────────────────────────────────────────────────────
12
+
13
+ const TIMEOUT_INIT = { timeout: 5000 };
14
+ const TABLE_SELECTOR = '[data-blok-tool="table"]';
15
+ const GRIP_COL_SELECTOR = '[data-blok-table-grip-col]';
16
+ const GRIP_ROW_SELECTOR = '[data-blok-table-grip-row]';
17
+ const CELL_SELECTOR = '[data-blok-table-cell]';
18
+ const ROW_SELECTOR = '[data-blok-table-row]';
19
+ const RESIZE_HANDLE_SELECTOR = '[data-blok-table-resize]';
20
+ const ADD_ROW_SELECTOR = '[data-blok-table-add-row]';
21
+ const ADD_COL_SELECTOR = '[data-blok-table-add-col]';
22
+
23
+ // ── Types ────────────────────────────────────────────────────────────
24
+
25
+ interface TableArgs extends EditorFactoryOptions {
26
+ minHeight: number;
27
+ data: OutputData | undefined;
28
+ readOnly: boolean;
29
+ }
30
+
31
+ // ── Tools configuration ──────────────────────────────────────────────
32
+
33
+ const tableTools = {
34
+ ...defaultTools,
35
+ table: {
36
+ class: Table,
37
+ inlineToolbar: true,
38
+ } as ToolSettings,
39
+ };
40
+
41
+ // ── Editor factory ───────────────────────────────────────────────────
42
+
43
+ const createEditor = (args: TableArgs): HTMLElement =>
44
+ createEditorContainer({
45
+ ...args,
46
+ tools: tableTools,
47
+ });
48
+
49
+ // ── Meta ─────────────────────────────────────────────────────────────
50
+
51
+ const meta: Meta<TableArgs> = {
52
+ title: 'Tools/Table',
53
+ tags: ['autodocs'],
54
+ args: {
55
+ minHeight: 300,
56
+ data: undefined,
57
+ readOnly: false,
58
+ },
59
+ render: createEditor,
60
+ };
61
+
62
+ export default meta;
63
+
64
+ type Story = StoryObj<TableArgs>;
65
+
66
+ // ── Helpers ──────────────────────────────────────────────────────────
67
+
68
+ const waitForTable = async (canvas: HTMLElement): Promise<void> => {
69
+ await waitFor(
70
+ () => {
71
+ expect(canvas.querySelector(TABLE_SELECTOR)).toBeInTheDocument();
72
+ },
73
+ TIMEOUT_INIT
74
+ );
75
+ };
76
+
77
+ const forceGripsVisible = (grips: NodeListOf<Element>): void => {
78
+ grips.forEach((grip) => {
79
+ grip.setAttribute('data-blok-table-grip-visible', '');
80
+ grip.classList.remove('opacity-0', 'pointer-events-none');
81
+ grip.classList.add('opacity-100', 'pointer-events-auto');
82
+ });
83
+ };
84
+
85
+ const forceGripActive = (grip: Element, expanded: { width: string; height: string }): void => {
86
+ grip.setAttribute('data-blok-table-grip-visible', '');
87
+ grip.classList.remove('opacity-0', 'pointer-events-none', 'bg-gray-300');
88
+ grip.classList.add('opacity-100', 'pointer-events-auto', 'bg-blue-500', 'text-white');
89
+
90
+ const el = grip as HTMLElement;
91
+
92
+ el.style.width = expanded.width;
93
+ el.style.height = expanded.height;
94
+
95
+ const svg = grip.querySelector('svg');
96
+
97
+ if (svg) {
98
+ svg.classList.remove('opacity-0', 'text-gray-400');
99
+ svg.classList.add('opacity-100', 'text-white');
100
+ }
101
+ };
102
+
103
+ const markRowCellsSelected = (
104
+ row: Element,
105
+ colRange: [number, number],
106
+ out: Element[]
107
+ ): void => {
108
+ const cells = row.querySelectorAll(CELL_SELECTOR);
109
+
110
+ for (let c = colRange[0]; c <= colRange[1]; c++) {
111
+ if (!cells[c]) {
112
+ continue;
113
+ }
114
+
115
+ cells[c].setAttribute('data-blok-table-cell-selected', '');
116
+ out.push(cells[c]);
117
+ }
118
+ };
119
+
120
+ const forceSelectionOverlay = (
121
+ canvasElement: HTMLElement,
122
+ rowRange: [number, number],
123
+ colRange: [number, number]
124
+ ): HTMLDivElement | null => {
125
+ const rows = canvasElement.querySelectorAll(ROW_SELECTOR);
126
+ const selectedCells: Element[] = [];
127
+
128
+ for (let r = rowRange[0]; r <= rowRange[1]; r++) {
129
+ if (rows[r]) {
130
+ markRowCellsSelected(rows[r], colRange, selectedCells);
131
+ }
132
+ }
133
+
134
+ if (selectedCells.length === 0) {
135
+ return null;
136
+ }
137
+
138
+ const grid = rows[0]?.parentElement;
139
+
140
+ if (!grid) {
141
+ return null;
142
+ }
143
+
144
+ const gridRect = grid.getBoundingClientRect();
145
+ const firstRect = selectedCells[0].getBoundingClientRect();
146
+ const lastRect = selectedCells[selectedCells.length - 1].getBoundingClientRect();
147
+
148
+ const overlay = document.createElement('div');
149
+
150
+ overlay.style.position = 'absolute';
151
+ overlay.style.border = '2px solid #3b82f6';
152
+ overlay.style.borderRadius = '1px';
153
+ overlay.style.pointerEvents = 'none';
154
+ overlay.style.zIndex = '2';
155
+ overlay.style.left = `${firstRect.left - gridRect.left}px`;
156
+ overlay.style.top = `${firstRect.top - gridRect.top}px`;
157
+ overlay.style.width = `${lastRect.right - firstRect.left}px`;
158
+ overlay.style.height = `${lastRect.bottom - firstRect.top}px`;
159
+
160
+ grid.style.position = grid.style.position || 'relative';
161
+ grid.appendChild(overlay);
162
+
163
+ return overlay;
164
+ };
165
+
166
+ const forceAddButtonsVisible = (canvas: HTMLElement): void => {
167
+ [ADD_ROW_SELECTOR, ADD_COL_SELECTOR].forEach((sel) => {
168
+ const btn = canvas.querySelector(sel);
169
+
170
+ if (btn instanceof HTMLElement) {
171
+ btn.style.opacity = '1';
172
+ btn.style.pointerEvents = 'auto';
173
+ }
174
+ });
175
+ };
176
+
177
+ const highlightCells = (cells: Element[]): void => {
178
+ cells.forEach((cell) => {
179
+ const el = cell as HTMLElement;
180
+
181
+ el.style.backgroundColor = '#f3f4f6';
182
+ el.style.opacity = '0.7';
183
+ });
184
+ };
185
+
186
+ const createDropIndicator = (
187
+ orientation: 'row' | 'col',
188
+ positionPx: number,
189
+ lengthPx?: number
190
+ ): HTMLDivElement => {
191
+ const indicator = document.createElement('div');
192
+
193
+ indicator.style.position = 'absolute';
194
+ indicator.style.backgroundColor = '#3b82f6';
195
+ indicator.style.borderRadius = '1.5px';
196
+ indicator.style.zIndex = '5';
197
+ indicator.style.pointerEvents = 'none';
198
+
199
+ if (orientation === 'row') {
200
+ indicator.style.height = '3px';
201
+ indicator.style.left = '0';
202
+ indicator.style.right = '0';
203
+ indicator.style.top = `${positionPx - 1.5}px`;
204
+ } else {
205
+ indicator.style.width = '3px';
206
+ indicator.style.top = '0';
207
+ indicator.style.height = `${lengthPx ?? 0}px`;
208
+ indicator.style.left = `${positionPx - 1.5}px`;
209
+ }
210
+
211
+ return indicator;
212
+ };
213
+
214
+ // ═══════════════════════════════════════════════════════════════════════
215
+ // LAYOUT & STRUCTURE
216
+ // ═══════════════════════════════════════════════════════════════════════
217
+
218
+ /**
219
+ * Basic 2×2 table with no headings.
220
+ */
221
+ export const Default: Story = {
222
+ args: {
223
+ data: {
224
+ time: Date.now(),
225
+ version: '1.0.0',
226
+ blocks: [
227
+ {
228
+ id: 'default-table',
229
+ type: 'table',
230
+ data: {
231
+ withHeadings: false,
232
+ withHeadingColumn: false,
233
+ content: [
234
+ ['Cell A1', 'Cell B1'],
235
+ ['Cell A2', 'Cell B2'],
236
+ ],
237
+ },
238
+ },
239
+ ],
240
+ },
241
+ },
242
+ };
243
+
244
+ /**
245
+ * 3×3 table with varied content.
246
+ */
247
+ export const ThreeByThree: Story = {
248
+ args: {
249
+ data: {
250
+ time: Date.now(),
251
+ version: '1.0.0',
252
+ blocks: [
253
+ {
254
+ id: 'three-table',
255
+ type: 'table',
256
+ data: {
257
+ withHeadings: false,
258
+ withHeadingColumn: false,
259
+ content: [
260
+ ['Product', 'Price', 'Stock'],
261
+ ['Widget', '$9.99', '142'],
262
+ ['Gadget', '$24.50', '38'],
263
+ ],
264
+ },
265
+ },
266
+ ],
267
+ },
268
+ },
269
+ };
270
+
271
+ /**
272
+ * Large 5×5 table with realistic data and heading row.
273
+ */
274
+ export const LargeTable: Story = {
275
+ args: {
276
+ minHeight: 400,
277
+ data: {
278
+ time: Date.now(),
279
+ version: '1.0.0',
280
+ blocks: [
281
+ {
282
+ id: 'large-table',
283
+ type: 'table',
284
+ data: {
285
+ withHeadings: true,
286
+ withHeadingColumn: false,
287
+ content: [
288
+ ['Name', 'Role', 'Department', 'Location', 'Status'],
289
+ ['Alice Chen', 'Engineer', 'Platform', 'San Francisco', 'Active'],
290
+ ['Bob Smith', 'Designer', 'Product', 'New York', 'Active'],
291
+ ['Carol Wu', 'Manager', 'Engineering', 'London', 'On Leave'],
292
+ ['Dave Park', 'Analyst', 'Data Science', 'Berlin', 'Active'],
293
+ ],
294
+ },
295
+ },
296
+ ],
297
+ },
298
+ },
299
+ };
300
+
301
+ /**
302
+ * Full-width stretched table.
303
+ */
304
+ export const Stretched: Story = {
305
+ args: {
306
+ data: {
307
+ time: Date.now(),
308
+ version: '1.0.0',
309
+ blocks: [
310
+ {
311
+ id: 'stretched-table',
312
+ type: 'table',
313
+ data: {
314
+ withHeadings: true,
315
+ withHeadingColumn: false,
316
+ stretched: true,
317
+ content: [
318
+ ['Feature', 'Status', 'Priority'],
319
+ ['Authentication', 'Complete', 'High'],
320
+ ['Dashboard', 'In Progress', 'Medium'],
321
+ ],
322
+ },
323
+ },
324
+ ],
325
+ },
326
+ },
327
+ };
328
+
329
+ /**
330
+ * Table with first row styled as heading (bold, gray background).
331
+ */
332
+ export const WithHeadingRow: Story = {
333
+ args: {
334
+ data: {
335
+ time: Date.now(),
336
+ version: '1.0.0',
337
+ blocks: [
338
+ {
339
+ id: 'heading-row-table',
340
+ type: 'table',
341
+ data: {
342
+ withHeadings: true,
343
+ withHeadingColumn: false,
344
+ content: [
345
+ ['Quarter', 'Revenue', 'Growth'],
346
+ ['Q1 2025', '$1.2M', '+15%'],
347
+ ['Q2 2025', '$1.5M', '+25%'],
348
+ ['Q3 2025', '$1.8M', '+20%'],
349
+ ],
350
+ },
351
+ },
352
+ ],
353
+ },
354
+ },
355
+ };
356
+
357
+ /**
358
+ * Table with first column styled as heading.
359
+ */
360
+ export const WithHeadingColumn: Story = {
361
+ args: {
362
+ data: {
363
+ time: Date.now(),
364
+ version: '1.0.0',
365
+ blocks: [
366
+ {
367
+ id: 'heading-col-table',
368
+ type: 'table',
369
+ data: {
370
+ withHeadings: false,
371
+ withHeadingColumn: true,
372
+ content: [
373
+ ['Monday', '9am - 5pm', 'Available'],
374
+ ['Tuesday', '10am - 6pm', 'Available'],
375
+ ['Wednesday', '9am - 1pm', 'Half day'],
376
+ ],
377
+ },
378
+ },
379
+ ],
380
+ },
381
+ },
382
+ };
383
+
384
+ /**
385
+ * Table with both heading row and heading column.
386
+ */
387
+ export const WithBothHeadings: Story = {
388
+ args: {
389
+ data: {
390
+ time: Date.now(),
391
+ version: '1.0.0',
392
+ blocks: [
393
+ {
394
+ id: 'both-headings-table',
395
+ type: 'table',
396
+ data: {
397
+ withHeadings: true,
398
+ withHeadingColumn: true,
399
+ content: [
400
+ ['', 'Jan', 'Feb', 'Mar'],
401
+ ['Sales', '120', '145', '163'],
402
+ ['Costs', '80', '85', '90'],
403
+ ['Profit', '40', '60', '73'],
404
+ ],
405
+ },
406
+ },
407
+ ],
408
+ },
409
+ },
410
+ };
411
+
412
+ // ═══════════════════════════════════════════════════════════════════════
413
+ // CELL CONTENT
414
+ // ═══════════════════════════════════════════════════════════════════════
415
+
416
+ /**
417
+ * Cells containing bold, italic, and linked text.
418
+ */
419
+ export const FormattedCellContent: Story = {
420
+ args: {
421
+ data: {
422
+ time: Date.now(),
423
+ version: '1.0.0',
424
+ blocks: [
425
+ {
426
+ id: 'formatted-table',
427
+ type: 'table',
428
+ data: {
429
+ withHeadings: true,
430
+ withHeadingColumn: false,
431
+ content: [
432
+ ['Format', 'Example'],
433
+ ['<b>Bold text</b>', 'Important items are bold'],
434
+ ['<i>Italic text</i>', 'Emphasis via italic'],
435
+ ['<a href="https://example.com">Link</a>', 'Clickable reference'],
436
+ ['<b><i>Bold italic</i></b>', 'Combined formatting'],
437
+ ],
438
+ },
439
+ },
440
+ ],
441
+ },
442
+ },
443
+ };
444
+
445
+ /**
446
+ * Cells containing header and list blocks (nested block types).
447
+ */
448
+ export const NestedBlocksInCells: Story = {
449
+ args: {
450
+ minHeight: 400,
451
+ data: {
452
+ time: Date.now(),
453
+ version: '1.0.0',
454
+ blocks: [
455
+ {
456
+ id: 'nested-table',
457
+ type: 'table',
458
+ data: {
459
+ withHeadings: false,
460
+ withHeadingColumn: false,
461
+ content: [
462
+ [
463
+ { blocks: ['nested-header'] },
464
+ { blocks: ['nested-para'] },
465
+ ],
466
+ [
467
+ { blocks: ['nested-list-1', 'nested-list-2', 'nested-list-3'] },
468
+ { blocks: ['nested-multi-1', 'nested-multi-2'] },
469
+ ],
470
+ ],
471
+ },
472
+ },
473
+ { id: 'nested-header', type: 'header', data: { text: 'Section Title', level: 3 } },
474
+ { id: 'nested-para', type: 'paragraph', data: { text: 'A regular paragraph in a cell.' } },
475
+ { id: 'nested-list-1', type: 'list', data: { text: 'First item', style: 'unordered' } },
476
+ { id: 'nested-list-2', type: 'list', data: { text: 'Second item', style: 'unordered' } },
477
+ { id: 'nested-list-3', type: 'list', data: { text: 'Third item', style: 'unordered' } },
478
+ { id: 'nested-multi-1', type: 'paragraph', data: { text: 'First paragraph in cell.' } },
479
+ { id: 'nested-multi-2', type: 'paragraph', data: { text: 'Second paragraph below.' } },
480
+ ],
481
+ },
482
+ },
483
+ };
484
+
485
+ /**
486
+ * Cells with background colors applied (green=done, yellow=progress, red=blocked).
487
+ */
488
+ export const ColoredCells: Story = {
489
+ args: {
490
+ data: {
491
+ time: Date.now(),
492
+ version: '1.0.0',
493
+ blocks: [
494
+ {
495
+ id: 'colored-table',
496
+ type: 'table',
497
+ data: {
498
+ withHeadings: true,
499
+ withHeadingColumn: false,
500
+ content: [
501
+ ['Status', 'Task', 'Owner'],
502
+ [
503
+ { blocks: ['cc-done'], color: '#D1FAE5' },
504
+ { blocks: ['cc-task-1'] },
505
+ { blocks: ['cc-owner-1'] },
506
+ ],
507
+ [
508
+ { blocks: ['cc-progress'], color: '#FEF3C7' },
509
+ { blocks: ['cc-task-2'] },
510
+ { blocks: ['cc-owner-2'] },
511
+ ],
512
+ [
513
+ { blocks: ['cc-blocked'], color: '#FEE2E2' },
514
+ { blocks: ['cc-task-3'] },
515
+ { blocks: ['cc-owner-3'] },
516
+ ],
517
+ ],
518
+ },
519
+ },
520
+ { id: 'cc-done', type: 'paragraph', data: { text: 'Done' } },
521
+ { id: 'cc-task-1', type: 'paragraph', data: { text: 'Setup CI/CD pipeline' } },
522
+ { id: 'cc-owner-1', type: 'paragraph', data: { text: 'Alice' } },
523
+ { id: 'cc-progress', type: 'paragraph', data: { text: 'In Progress' } },
524
+ { id: 'cc-task-2', type: 'paragraph', data: { text: 'Implement auth flow' } },
525
+ { id: 'cc-owner-2', type: 'paragraph', data: { text: 'Bob' } },
526
+ { id: 'cc-blocked', type: 'paragraph', data: { text: 'Blocked' } },
527
+ { id: 'cc-task-3', type: 'paragraph', data: { text: 'Deploy to production' } },
528
+ { id: 'cc-owner-3', type: 'paragraph', data: { text: 'Carol' } },
529
+ ],
530
+ },
531
+ },
532
+ };
533
+
534
+ /**
535
+ * Cells with text color and combined background + text color.
536
+ */
537
+ export const TextColoredCells: Story = {
538
+ args: {
539
+ data: {
540
+ time: Date.now(),
541
+ version: '1.0.0',
542
+ blocks: [
543
+ {
544
+ id: 'textcolor-table',
545
+ type: 'table',
546
+ data: {
547
+ withHeadings: false,
548
+ withHeadingColumn: false,
549
+ content: [
550
+ [
551
+ { blocks: ['tc-1'], textColor: '#DC2626' },
552
+ { blocks: ['tc-2'], textColor: '#2563EB' },
553
+ ],
554
+ [
555
+ { blocks: ['tc-3'], textColor: '#059669' },
556
+ { blocks: ['tc-4'], color: '#1E293B', textColor: '#F8FAFC' },
557
+ ],
558
+ ],
559
+ },
560
+ },
561
+ { id: 'tc-1', type: 'paragraph', data: { text: 'Red text' } },
562
+ { id: 'tc-2', type: 'paragraph', data: { text: 'Blue text' } },
563
+ { id: 'tc-3', type: 'paragraph', data: { text: 'Green text' } },
564
+ { id: 'tc-4', type: 'paragraph', data: { text: 'Light on dark' } },
565
+ ],
566
+ },
567
+ },
568
+ };
569
+
570
+ // ═══════════════════════════════════════════════════════════════════════
571
+ // GRIPS & CONTROLS
572
+ // ═══════════════════════════════════════════════════════════════════════
573
+
574
+ /**
575
+ * Column grips in visible (hover) state.
576
+ */
577
+ export const ColumnGripsVisible: Story = {
578
+ args: {
579
+ data: {
580
+ time: Date.now(),
581
+ version: '1.0.0',
582
+ blocks: [
583
+ {
584
+ id: 'gripscol-table',
585
+ type: 'table',
586
+ data: {
587
+ withHeadings: true,
588
+ withHeadingColumn: false,
589
+ content: [
590
+ ['Name', 'Email', 'Role'],
591
+ ['Alice', 'alice@co.com', 'Admin'],
592
+ ['Bob', 'bob@co.com', 'User'],
593
+ ],
594
+ },
595
+ },
596
+ ],
597
+ },
598
+ },
599
+ play: async ({ canvasElement, step }) => {
600
+ await step('Wait for table', async () => {
601
+ await waitForTable(canvasElement);
602
+ });
603
+
604
+ await step('Force column grips visible', async () => {
605
+ const grips = canvasElement.querySelectorAll(GRIP_COL_SELECTOR);
606
+
607
+ forceGripsVisible(grips);
608
+
609
+ await waitFor(
610
+ () => {
611
+ const visible = canvasElement.querySelectorAll(`${GRIP_COL_SELECTOR}[data-blok-table-grip-visible]`);
612
+
613
+ expect(visible.length).toBeGreaterThan(0);
614
+ },
615
+ TIMEOUT_INIT
616
+ );
617
+ });
618
+ },
619
+ };
620
+
621
+ /**
622
+ * Row grips in visible (hover) state.
623
+ */
624
+ export const RowGripsVisible: Story = {
625
+ args: {
626
+ data: {
627
+ time: Date.now(),
628
+ version: '1.0.0',
629
+ blocks: [
630
+ {
631
+ id: 'gripsrow-table',
632
+ type: 'table',
633
+ data: {
634
+ withHeadings: false,
635
+ withHeadingColumn: false,
636
+ content: [
637
+ ['Row 1 Col 1', 'Row 1 Col 2'],
638
+ ['Row 2 Col 1', 'Row 2 Col 2'],
639
+ ['Row 3 Col 1', 'Row 3 Col 2'],
640
+ ],
641
+ },
642
+ },
643
+ ],
644
+ },
645
+ },
646
+ play: async ({ canvasElement, step }) => {
647
+ await step('Wait for table', async () => {
648
+ await waitForTable(canvasElement);
649
+ });
650
+
651
+ await step('Force row grips visible', async () => {
652
+ const grips = canvasElement.querySelectorAll(GRIP_ROW_SELECTOR);
653
+
654
+ forceGripsVisible(grips);
655
+
656
+ await waitFor(
657
+ () => {
658
+ const visible = canvasElement.querySelectorAll(`${GRIP_ROW_SELECTOR}[data-blok-table-grip-visible]`);
659
+
660
+ expect(visible.length).toBeGreaterThan(0);
661
+ },
662
+ TIMEOUT_INIT
663
+ );
664
+ });
665
+ },
666
+ };
667
+
668
+ /**
669
+ * Column grip in active (selected, blue) state with expanded dot pattern.
670
+ */
671
+ export const ColumnGripActive: Story = {
672
+ args: {
673
+ data: {
674
+ time: Date.now(),
675
+ version: '1.0.0',
676
+ blocks: [
677
+ {
678
+ id: 'gripactive-col-table',
679
+ type: 'table',
680
+ data: {
681
+ withHeadings: false,
682
+ withHeadingColumn: false,
683
+ content: [
684
+ ['A1', 'B1', 'C1'],
685
+ ['A2', 'B2', 'C2'],
686
+ ],
687
+ },
688
+ },
689
+ ],
690
+ },
691
+ },
692
+ play: async ({ canvasElement, step }) => {
693
+ await step('Wait for table', async () => {
694
+ await waitForTable(canvasElement);
695
+ });
696
+
697
+ await step('Force first column grip active', async () => {
698
+ const grip = canvasElement.querySelector(GRIP_COL_SELECTOR);
699
+
700
+ if (grip) {
701
+ forceGripActive(grip, { width: '24px', height: '16px' });
702
+ }
703
+
704
+ await waitFor(
705
+ () => {
706
+ expect(canvasElement.querySelector(`${GRIP_COL_SELECTOR}.bg-blue-500`)).toBeInTheDocument();
707
+ },
708
+ TIMEOUT_INIT
709
+ );
710
+ });
711
+ },
712
+ };
713
+
714
+ /**
715
+ * Row grip in active (selected, blue) state with expanded dot pattern.
716
+ */
717
+ export const RowGripActive: Story = {
718
+ args: {
719
+ data: {
720
+ time: Date.now(),
721
+ version: '1.0.0',
722
+ blocks: [
723
+ {
724
+ id: 'gripactive-row-table',
725
+ type: 'table',
726
+ data: {
727
+ withHeadings: false,
728
+ withHeadingColumn: false,
729
+ content: [
730
+ ['A1', 'B1'],
731
+ ['A2', 'B2'],
732
+ ['A3', 'B3'],
733
+ ],
734
+ },
735
+ },
736
+ ],
737
+ },
738
+ },
739
+ play: async ({ canvasElement, step }) => {
740
+ await step('Wait for table', async () => {
741
+ await waitForTable(canvasElement);
742
+ });
743
+
744
+ await step('Force first row grip active', async () => {
745
+ const grip = canvasElement.querySelector(GRIP_ROW_SELECTOR);
746
+
747
+ if (grip) {
748
+ forceGripActive(grip, { width: '16px', height: '20px' });
749
+ }
750
+
751
+ await waitFor(
752
+ () => {
753
+ expect(canvasElement.querySelector(`${GRIP_ROW_SELECTOR}.bg-blue-500`)).toBeInTheDocument();
754
+ },
755
+ TIMEOUT_INIT
756
+ );
757
+ });
758
+ },
759
+ };
760
+
761
+ // ═══════════════════════════════════════════════════════════════════════
762
+ // SELECTION
763
+ // ═══════════════════════════════════════════════════════════════════════
764
+
765
+ /**
766
+ * Single cell in focused state with cursor inside.
767
+ */
768
+ export const CellFocused: Story = {
769
+ args: {
770
+ data: {
771
+ time: Date.now(),
772
+ version: '1.0.0',
773
+ blocks: [
774
+ {
775
+ id: 'focus-table',
776
+ type: 'table',
777
+ data: {
778
+ withHeadings: true,
779
+ withHeadingColumn: false,
780
+ content: [
781
+ ['Header A', 'Header B'],
782
+ ['Click me', 'Other cell'],
783
+ ],
784
+ },
785
+ },
786
+ ],
787
+ },
788
+ },
789
+ play: async ({ canvasElement, step }) => {
790
+ await step('Wait for table and toolbar', async () => {
791
+ await waitForTable(canvasElement);
792
+ await waitForToolbar(canvasElement);
793
+ });
794
+
795
+ await step('Click cell to focus', async () => {
796
+ const rows = canvasElement.querySelectorAll(ROW_SELECTOR);
797
+ const targetCell = rows[1]?.querySelector(CELL_SELECTOR);
798
+
799
+ if (targetCell) {
800
+ const editable = targetCell.querySelector('[contenteditable="true"]');
801
+
802
+ if (editable) {
803
+ simulateClick(editable);
804
+ }
805
+ }
806
+
807
+ await waitFor(
808
+ () => {
809
+ const focused = document.activeElement;
810
+
811
+ expect(focused?.closest(CELL_SELECTOR)).toBeTruthy();
812
+ },
813
+ TIMEOUT_INIT
814
+ );
815
+ });
816
+ },
817
+ };
818
+
819
+ /**
820
+ * Multi-cell rectangular selection with blue border overlay.
821
+ */
822
+ export const MultiCellSelection: Story = {
823
+ args: {
824
+ data: {
825
+ time: Date.now(),
826
+ version: '1.0.0',
827
+ blocks: [
828
+ {
829
+ id: 'selection-table',
830
+ type: 'table',
831
+ data: {
832
+ withHeadings: false,
833
+ withHeadingColumn: false,
834
+ content: [
835
+ ['A1', 'B1', 'C1'],
836
+ ['A2', 'B2', 'C2'],
837
+ ['A3', 'B3', 'C3'],
838
+ ],
839
+ },
840
+ },
841
+ ],
842
+ },
843
+ },
844
+ play: async ({ canvasElement, step }) => {
845
+ await step('Wait for table', async () => {
846
+ await waitForTable(canvasElement);
847
+ });
848
+
849
+ await step('Force 2x2 cell selection with border overlay', async () => {
850
+ forceSelectionOverlay(canvasElement, [0, 1], [0, 1]);
851
+
852
+ const selected = canvasElement.querySelectorAll('[data-blok-table-cell-selected]');
853
+
854
+ expect(selected.length).toBe(4);
855
+ });
856
+ },
857
+ };
858
+
859
+ /**
860
+ * Selection pill in expanded state at the edge of a column selection.
861
+ */
862
+ export const SelectionPillExpanded: Story = {
863
+ args: {
864
+ data: {
865
+ time: Date.now(),
866
+ version: '1.0.0',
867
+ blocks: [
868
+ {
869
+ id: 'pill-table',
870
+ type: 'table',
871
+ data: {
872
+ withHeadings: false,
873
+ withHeadingColumn: false,
874
+ content: [
875
+ ['A1', 'B1', 'C1'],
876
+ ['A2', 'B2', 'C2'],
877
+ ['A3', 'B3', 'C3'],
878
+ ],
879
+ },
880
+ },
881
+ ],
882
+ },
883
+ },
884
+ play: async ({ canvasElement, step }) => {
885
+ await step('Wait for table', async () => {
886
+ await waitForTable(canvasElement);
887
+ });
888
+
889
+ await step('Force column selection with pill', async () => {
890
+ const overlay = forceSelectionOverlay(canvasElement, [0, 2], [0, 0]);
891
+
892
+ if (overlay) {
893
+ const pill = document.createElement('div');
894
+
895
+ pill.style.position = 'absolute';
896
+ pill.style.right = '-10px';
897
+ pill.style.top = '50%';
898
+ pill.style.transform = 'translateY(-50%)';
899
+ pill.style.width = '16px';
900
+ pill.style.height = '20px';
901
+ pill.style.backgroundColor = '#3b82f6';
902
+ pill.style.borderRadius = '2px';
903
+ pill.style.cursor = 'pointer';
904
+ pill.setAttribute('data-blok-table-selection-pill', '');
905
+
906
+ overlay.appendChild(pill);
907
+ }
908
+
909
+ const selected = canvasElement.querySelectorAll('[data-blok-table-cell-selected]');
910
+
911
+ expect(selected.length).toBe(3);
912
+ });
913
+ },
914
+ };
915
+
916
+ // ═══════════════════════════════════════════════════════════════════════
917
+ // RESIZE & SCROLL
918
+ // ═══════════════════════════════════════════════════════════════════════
919
+
920
+ /**
921
+ * Table with explicit pixel-width columns.
922
+ */
923
+ export const PixelWidthColumns: Story = {
924
+ args: {
925
+ data: {
926
+ time: Date.now(),
927
+ version: '1.0.0',
928
+ blocks: [
929
+ {
930
+ id: 'pixelwidth-table',
931
+ type: 'table',
932
+ data: {
933
+ withHeadings: true,
934
+ withHeadingColumn: false,
935
+ content: [
936
+ ['ID', 'Description', 'Status'],
937
+ ['1', 'A task with a longer description text', 'Done'],
938
+ ['2', 'Short', 'Pending'],
939
+ ],
940
+ colWidths: [60, 350, 100],
941
+ },
942
+ },
943
+ ],
944
+ },
945
+ },
946
+ };
947
+
948
+ /**
949
+ * Horizontally scrollable table with haze indicators on edges.
950
+ */
951
+ export const ScrollOverflow: Story = {
952
+ args: {
953
+ width: 500,
954
+ data: {
955
+ time: Date.now(),
956
+ version: '1.0.0',
957
+ blocks: [
958
+ {
959
+ id: 'scroll-table',
960
+ type: 'table',
961
+ data: {
962
+ withHeadings: true,
963
+ withHeadingColumn: false,
964
+ content: [
965
+ ['Col 1', 'Col 2', 'Col 3', 'Col 4', 'Col 5', 'Col 6'],
966
+ ['Data A1', 'Data A2', 'Data A3', 'Data A4', 'Data A5', 'Data A6'],
967
+ ['Data B1', 'Data B2', 'Data B3', 'Data B4', 'Data B5', 'Data B6'],
968
+ ],
969
+ colWidths: [150, 150, 150, 150, 150, 150],
970
+ },
971
+ },
972
+ ],
973
+ },
974
+ },
975
+ play: async ({ canvasElement, step }) => {
976
+ await step('Wait for table', async () => {
977
+ await waitForTable(canvasElement);
978
+ });
979
+
980
+ await step('Force scroll haze visible', async () => {
981
+ const hazes = canvasElement.querySelectorAll('[data-blok-table-haze]');
982
+
983
+ hazes.forEach((haze) => {
984
+ haze.setAttribute('data-blok-table-haze-visible', '');
985
+ haze.classList.remove('opacity-0');
986
+ haze.classList.add('opacity-100');
987
+ });
988
+
989
+ await waitFor(
990
+ () => {
991
+ const visible = canvasElement.querySelectorAll('[data-blok-table-haze-visible]');
992
+
993
+ expect(visible.length).toBeGreaterThan(0);
994
+ },
995
+ TIMEOUT_INIT
996
+ );
997
+ });
998
+ },
999
+ };
1000
+
1001
+ // ═══════════════════════════════════════════════════════════════════════
1002
+ // READONLY
1003
+ // ═══════════════════════════════════════════════════════════════════════
1004
+
1005
+ /**
1006
+ * Table in read-only mode — no grips, controls, or selection.
1007
+ */
1008
+ export const ReadOnly: Story = {
1009
+ args: {
1010
+ readOnly: true,
1011
+ data: {
1012
+ time: Date.now(),
1013
+ version: '1.0.0',
1014
+ blocks: [
1015
+ {
1016
+ id: 'readonly-table',
1017
+ type: 'table',
1018
+ data: {
1019
+ withHeadings: true,
1020
+ withHeadingColumn: false,
1021
+ content: [
1022
+ ['Language', 'Year', 'Creator'],
1023
+ ['TypeScript', '2012', 'Microsoft'],
1024
+ ['Rust', '2010', 'Mozilla'],
1025
+ ['Go', '2009', 'Google'],
1026
+ ],
1027
+ },
1028
+ },
1029
+ ],
1030
+ },
1031
+ },
1032
+ };
1033
+
1034
+ /**
1035
+ * Read-only table with colored cells preserved.
1036
+ */
1037
+ export const ReadOnlyWithColors: Story = {
1038
+ args: {
1039
+ readOnly: true,
1040
+ data: {
1041
+ time: Date.now(),
1042
+ version: '1.0.0',
1043
+ blocks: [
1044
+ {
1045
+ id: 'readonly-colored-table',
1046
+ type: 'table',
1047
+ data: {
1048
+ withHeadings: true,
1049
+ withHeadingColumn: false,
1050
+ content: [
1051
+ ['Priority', 'Task'],
1052
+ [
1053
+ { blocks: ['rc-high'], color: '#FEE2E2', textColor: '#DC2626' },
1054
+ { blocks: ['rc-task-1'] },
1055
+ ],
1056
+ [
1057
+ { blocks: ['rc-medium'], color: '#FEF3C7', textColor: '#D97706' },
1058
+ { blocks: ['rc-task-2'] },
1059
+ ],
1060
+ [
1061
+ { blocks: ['rc-low'], color: '#D1FAE5', textColor: '#059669' },
1062
+ { blocks: ['rc-task-3'] },
1063
+ ],
1064
+ ],
1065
+ },
1066
+ },
1067
+ { id: 'rc-high', type: 'paragraph', data: { text: 'High' } },
1068
+ { id: 'rc-task-1', type: 'paragraph', data: { text: 'Fix critical security bug' } },
1069
+ { id: 'rc-medium', type: 'paragraph', data: { text: 'Medium' } },
1070
+ { id: 'rc-task-2', type: 'paragraph', data: { text: 'Update documentation' } },
1071
+ { id: 'rc-low', type: 'paragraph', data: { text: 'Low' } },
1072
+ { id: 'rc-task-3', type: 'paragraph', data: { text: 'Refactor logging module' } },
1073
+ ],
1074
+ },
1075
+ },
1076
+ };
1077
+
1078
+ // ═══════════════════════════════════════════════════════════════════════
1079
+ // PRESET COLORS
1080
+ // ═══════════════════════════════════════════════════════════════════════
1081
+
1082
+ /**
1083
+ * 2×5 table showcasing all 10 preset background colors.
1084
+ */
1085
+ export const AllPresetBackgroundColors: Story = {
1086
+ args: {
1087
+ data: {
1088
+ time: Date.now(),
1089
+ version: '1.0.0',
1090
+ blocks: [
1091
+ {
1092
+ id: 'preset-bg-table',
1093
+ type: 'table',
1094
+ data: {
1095
+ withHeadings: false,
1096
+ withHeadingColumn: false,
1097
+ content: [
1098
+ [
1099
+ { blocks: ['pb-gray'], color: '#f1f1ef' },
1100
+ { blocks: ['pb-brown'], color: '#f4eeee' },
1101
+ { blocks: ['pb-orange'], color: '#fbecdd' },
1102
+ { blocks: ['pb-yellow'], color: '#fbf3db' },
1103
+ { blocks: ['pb-green'], color: '#edf3ec' },
1104
+ ],
1105
+ [
1106
+ { blocks: ['pb-teal'], color: '#e4f5f3' },
1107
+ { blocks: ['pb-blue'], color: '#e7f3f8' },
1108
+ { blocks: ['pb-purple'], color: '#f6f3f9' },
1109
+ { blocks: ['pb-pink'], color: '#f9f0f5' },
1110
+ { blocks: ['pb-red'], color: '#fdebec' },
1111
+ ],
1112
+ ],
1113
+ },
1114
+ },
1115
+ { id: 'pb-gray', type: 'paragraph', data: { text: 'Gray' } },
1116
+ { id: 'pb-brown', type: 'paragraph', data: { text: 'Brown' } },
1117
+ { id: 'pb-orange', type: 'paragraph', data: { text: 'Orange' } },
1118
+ { id: 'pb-yellow', type: 'paragraph', data: { text: 'Yellow' } },
1119
+ { id: 'pb-green', type: 'paragraph', data: { text: 'Green' } },
1120
+ { id: 'pb-teal', type: 'paragraph', data: { text: 'Teal' } },
1121
+ { id: 'pb-blue', type: 'paragraph', data: { text: 'Blue' } },
1122
+ { id: 'pb-purple', type: 'paragraph', data: { text: 'Purple' } },
1123
+ { id: 'pb-pink', type: 'paragraph', data: { text: 'Pink' } },
1124
+ { id: 'pb-red', type: 'paragraph', data: { text: 'Red' } },
1125
+ ],
1126
+ },
1127
+ },
1128
+ };
1129
+
1130
+ /**
1131
+ * 2×5 table showcasing all 10 preset text colors.
1132
+ */
1133
+ export const AllPresetTextColors: Story = {
1134
+ args: {
1135
+ data: {
1136
+ time: Date.now(),
1137
+ version: '1.0.0',
1138
+ blocks: [
1139
+ {
1140
+ id: 'preset-text-table',
1141
+ type: 'table',
1142
+ data: {
1143
+ withHeadings: false,
1144
+ withHeadingColumn: false,
1145
+ content: [
1146
+ [
1147
+ { blocks: ['pt-gray'], textColor: '#787774' },
1148
+ { blocks: ['pt-brown'], textColor: '#9f6b53' },
1149
+ { blocks: ['pt-orange'], textColor: '#d9730d' },
1150
+ { blocks: ['pt-yellow'], textColor: '#cb9b00' },
1151
+ { blocks: ['pt-green'], textColor: '#448361' },
1152
+ ],
1153
+ [
1154
+ { blocks: ['pt-teal'], textColor: '#2b9a8f' },
1155
+ { blocks: ['pt-blue'], textColor: '#337ea9' },
1156
+ { blocks: ['pt-purple'], textColor: '#9065b0' },
1157
+ { blocks: ['pt-pink'], textColor: '#c14c8a' },
1158
+ { blocks: ['pt-red'], textColor: '#d44c47' },
1159
+ ],
1160
+ ],
1161
+ },
1162
+ },
1163
+ { id: 'pt-gray', type: 'paragraph', data: { text: 'Gray' } },
1164
+ { id: 'pt-brown', type: 'paragraph', data: { text: 'Brown' } },
1165
+ { id: 'pt-orange', type: 'paragraph', data: { text: 'Orange' } },
1166
+ { id: 'pt-yellow', type: 'paragraph', data: { text: 'Yellow' } },
1167
+ { id: 'pt-green', type: 'paragraph', data: { text: 'Green' } },
1168
+ { id: 'pt-teal', type: 'paragraph', data: { text: 'Teal' } },
1169
+ { id: 'pt-blue', type: 'paragraph', data: { text: 'Blue' } },
1170
+ { id: 'pt-purple', type: 'paragraph', data: { text: 'Purple' } },
1171
+ { id: 'pt-pink', type: 'paragraph', data: { text: 'Pink' } },
1172
+ { id: 'pt-red', type: 'paragraph', data: { text: 'Red' } },
1173
+ ],
1174
+ },
1175
+ },
1176
+ };
1177
+
1178
+ /**
1179
+ * 2×5 table with both preset background and text colors combined.
1180
+ */
1181
+ export const PresetDualColors: Story = {
1182
+ args: {
1183
+ data: {
1184
+ time: Date.now(),
1185
+ version: '1.0.0',
1186
+ blocks: [
1187
+ {
1188
+ id: 'preset-dual-table',
1189
+ type: 'table',
1190
+ data: {
1191
+ withHeadings: false,
1192
+ withHeadingColumn: false,
1193
+ content: [
1194
+ [
1195
+ { blocks: ['pd-gray'], color: '#f1f1ef', textColor: '#787774' },
1196
+ { blocks: ['pd-brown'], color: '#f4eeee', textColor: '#9f6b53' },
1197
+ { blocks: ['pd-orange'], color: '#fbecdd', textColor: '#d9730d' },
1198
+ { blocks: ['pd-yellow'], color: '#fbf3db', textColor: '#cb9b00' },
1199
+ { blocks: ['pd-green'], color: '#edf3ec', textColor: '#448361' },
1200
+ ],
1201
+ [
1202
+ { blocks: ['pd-teal'], color: '#e4f5f3', textColor: '#2b9a8f' },
1203
+ { blocks: ['pd-blue'], color: '#e7f3f8', textColor: '#337ea9' },
1204
+ { blocks: ['pd-purple'], color: '#f6f3f9', textColor: '#9065b0' },
1205
+ { blocks: ['pd-pink'], color: '#f9f0f5', textColor: '#c14c8a' },
1206
+ { blocks: ['pd-red'], color: '#fdebec', textColor: '#d44c47' },
1207
+ ],
1208
+ ],
1209
+ },
1210
+ },
1211
+ { id: 'pd-gray', type: 'paragraph', data: { text: 'Gray' } },
1212
+ { id: 'pd-brown', type: 'paragraph', data: { text: 'Brown' } },
1213
+ { id: 'pd-orange', type: 'paragraph', data: { text: 'Orange' } },
1214
+ { id: 'pd-yellow', type: 'paragraph', data: { text: 'Yellow' } },
1215
+ { id: 'pd-green', type: 'paragraph', data: { text: 'Green' } },
1216
+ { id: 'pd-teal', type: 'paragraph', data: { text: 'Teal' } },
1217
+ { id: 'pd-blue', type: 'paragraph', data: { text: 'Blue' } },
1218
+ { id: 'pd-purple', type: 'paragraph', data: { text: 'Purple' } },
1219
+ { id: 'pd-pink', type: 'paragraph', data: { text: 'Pink' } },
1220
+ { id: 'pd-red', type: 'paragraph', data: { text: 'Red' } },
1221
+ ],
1222
+ },
1223
+ },
1224
+ };
1225
+
1226
+ // ═══════════════════════════════════════════════════════════════════════
1227
+ // INLINE MARKERS IN CELLS
1228
+ // ═══════════════════════════════════════════════════════════════════════
1229
+
1230
+ /**
1231
+ * 2×5 table: each cell has a preset background color AND an inline text-color
1232
+ * mark on the word. Tests CSS cascade — mark text color overrides cell text.
1233
+ */
1234
+ export const CellBgWithInlineMarker: Story = {
1235
+ args: {
1236
+ data: {
1237
+ time: Date.now(),
1238
+ version: '1.0.0',
1239
+ blocks: [
1240
+ {
1241
+ id: 'cellmark-bg-table',
1242
+ type: 'table',
1243
+ data: {
1244
+ withHeadings: false,
1245
+ withHeadingColumn: false,
1246
+ content: [
1247
+ [
1248
+ { blocks: ['cm-bg-gray'], color: '#f1f1ef' },
1249
+ { blocks: ['cm-bg-brown'], color: '#f4eeee' },
1250
+ { blocks: ['cm-bg-orange'], color: '#fbecdd' },
1251
+ { blocks: ['cm-bg-yellow'], color: '#fbf3db' },
1252
+ { blocks: ['cm-bg-green'], color: '#edf3ec' },
1253
+ ],
1254
+ [
1255
+ { blocks: ['cm-bg-teal'], color: '#e4f5f3' },
1256
+ { blocks: ['cm-bg-blue'], color: '#e7f3f8' },
1257
+ { blocks: ['cm-bg-purple'], color: '#f6f3f9' },
1258
+ { blocks: ['cm-bg-pink'], color: '#f9f0f5' },
1259
+ { blocks: ['cm-bg-red'], color: '#fdebec' },
1260
+ ],
1261
+ ],
1262
+ },
1263
+ },
1264
+ { id: 'cm-bg-gray', type: 'paragraph', data: { text: '<mark style="color: #787774; background-color: transparent">Gray</mark>' } },
1265
+ { id: 'cm-bg-brown', type: 'paragraph', data: { text: '<mark style="color: #9f6b53; background-color: transparent">Brown</mark>' } },
1266
+ { id: 'cm-bg-orange', type: 'paragraph', data: { text: '<mark style="color: #d9730d; background-color: transparent">Orange</mark>' } },
1267
+ { id: 'cm-bg-yellow', type: 'paragraph', data: { text: '<mark style="color: #cb9b00; background-color: transparent">Yellow</mark>' } },
1268
+ { id: 'cm-bg-green', type: 'paragraph', data: { text: '<mark style="color: #448361; background-color: transparent">Green</mark>' } },
1269
+ { id: 'cm-bg-teal', type: 'paragraph', data: { text: '<mark style="color: #2b9a8f; background-color: transparent">Teal</mark>' } },
1270
+ { id: 'cm-bg-blue', type: 'paragraph', data: { text: '<mark style="color: #337ea9; background-color: transparent">Blue</mark>' } },
1271
+ { id: 'cm-bg-purple', type: 'paragraph', data: { text: '<mark style="color: #9065b0; background-color: transparent">Purple</mark>' } },
1272
+ { id: 'cm-bg-pink', type: 'paragraph', data: { text: '<mark style="color: #c14c8a; background-color: transparent">Pink</mark>' } },
1273
+ { id: 'cm-bg-red', type: 'paragraph', data: { text: '<mark style="color: #d44c47; background-color: transparent">Red</mark>' } },
1274
+ ],
1275
+ },
1276
+ },
1277
+ };
1278
+
1279
+ /**
1280
+ * 2×5 table: each cell has a preset text color AND an inline background-color
1281
+ * mark. Tests CSS cascade — mark background overlays cell, text inherits cell color.
1282
+ */
1283
+ export const CellTextColorWithInlineMarker: Story = {
1284
+ args: {
1285
+ data: {
1286
+ time: Date.now(),
1287
+ version: '1.0.0',
1288
+ blocks: [
1289
+ {
1290
+ id: 'cellmark-text-table',
1291
+ type: 'table',
1292
+ data: {
1293
+ withHeadings: false,
1294
+ withHeadingColumn: false,
1295
+ content: [
1296
+ [
1297
+ { blocks: ['cm-tc-gray'], textColor: '#787774' },
1298
+ { blocks: ['cm-tc-brown'], textColor: '#9f6b53' },
1299
+ { blocks: ['cm-tc-orange'], textColor: '#d9730d' },
1300
+ { blocks: ['cm-tc-yellow'], textColor: '#cb9b00' },
1301
+ { blocks: ['cm-tc-green'], textColor: '#448361' },
1302
+ ],
1303
+ [
1304
+ { blocks: ['cm-tc-teal'], textColor: '#2b9a8f' },
1305
+ { blocks: ['cm-tc-blue'], textColor: '#337ea9' },
1306
+ { blocks: ['cm-tc-purple'], textColor: '#9065b0' },
1307
+ { blocks: ['cm-tc-pink'], textColor: '#c14c8a' },
1308
+ { blocks: ['cm-tc-red'], textColor: '#d44c47' },
1309
+ ],
1310
+ ],
1311
+ },
1312
+ },
1313
+ { id: 'cm-tc-gray', type: 'paragraph', data: { text: '<mark style="background-color: #f1f1ef">Gray</mark>' } },
1314
+ { id: 'cm-tc-brown', type: 'paragraph', data: { text: '<mark style="background-color: #f4eeee">Brown</mark>' } },
1315
+ { id: 'cm-tc-orange', type: 'paragraph', data: { text: '<mark style="background-color: #fbecdd">Orange</mark>' } },
1316
+ { id: 'cm-tc-yellow', type: 'paragraph', data: { text: '<mark style="background-color: #fbf3db">Yellow</mark>' } },
1317
+ { id: 'cm-tc-green', type: 'paragraph', data: { text: '<mark style="background-color: #edf3ec">Green</mark>' } },
1318
+ { id: 'cm-tc-teal', type: 'paragraph', data: { text: '<mark style="background-color: #e4f5f3">Teal</mark>' } },
1319
+ { id: 'cm-tc-blue', type: 'paragraph', data: { text: '<mark style="background-color: #e7f3f8">Blue</mark>' } },
1320
+ { id: 'cm-tc-purple', type: 'paragraph', data: { text: '<mark style="background-color: #f6f3f9">Purple</mark>' } },
1321
+ { id: 'cm-tc-pink', type: 'paragraph', data: { text: '<mark style="background-color: #f9f0f5">Pink</mark>' } },
1322
+ { id: 'cm-tc-red', type: 'paragraph', data: { text: '<mark style="background-color: #fdebec">Red</mark>' } },
1323
+ ],
1324
+ },
1325
+ },
1326
+ };
1327
+
1328
+ /**
1329
+ * 2×5 table: each cell has preset dual colors (bg + text) AND an inline
1330
+ * dual mark (different text + bg). Tests full override — mark colors take
1331
+ * precedence over cell colors on marked text.
1332
+ */
1333
+ export const CellDualWithInlineMarker: Story = {
1334
+ args: {
1335
+ data: {
1336
+ time: Date.now(),
1337
+ version: '1.0.0',
1338
+ blocks: [
1339
+ {
1340
+ id: 'cellmark-dual-table',
1341
+ type: 'table',
1342
+ data: {
1343
+ withHeadings: false,
1344
+ withHeadingColumn: false,
1345
+ content: [
1346
+ [
1347
+ { blocks: ['cm-d-gray'], color: '#f1f1ef', textColor: '#787774' },
1348
+ { blocks: ['cm-d-brown'], color: '#f4eeee', textColor: '#9f6b53' },
1349
+ { blocks: ['cm-d-orange'], color: '#fbecdd', textColor: '#d9730d' },
1350
+ { blocks: ['cm-d-yellow'], color: '#fbf3db', textColor: '#cb9b00' },
1351
+ { blocks: ['cm-d-green'], color: '#edf3ec', textColor: '#448361' },
1352
+ ],
1353
+ [
1354
+ { blocks: ['cm-d-teal'], color: '#e4f5f3', textColor: '#2b9a8f' },
1355
+ { blocks: ['cm-d-blue'], color: '#e7f3f8', textColor: '#337ea9' },
1356
+ { blocks: ['cm-d-purple'], color: '#f6f3f9', textColor: '#9065b0' },
1357
+ { blocks: ['cm-d-pink'], color: '#f9f0f5', textColor: '#c14c8a' },
1358
+ { blocks: ['cm-d-red'], color: '#fdebec', textColor: '#d44c47' },
1359
+ ],
1360
+ ],
1361
+ },
1362
+ },
1363
+ { id: 'cm-d-gray', type: 'paragraph', data: { text: 'Plain and <mark style="color: #d44c47; background-color: #fdebec">red mark</mark>' } },
1364
+ { id: 'cm-d-brown', type: 'paragraph', data: { text: 'Plain and <mark style="color: #337ea9; background-color: #e7f3f8">blue mark</mark>' } },
1365
+ { id: 'cm-d-orange', type: 'paragraph', data: { text: 'Plain and <mark style="color: #448361; background-color: #edf3ec">green mark</mark>' } },
1366
+ { id: 'cm-d-yellow', type: 'paragraph', data: { text: 'Plain and <mark style="color: #9065b0; background-color: #f6f3f9">purple mark</mark>' } },
1367
+ { id: 'cm-d-green', type: 'paragraph', data: { text: 'Plain and <mark style="color: #c14c8a; background-color: #f9f0f5">pink mark</mark>' } },
1368
+ { id: 'cm-d-teal', type: 'paragraph', data: { text: 'Plain and <mark style="color: #d9730d; background-color: #fbecdd">orange mark</mark>' } },
1369
+ { id: 'cm-d-blue', type: 'paragraph', data: { text: 'Plain and <mark style="color: #cb9b00; background-color: #fbf3db">yellow mark</mark>' } },
1370
+ { id: 'cm-d-purple', type: 'paragraph', data: { text: 'Plain and <mark style="color: #2b9a8f; background-color: #e4f5f3">teal mark</mark>' } },
1371
+ { id: 'cm-d-pink', type: 'paragraph', data: { text: 'Plain and <mark style="color: #787774; background-color: #f1f1ef">gray mark</mark>' } },
1372
+ { id: 'cm-d-red', type: 'paragraph', data: { text: 'Plain and <mark style="color: #9f6b53; background-color: #f4eeee">brown mark</mark>' } },
1373
+ ],
1374
+ },
1375
+ },
1376
+ };
1377
+
1378
+ // ═══════════════════════════════════════════════════════════════════════
1379
+ // EDGE CASES
1380
+ // ═══════════════════════════════════════════════════════════════════════
1381
+
1382
+ /**
1383
+ * Default empty table — renders 3×3 grid with no content.
1384
+ */
1385
+ export const EmptyTable: Story = {
1386
+ args: {
1387
+ data: {
1388
+ time: Date.now(),
1389
+ version: '1.0.0',
1390
+ blocks: [
1391
+ {
1392
+ id: 'empty-table',
1393
+ type: 'table',
1394
+ data: {
1395
+ withHeadings: false,
1396
+ withHeadingColumn: false,
1397
+ content: [],
1398
+ },
1399
+ },
1400
+ ],
1401
+ },
1402
+ },
1403
+ };
1404
+
1405
+ /**
1406
+ * Single-row table (1×3) — edge case for minimum row count.
1407
+ */
1408
+ export const SingleRowTable: Story = {
1409
+ args: {
1410
+ data: {
1411
+ time: Date.now(),
1412
+ version: '1.0.0',
1413
+ blocks: [
1414
+ {
1415
+ id: 'singlerow-table',
1416
+ type: 'table',
1417
+ data: {
1418
+ withHeadings: false,
1419
+ withHeadingColumn: false,
1420
+ content: [['Column A', 'Column B', 'Column C']],
1421
+ },
1422
+ },
1423
+ ],
1424
+ },
1425
+ },
1426
+ };
1427
+
1428
+ /**
1429
+ * Single-column table with heading row — edge case for minimum column count.
1430
+ */
1431
+ export const SingleColumnTable: Story = {
1432
+ args: {
1433
+ data: {
1434
+ time: Date.now(),
1435
+ version: '1.0.0',
1436
+ blocks: [
1437
+ {
1438
+ id: 'singlecol-table',
1439
+ type: 'table',
1440
+ data: {
1441
+ withHeadings: true,
1442
+ withHeadingColumn: false,
1443
+ content: [['Title'], ['First'], ['Second'], ['Third']],
1444
+ },
1445
+ },
1446
+ ],
1447
+ },
1448
+ },
1449
+ };
1450
+
1451
+ // ═══════════════════════════════════════════════════════════════════════
1452
+ // ADD & RESIZE CONTROLS
1453
+ // ═══════════════════════════════════════════════════════════════════════
1454
+
1455
+ /**
1456
+ * Add-row and add-column buttons in visible state.
1457
+ */
1458
+ export const AddButtonsVisible: Story = {
1459
+ args: {
1460
+ data: {
1461
+ time: Date.now(),
1462
+ version: '1.0.0',
1463
+ blocks: [
1464
+ {
1465
+ id: 'addbtns-table',
1466
+ type: 'table',
1467
+ data: {
1468
+ withHeadings: false,
1469
+ withHeadingColumn: false,
1470
+ content: [
1471
+ ['A1', 'B1'],
1472
+ ['A2', 'B2'],
1473
+ ],
1474
+ },
1475
+ },
1476
+ ],
1477
+ },
1478
+ },
1479
+ play: async ({ canvasElement, step }) => {
1480
+ await step('Wait for table', async () => {
1481
+ await waitForTable(canvasElement);
1482
+ });
1483
+
1484
+ await step('Force add buttons visible', async () => {
1485
+ forceAddButtonsVisible(canvasElement);
1486
+
1487
+ await waitFor(
1488
+ () => {
1489
+ const rowBtn = canvasElement.querySelector(ADD_ROW_SELECTOR);
1490
+
1491
+ expect(rowBtn).toBeInTheDocument();
1492
+ expect(rowBtn instanceof HTMLElement && rowBtn.style.opacity === '1').toBe(true);
1493
+ },
1494
+ TIMEOUT_INIT
1495
+ );
1496
+ });
1497
+ },
1498
+ };
1499
+
1500
+ /**
1501
+ * Column resize handles in visible (hover) state.
1502
+ */
1503
+ export const ResizeHandlesVisible: Story = {
1504
+ args: {
1505
+ data: {
1506
+ time: Date.now(),
1507
+ version: '1.0.0',
1508
+ blocks: [
1509
+ {
1510
+ id: 'resize-table',
1511
+ type: 'table',
1512
+ data: {
1513
+ withHeadings: true,
1514
+ withHeadingColumn: false,
1515
+ content: [
1516
+ ['Name', 'Description', 'Status'],
1517
+ ['Item 1', 'A longer description here', 'Active'],
1518
+ ['Item 2', 'Short', 'Pending'],
1519
+ ],
1520
+ colWidths: [100, 300, 100],
1521
+ },
1522
+ },
1523
+ ],
1524
+ },
1525
+ },
1526
+ play: async ({ canvasElement, step }) => {
1527
+ await step('Wait for table', async () => {
1528
+ await waitForTable(canvasElement);
1529
+ });
1530
+
1531
+ await step('Force resize handles visible', async () => {
1532
+ const handles = canvasElement.querySelectorAll(RESIZE_HANDLE_SELECTOR);
1533
+
1534
+ handles.forEach((handle) => {
1535
+ const el = handle as HTMLElement;
1536
+
1537
+ el.style.opacity = '1';
1538
+ });
1539
+
1540
+ await waitFor(
1541
+ () => {
1542
+ expect(canvasElement.querySelectorAll(RESIZE_HANDLE_SELECTOR).length).toBeGreaterThan(0);
1543
+ },
1544
+ TIMEOUT_INIT
1545
+ );
1546
+ });
1547
+ },
1548
+ };
1549
+
1550
+ // ═══════════════════════════════════════════════════════════════════════
1551
+ // DRAG STATES
1552
+ // ═══════════════════════════════════════════════════════════════════════
1553
+
1554
+ /**
1555
+ * Row drag in progress — source row highlighted with blue drop indicator.
1556
+ */
1557
+ export const RowDragInProgress: Story = {
1558
+ args: {
1559
+ minHeight: 350,
1560
+ data: {
1561
+ time: Date.now(),
1562
+ version: '1.0.0',
1563
+ blocks: [
1564
+ {
1565
+ id: 'rowdrag-table',
1566
+ type: 'table',
1567
+ data: {
1568
+ withHeadings: true,
1569
+ withHeadingColumn: false,
1570
+ content: [
1571
+ ['Name', 'Role', 'Status'],
1572
+ ['Alice', 'Engineer', 'Active'],
1573
+ ['Bob', 'Designer', 'Active'],
1574
+ ['Carol', 'Manager', 'On Leave'],
1575
+ ],
1576
+ },
1577
+ },
1578
+ ],
1579
+ },
1580
+ },
1581
+ play: async ({ canvasElement, step }) => {
1582
+ await step('Wait for table', async () => {
1583
+ await waitForTable(canvasElement);
1584
+ });
1585
+
1586
+ await step('Force row drag state', async () => {
1587
+ const rows = canvasElement.querySelectorAll(ROW_SELECTOR);
1588
+ const sourceRow = rows[1];
1589
+ const grid = rows[0]?.parentElement;
1590
+ const dropTarget = rows[3] as HTMLElement | undefined;
1591
+
1592
+ if (sourceRow) {
1593
+ highlightCells(Array.from(sourceRow.querySelectorAll(CELL_SELECTOR)));
1594
+ }
1595
+
1596
+ if (grid && dropTarget) {
1597
+ grid.style.position = grid.style.position || 'relative';
1598
+ grid.appendChild(createDropIndicator('row', dropTarget.offsetTop));
1599
+ }
1600
+
1601
+ expect(rows.length).toBeGreaterThanOrEqual(4);
1602
+ });
1603
+ },
1604
+ };
1605
+
1606
+ /**
1607
+ * Column drag in progress — source column highlighted with blue drop indicator.
1608
+ */
1609
+ export const ColumnDragInProgress: Story = {
1610
+ args: {
1611
+ minHeight: 350,
1612
+ data: {
1613
+ time: Date.now(),
1614
+ version: '1.0.0',
1615
+ blocks: [
1616
+ {
1617
+ id: 'coldrag-table',
1618
+ type: 'table',
1619
+ data: {
1620
+ withHeadings: true,
1621
+ withHeadingColumn: false,
1622
+ content: [
1623
+ ['Name', 'Role', 'Department', 'Status'],
1624
+ ['Alice', 'Engineer', 'Platform', 'Active'],
1625
+ ['Bob', 'Designer', 'Product', 'Active'],
1626
+ ],
1627
+ },
1628
+ },
1629
+ ],
1630
+ },
1631
+ },
1632
+ play: async ({ canvasElement, step }) => {
1633
+ await step('Wait for table', async () => {
1634
+ await waitForTable(canvasElement);
1635
+ });
1636
+
1637
+ await step('Force column drag state', async () => {
1638
+ const rows = canvasElement.querySelectorAll(ROW_SELECTOR);
1639
+ const columnCells: Element[] = [];
1640
+
1641
+ rows.forEach((row) => {
1642
+ const cell = row.querySelectorAll(CELL_SELECTOR)[0];
1643
+
1644
+ if (cell) {
1645
+ columnCells.push(cell);
1646
+ }
1647
+ });
1648
+
1649
+ highlightCells(columnCells);
1650
+
1651
+ const grid = rows[0]?.parentElement;
1652
+ const targetCell = rows[0]?.querySelectorAll(CELL_SELECTOR)[2] as HTMLElement | undefined;
1653
+
1654
+ if (grid && targetCell) {
1655
+ grid.style.position = grid.style.position || 'relative';
1656
+ grid.appendChild(createDropIndicator('col', targetCell.offsetLeft, grid.offsetHeight));
1657
+ }
1658
+
1659
+ expect(rows.length).toBeGreaterThan(0);
1660
+ });
1661
+ },
1662
+ };