@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,737 @@
1
+ import type {
2
+ InlineTool,
3
+ InlineToolConstructorOptions,
4
+ SanitizerConfig
5
+ } from '../../../types';
6
+ import type { I18n, InlineToolbar } from '../../../types/api';
7
+ import type { MenuConfig } from '../../../types/tools';
8
+ import { IconMarker } from '../icons';
9
+ import { SelectionUtils } from '../selection/index';
10
+ import { PopoverItemType } from '../utils/popover';
11
+ import { isMarkTag, findMarkElement } from './utils/marker-dom-utils';
12
+ import {
13
+ isRangeFormatted,
14
+ collectFormattingAncestors,
15
+ } from './utils/formatting-range-utils';
16
+ import { createColorPicker } from '../shared/color-picker';
17
+ import type { ColorPickerHandle } from '../shared/color-picker';
18
+
19
+ /**
20
+ * Color mode type — either text color or background color
21
+ */
22
+ type ColorMode = 'color' | 'background-color';
23
+
24
+ /**
25
+ * The opposite color mode key for checking remaining styles
26
+ */
27
+ const OPPOSITE_MODE: Record<ColorMode, ColorMode> = {
28
+ 'color': 'background-color',
29
+ 'background-color': 'color',
30
+ };
31
+
32
+ /**
33
+ * Marker Color Inline Tool
34
+ *
35
+ * Wraps selected text in <mark> with color or background-color styles.
36
+ */
37
+ export class MarkerInlineTool implements InlineTool {
38
+ /**
39
+ * Specifies Tool as Inline Toolbar Tool
40
+ */
41
+ public static isInline = true;
42
+
43
+ /**
44
+ * Title for the Inline Tool
45
+ */
46
+ public static title = 'Color';
47
+
48
+ /**
49
+ * Translation key for i18n
50
+ */
51
+ public static titleKey = 'marker';
52
+
53
+ /**
54
+ * Keyboard shortcut to open the marker color picker
55
+ */
56
+ public static shortcut = 'CMD+SHIFT+H';
57
+
58
+ /**
59
+ * CSS properties allowed on <mark> elements.
60
+ * All other properties are stripped during sanitization to prevent
61
+ * style-based attacks (e.g. position:fixed overlays via pasted HTML).
62
+ */
63
+ private static readonly ALLOWED_STYLE_PROPS = new Set(['color', 'background-color']);
64
+
65
+ /**
66
+ * Sanitizer Rule — preserve <mark> tags with only color-related style properties.
67
+ *
68
+ * Uses a function-based rule so HTMLJanitor calls it with the live DOM node,
69
+ * allowing in-place filtering of CSS properties before the node is serialized.
70
+ */
71
+ public static get sanitize(): SanitizerConfig {
72
+ return {
73
+ mark: (node: Element): { [attr: string]: boolean | string } => {
74
+ const el = node as HTMLElement;
75
+ const style = el.style;
76
+
77
+ /**
78
+ * Collect property names first, then remove disallowed ones.
79
+ * This avoids mutating the CSSStyleDeclaration while iterating its indices.
80
+ */
81
+ const props = Array.from({ length: style.length }, (_, i) => style.item(i));
82
+
83
+ for (const prop of props) {
84
+ if (!MarkerInlineTool.ALLOWED_STYLE_PROPS.has(prop)) {
85
+ style.removeProperty(prop);
86
+ }
87
+ }
88
+
89
+ return style.length > 0 ? { style: true } : {};
90
+ },
91
+ } as SanitizerConfig;
92
+ }
93
+
94
+ /**
95
+ * I18n API
96
+ */
97
+ private i18n: I18n;
98
+
99
+ /**
100
+ * Inline toolbar API
101
+ */
102
+ private inlineToolbar: InlineToolbar;
103
+
104
+ /**
105
+ * SelectionUtils instance for saving/restoring selection
106
+ */
107
+ private selection: SelectionUtils;
108
+
109
+ /**
110
+ * Currently active color mode, updated by the shared picker via callback
111
+ */
112
+ private colorMode: ColorMode = 'color';
113
+
114
+ /**
115
+ * The color picker handle with element and control methods
116
+ */
117
+ private picker: ColorPickerHandle;
118
+
119
+ /**
120
+ * @param options - Inline tool constructor options with API
121
+ */
122
+ constructor({ api }: InlineToolConstructorOptions) {
123
+ this.i18n = api.i18n;
124
+ this.inlineToolbar = api.inlineToolbar;
125
+ this.selection = new SelectionUtils();
126
+
127
+ this.picker = createColorPicker({
128
+ i18n: this.i18n,
129
+ testIdPrefix: 'marker',
130
+ defaultModeIndex: 0,
131
+ modes: [
132
+ { key: 'color', labelKey: 'tools.marker.textColor', presetField: 'text' },
133
+ { key: 'background-color', labelKey: 'tools.marker.background', presetField: 'bg' },
134
+ ],
135
+ onColorSelect: (color, modeKey) => {
136
+ this.colorMode = modeKey as ColorMode;
137
+
138
+ if (color !== null) {
139
+ this.applyColor(this.colorMode, color);
140
+ } else {
141
+ this.removeColor(this.colorMode);
142
+ }
143
+ this.selection.setFakeBackground();
144
+ this.selection.save();
145
+ },
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Create button for Inline Toolbar
151
+ */
152
+ public render(): MenuConfig {
153
+ return {
154
+ icon: IconMarker,
155
+ name: 'marker',
156
+ isActive: () => {
157
+ const selection = window.getSelection();
158
+
159
+ if (!selection || selection.rangeCount === 0) {
160
+ return false;
161
+ }
162
+
163
+ const range = selection.getRangeAt(0);
164
+
165
+ return isRangeFormatted(range, isMarkTag, { ignoreWhitespace: true });
166
+ },
167
+ children: {
168
+ hideChevron: true,
169
+ width: '200px',
170
+ items: [
171
+ {
172
+ type: PopoverItemType.Html,
173
+ element: this.picker.element,
174
+ },
175
+ ],
176
+ onOpen: () => {
177
+ this.onPickerOpen();
178
+ },
179
+ onClose: () => {
180
+ this.onPickerClose();
181
+ },
182
+ },
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Apply a color to the current selection by wrapping in <mark>
188
+ * @param mode - 'color' or 'background-color'
189
+ * @param value - CSS color value
190
+ */
191
+ public applyColor(mode: ColorMode, value: string): void {
192
+ this.restoreSelectionIfSaved();
193
+
194
+ const selection = window.getSelection();
195
+
196
+ if (!selection || selection.rangeCount === 0) {
197
+ return;
198
+ }
199
+
200
+ const range = selection.getRangeAt(0);
201
+
202
+ if (range.collapsed) {
203
+ return;
204
+ }
205
+
206
+ /**
207
+ * Check if the entire selection is already inside a single mark element
208
+ */
209
+ const existingMark = this.findContainingMark(range);
210
+
211
+ if (existingMark) {
212
+ /**
213
+ * If the selection covers the entire mark content, update in-place
214
+ */
215
+ const markRange = document.createRange();
216
+
217
+ markRange.selectNodeContents(existingMark);
218
+
219
+ const coversAll =
220
+ range.compareBoundaryPoints(Range.START_TO_START, markRange) <= 0 &&
221
+ range.compareBoundaryPoints(Range.END_TO_END, markRange) >= 0;
222
+
223
+ if (coversAll) {
224
+ existingMark.style.setProperty(mode, value);
225
+ this.ensureTransparentBg(existingMark);
226
+
227
+ return;
228
+ }
229
+
230
+ /**
231
+ * Partial selection: split the mark around the selection
232
+ * so the new color applies only to the selected text
233
+ */
234
+ this.splitMarkAroundRange(existingMark, range, mode, value);
235
+
236
+ return;
237
+ }
238
+
239
+ /**
240
+ * Split any marks that extend beyond the selection boundaries
241
+ * so removeNestedMarkStyle only processes the portion within the range
242
+ */
243
+ this.splitMarksAtBoundaries(range);
244
+
245
+ /**
246
+ * Remove any nested marks with the same mode before wrapping
247
+ */
248
+ this.removeNestedMarkStyle(range, mode);
249
+
250
+ const mark = document.createElement('mark');
251
+
252
+ mark.style.setProperty(mode, value);
253
+ this.ensureTransparentBg(mark);
254
+ mark.appendChild(range.extractContents());
255
+ range.insertNode(mark);
256
+
257
+ /**
258
+ * Select the newly inserted mark contents
259
+ */
260
+ selection.removeAllRanges();
261
+ const newRange = document.createRange();
262
+
263
+ newRange.selectNodeContents(mark);
264
+ selection.addRange(newRange);
265
+ }
266
+
267
+ /**
268
+ * Remove a color style from the current selection's mark elements
269
+ * @param mode - 'color' or 'background-color'
270
+ */
271
+ public removeColor(mode: ColorMode): void {
272
+ this.restoreSelectionIfSaved();
273
+
274
+ const selection = window.getSelection();
275
+
276
+ if (!selection || selection.rangeCount === 0) {
277
+ return;
278
+ }
279
+
280
+ const range = selection.getRangeAt(0);
281
+
282
+ /**
283
+ * Capture range anchors before DOM mutations so we can restore the selection
284
+ * after marks are unwrapped (browsers may collapse selection on DOM changes)
285
+ */
286
+ const startContainer = range.startContainer;
287
+ const startOffset = range.startOffset;
288
+ const endContainer = range.endContainer;
289
+ const endOffset = range.endOffset;
290
+
291
+ /**
292
+ * Also capture the selected text and a surviving parent node so we
293
+ * can fall back to offset-based restoration when anchors become stale.
294
+ * The commonAncestorContainer may itself be the mark element being removed,
295
+ * so walk up to find a parent that will survive the unwrap.
296
+ */
297
+ const selectedText = range.toString();
298
+ const ancestor = range.commonAncestorContainer;
299
+ const ancestorEl = ancestor.nodeType === Node.ELEMENT_NODE
300
+ ? ancestor as HTMLElement
301
+ : ancestor.parentElement;
302
+ const survivingParent = ancestorEl?.closest('mark')
303
+ ? ancestorEl.closest('mark')?.parentElement ?? ancestorEl
304
+ : ancestorEl;
305
+
306
+ const markAncestors = collectFormattingAncestors(range, isMarkTag);
307
+
308
+ for (const mark of markAncestors) {
309
+ mark.style.removeProperty(mode);
310
+
311
+ const oppositeMode = OPPOSITE_MODE[mode];
312
+ const oppositeValue = mark.style.getPropertyValue(oppositeMode);
313
+ const hasOtherStyle = oppositeValue !== '' && oppositeValue !== 'transparent';
314
+
315
+ if (!hasOtherStyle) {
316
+ this.unwrapElement(mark);
317
+ } else {
318
+ this.ensureTransparentBg(mark);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Re-establish the selection after DOM mutations.
324
+ * When the range was anchored to text nodes (moved, not cloned by unwrapElement),
325
+ * the original anchors remain valid. When the range was anchored to the mark
326
+ * element itself (e.g. via selectNodeContents), the node is now detached.
327
+ * Check connectivity before attempting restoration; fall back to text-offset
328
+ * restoration when anchors are stale.
329
+ */
330
+ const startConnected = startContainer.isConnected;
331
+ const endConnected = endContainer.isConnected;
332
+
333
+ if (startConnected && endConnected) {
334
+ try {
335
+ const restoredRange = document.createRange();
336
+
337
+ restoredRange.setStart(startContainer, startOffset);
338
+ restoredRange.setEnd(endContainer, endOffset);
339
+ selection.removeAllRanges();
340
+ selection.addRange(restoredRange);
341
+ } catch {
342
+ this.restoreSelectionByText(selection, survivingParent, selectedText);
343
+ }
344
+ } else {
345
+ this.restoreSelectionByText(selection, survivingParent, selectedText);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Called when the picker popover opens — save selection, reset tab state,
351
+ * and detect the current selection's color to highlight the active swatch.
352
+ */
353
+ private onPickerOpen(): void {
354
+ this.picker.reset();
355
+
356
+ const activeColor = this.detectSelectionColor();
357
+
358
+ if (activeColor) {
359
+ this.picker.setActiveColor(activeColor.value, activeColor.mode);
360
+ }
361
+
362
+ this.selection.setFakeBackground();
363
+ this.selection.save();
364
+ }
365
+
366
+ /**
367
+ * Called when the picker popover closes — clean up selection state
368
+ */
369
+ private onPickerClose(): void {
370
+ this.selection.removeFakeBackground();
371
+
372
+ if (this.selection.savedSelectionRange) {
373
+ this.selection.restore();
374
+ }
375
+
376
+ this.selection.clearSaved();
377
+ }
378
+
379
+ /**
380
+ * Detect the color of the current selection's mark element.
381
+ * Returns the first color mode found (text color preferred over background).
382
+ */
383
+ private detectSelectionColor(): { value: string; mode: string } | null {
384
+ const selection = window.getSelection();
385
+
386
+ if (!selection || selection.rangeCount === 0) {
387
+ return null;
388
+ }
389
+
390
+ const range = selection.getRangeAt(0);
391
+ const mark = findMarkElement(range.startContainer);
392
+
393
+ if (!mark) {
394
+ return null;
395
+ }
396
+
397
+ const textColor = mark.style.getPropertyValue('color');
398
+
399
+ if (textColor && textColor !== 'transparent') {
400
+ return { value: textColor, mode: 'color' };
401
+ }
402
+
403
+ const bgColor = mark.style.getPropertyValue('background-color');
404
+
405
+ if (bgColor && bgColor !== 'transparent') {
406
+ return { value: bgColor, mode: 'background-color' };
407
+ }
408
+
409
+ return null;
410
+ }
411
+
412
+ /**
413
+ * Restore selection from saved state if available
414
+ */
415
+ private restoreSelectionIfSaved(): void {
416
+ if (this.selection.savedSelectionRange) {
417
+ this.selection.removeFakeBackground();
418
+ this.selection.restore();
419
+ this.selection.clearSaved();
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Find a single mark element that fully contains the range
425
+ * @param range - The range to check
426
+ */
427
+ private findContainingMark(range: Range): HTMLElement | null {
428
+ const startMark = findMarkElement(range.startContainer);
429
+ const endMark = findMarkElement(range.endContainer);
430
+
431
+ if (startMark && startMark === endMark) {
432
+ return startMark;
433
+ }
434
+
435
+ return null;
436
+ }
437
+
438
+ /**
439
+ * Remove a specific style property from nested mark elements within a range
440
+ * @param range - The range to process
441
+ * @param mode - The style property to remove
442
+ */
443
+ private removeNestedMarkStyle(range: Range, mode: ColorMode): void {
444
+ const liveMarks = collectFormattingAncestors(range, isMarkTag);
445
+
446
+ for (const mark of liveMarks) {
447
+ mark.style.removeProperty(mode);
448
+
449
+ const oppositeMode = OPPOSITE_MODE[mode];
450
+ const oppositeValue = mark.style.getPropertyValue(oppositeMode);
451
+ const hasOtherStyle = oppositeValue !== '' && oppositeValue !== 'transparent';
452
+
453
+ if (!hasOtherStyle) {
454
+ this.unwrapElement(mark);
455
+ } else {
456
+ this.ensureTransparentBg(mark);
457
+ }
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Split a mark element around a range so only the selected portion gets the new style.
463
+ * Produces up to three segments: before (original style), selected (new style), after (original style).
464
+ * @param mark - The existing mark element to split
465
+ * @param range - The selection range within the mark
466
+ * @param mode - The style property to set on the selected portion
467
+ * @param value - The CSS value for the style property
468
+ */
469
+ private splitMarkAroundRange(mark: HTMLElement, range: Range, mode: ColorMode, value: string): void {
470
+ const parent = mark.parentNode;
471
+
472
+ if (!parent) {
473
+ return;
474
+ }
475
+
476
+ const beforeRange = document.createRange();
477
+
478
+ beforeRange.setStart(mark, 0);
479
+ beforeRange.setEnd(range.startContainer, range.startOffset);
480
+
481
+ const afterRange = document.createRange();
482
+
483
+ afterRange.setStart(range.endContainer, range.endOffset);
484
+ afterRange.setEnd(mark, mark.childNodes.length);
485
+
486
+ const beforeContents = beforeRange.extractContents();
487
+ const selectedContents = range.extractContents();
488
+ const afterContents = afterRange.extractContents();
489
+
490
+ const newMark = document.createElement('mark');
491
+
492
+ newMark.style.cssText = mark.style.cssText;
493
+ newMark.style.setProperty(mode, value);
494
+ this.ensureTransparentBg(newMark);
495
+ newMark.appendChild(selectedContents);
496
+
497
+ const fragment = document.createDocumentFragment();
498
+
499
+ if (beforeContents.textContent) {
500
+ const beforeMark = document.createElement('mark');
501
+
502
+ beforeMark.style.cssText = mark.style.cssText;
503
+ this.ensureTransparentBg(beforeMark);
504
+ beforeMark.appendChild(beforeContents);
505
+ fragment.appendChild(beforeMark);
506
+ }
507
+
508
+ fragment.appendChild(newMark);
509
+
510
+ if (afterContents.textContent) {
511
+ const afterMark = document.createElement('mark');
512
+
513
+ afterMark.style.cssText = mark.style.cssText;
514
+ this.ensureTransparentBg(afterMark);
515
+ afterMark.appendChild(afterContents);
516
+ fragment.appendChild(afterMark);
517
+ }
518
+
519
+ parent.replaceChild(fragment, mark);
520
+
521
+ const selection = window.getSelection();
522
+
523
+ if (selection) {
524
+ selection.removeAllRanges();
525
+
526
+ const newRange = document.createRange();
527
+
528
+ newRange.selectNodeContents(newMark);
529
+ selection.addRange(newRange);
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Split mark elements at range boundaries so that marks extending
535
+ * beyond the selection are separated into inside/outside portions.
536
+ * This preserves mark styling on text outside the selection range.
537
+ * @param range - The selection range
538
+ */
539
+ private splitMarksAtBoundaries(range: Range): void {
540
+ const marks = collectFormattingAncestors(range, isMarkTag);
541
+
542
+ for (const mark of marks) {
543
+ const markRange = document.createRange();
544
+
545
+ markRange.selectNodeContents(mark);
546
+
547
+ const rangeStartsBeforeMark = range.compareBoundaryPoints(Range.START_TO_START, markRange) <= 0;
548
+ const rangeEndsAfterMark = range.compareBoundaryPoints(Range.END_TO_END, markRange) >= 0;
549
+
550
+ if (rangeStartsBeforeMark && rangeEndsAfterMark) {
551
+ /**
552
+ * Range fully contains the mark — no split needed
553
+ */
554
+ continue;
555
+ }
556
+
557
+ if (!mark.parentNode) {
558
+ continue;
559
+ }
560
+
561
+ /**
562
+ * Split at the end boundary first (to avoid invalidating start offsets)
563
+ */
564
+ if (!rangeEndsAfterMark) {
565
+ this.extractTrailingMark(mark, range.endContainer, range.endOffset);
566
+ }
567
+
568
+ /**
569
+ * Split at the start boundary
570
+ */
571
+ if (!rangeStartsBeforeMark) {
572
+ this.extractLeadingMark(mark, range.startContainer, range.startOffset);
573
+ }
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Extract content after a boundary point from a mark into a new sibling mark.
579
+ * @param mark - The mark to split
580
+ * @param boundaryNode - The node at the boundary
581
+ * @param boundaryOffset - The offset at the boundary
582
+ */
583
+ private extractTrailingMark(mark: HTMLElement, boundaryNode: Node, boundaryOffset: number): void {
584
+ const trailingRange = document.createRange();
585
+
586
+ trailingRange.setStart(boundaryNode, boundaryOffset);
587
+ trailingRange.setEnd(mark, mark.childNodes.length);
588
+
589
+ const contents = trailingRange.extractContents();
590
+
591
+ if (!contents.textContent) {
592
+ return;
593
+ }
594
+
595
+ const trailingMark = document.createElement('mark');
596
+
597
+ trailingMark.style.cssText = mark.style.cssText;
598
+ trailingMark.appendChild(contents);
599
+ mark.after(trailingMark);
600
+ }
601
+
602
+ /**
603
+ * Extract content before a boundary point from a mark into a new sibling mark.
604
+ * @param mark - The mark to split
605
+ * @param boundaryNode - The node at the boundary
606
+ * @param boundaryOffset - The offset at the boundary
607
+ */
608
+ private extractLeadingMark(mark: HTMLElement, boundaryNode: Node, boundaryOffset: number): void {
609
+ const leadingRange = document.createRange();
610
+
611
+ leadingRange.setStart(mark, 0);
612
+ leadingRange.setEnd(boundaryNode, boundaryOffset);
613
+
614
+ const contents = leadingRange.extractContents();
615
+
616
+ if (!contents.textContent) {
617
+ return;
618
+ }
619
+
620
+ const leadingMark = document.createElement('mark');
621
+
622
+ leadingMark.style.cssText = mark.style.cssText;
623
+ leadingMark.appendChild(contents);
624
+ mark.before(leadingMark);
625
+ }
626
+
627
+ /**
628
+ * Restore selection by finding the selected text within a surviving parent.
629
+ * Used as a fallback when range anchors become stale after DOM mutations.
630
+ * @param selection - The window selection to restore
631
+ * @param parent - A parent element that survived the DOM mutation
632
+ * @param text - The text content that was selected before mutation
633
+ */
634
+ private restoreSelectionByText(
635
+ selection: Selection,
636
+ parent: HTMLElement | null,
637
+ text: string
638
+ ): void {
639
+ if (!parent || text.length === 0) {
640
+ return;
641
+ }
642
+
643
+ const fullText = parent.textContent ?? '';
644
+ const startIdx = fullText.indexOf(text);
645
+
646
+ if (startIdx === -1) {
647
+ return;
648
+ }
649
+
650
+ const endIdx = startIdx + text.length;
651
+
652
+ /**
653
+ * Walk text nodes to find the nodes and offsets corresponding
654
+ * to the character positions in the parent's textContent
655
+ */
656
+ const { startNode, startNodeOffset, endNode, endNodeOffset } = this.findTextBoundaries(parent, startIdx, endIdx);
657
+
658
+ if (startNode && endNode) {
659
+ const restoredRange = document.createRange();
660
+
661
+ restoredRange.setStart(startNode, startNodeOffset);
662
+ restoredRange.setEnd(endNode, endNodeOffset);
663
+ selection.removeAllRanges();
664
+ selection.addRange(restoredRange);
665
+ }
666
+ }
667
+
668
+ /**
669
+ * Walk text nodes within a parent to find the nodes and offsets
670
+ * corresponding to character positions in the parent's textContent.
671
+ * @param parent - The parent element to walk
672
+ * @param startIdx - The start character index
673
+ * @param endIdx - The end character index
674
+ * @returns An object with the start/end nodes and their offsets
675
+ */
676
+ private findTextBoundaries(
677
+ parent: HTMLElement,
678
+ startIdx: number,
679
+ endIdx: number
680
+ ): { startNode: Text | null; startNodeOffset: number; endNode: Text | null; endNodeOffset: number } {
681
+ const walker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT);
682
+ const result = { startNode: null as Text | null, startNodeOffset: 0, endNode: null as Text | null, endNodeOffset: 0 };
683
+ const charCounter = { value: 0 };
684
+
685
+ while (walker.nextNode()) {
686
+ const node = walker.currentNode as Text;
687
+ const nodeLength = node.textContent?.length ?? 0;
688
+
689
+ if (result.startNode === null && charCounter.value + nodeLength > startIdx) {
690
+ result.startNode = node;
691
+ result.startNodeOffset = startIdx - charCounter.value;
692
+ }
693
+
694
+ if (charCounter.value + nodeLength >= endIdx) {
695
+ result.endNode = node;
696
+ result.endNodeOffset = endIdx - charCounter.value;
697
+ break;
698
+ }
699
+
700
+ charCounter.value += nodeLength;
701
+ }
702
+
703
+ return result;
704
+ }
705
+
706
+ /**
707
+ * Unwrap an element, moving its children to its parent
708
+ * @param element - Element to unwrap
709
+ */
710
+ private unwrapElement(element: HTMLElement): void {
711
+ const parent = element.parentNode;
712
+
713
+ if (!parent) {
714
+ return;
715
+ }
716
+
717
+ while (element.firstChild) {
718
+ parent.insertBefore(element.firstChild, element);
719
+ }
720
+
721
+ parent.removeChild(element);
722
+ }
723
+
724
+ /**
725
+ * Ensure a mark with text color has an explicit transparent background
726
+ * to override the browser's default yellow <mark> background.
727
+ * @param mark - The mark element to check
728
+ */
729
+ private ensureTransparentBg(mark: HTMLElement): void {
730
+ if (
731
+ mark.style.getPropertyValue('color') &&
732
+ !mark.style.getPropertyValue('background-color')
733
+ ) {
734
+ mark.style.setProperty('background-color', 'transparent');
735
+ }
736
+ }
737
+ }