@jackuait/blok 0.7.0-beta.1 → 0.7.0-beta.3

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 (256) hide show
  1. package/README.md +20 -4
  2. package/dist/blok.mjs +2 -2
  3. package/dist/chunks/{blok-ob9Fwr1L.mjs → blok-BlyYiZTm.mjs} +1840 -1654
  4. package/dist/chunks/{inline-tool-convert-CvFW2iie.mjs → constants-DEy4jBO5.mjs} +316 -297
  5. package/dist/chunks/{i18next-loader-Bu3vFvye.mjs → i18next-loader-Cfbv-x6v.mjs} +1 -1
  6. package/dist/chunks/index-Cu1w-sLZ.mjs +130 -0
  7. package/dist/chunks/{messages-D2NOpHn9.mjs → messages-0Pxnqd4N.mjs} +7 -0
  8. package/dist/chunks/{messages-GSByFygY.mjs → messages-0ZXYUq7S.mjs} +7 -0
  9. package/dist/{messages-BUl_Rcnj.mjs → chunks/messages-2OD2uUDS.mjs} +9 -2
  10. package/dist/{messages-CgTq3QhU.mjs → chunks/messages-7cEMfYzh.mjs} +7 -0
  11. package/dist/{messages-DlJbPF2T.mjs → chunks/messages-8mwfda1Q.mjs} +7 -0
  12. package/dist/{messages-D9ndgBnU.mjs → chunks/messages-B-FqWsBM.mjs} +7 -0
  13. package/dist/{messages-B217znr-.mjs → chunks/messages-B1jzqWiQ.mjs} +7 -0
  14. package/dist/{messages-BcpCubnC.mjs → chunks/messages-B5wk4Ezz.mjs} +7 -0
  15. package/dist/chunks/{messages-DRXWF0PV.mjs → messages-BAZ5Ld8x.mjs} +7 -0
  16. package/dist/{messages-CRJ_mchV.mjs → chunks/messages-BBhGp198.mjs} +7 -0
  17. package/dist/chunks/{messages-yHcs38yI.mjs → messages-BC9IjIb7.mjs} +7 -0
  18. package/dist/chunks/{messages-Cr94GzbX.mjs → messages-BFEmpeV-.mjs} +7 -0
  19. package/dist/chunks/{messages-ucTVgS5G.mjs → messages-BGqzTZy0.mjs} +7 -0
  20. package/dist/{messages-begYOTgC.mjs → chunks/messages-BICs1abK.mjs} +7 -0
  21. package/dist/chunks/{messages-DVQvl8Qj.mjs → messages-BJX6rOnd.mjs} +7 -0
  22. package/dist/chunks/{messages-Chb7k3Rg.mjs → messages-BL2bXRhN.mjs} +7 -0
  23. package/dist/{messages-Phkd7XmE.mjs → chunks/messages-BMs5qdlH.mjs} +7 -0
  24. package/dist/chunks/{messages-Cjjo7yHR.mjs → messages-BRsjUNwB.mjs} +7 -0
  25. package/dist/chunks/{messages-D4qqwVgQ.mjs → messages-BSqV8OUR.mjs} +7 -0
  26. package/dist/chunks/{messages-DviiFSv2.mjs → messages-BTqu3DfG.mjs} +7 -0
  27. package/dist/chunks/{messages-0AbcLMLm.mjs → messages-BXnDEsur.mjs} +7 -0
  28. package/dist/{messages-CmR9ftc_.mjs → chunks/messages-BYcre4-6.mjs} +7 -0
  29. package/dist/{messages-wmi-iFkH.mjs → chunks/messages-BZ9LRJf-.mjs} +7 -0
  30. package/dist/chunks/{messages-D00x4S8o.mjs → messages-BgypBy7y.mjs} +7 -0
  31. package/dist/{messages-96kNZDll.mjs → chunks/messages-BsuGf70G.mjs} +7 -0
  32. package/dist/chunks/{messages-v3GipbFl.mjs → messages-BwaoF4lQ.mjs} +7 -0
  33. package/dist/{messages-DDTQgImT.mjs → chunks/messages-C1l8_7-y.mjs} +7 -0
  34. package/dist/{messages-B1FZ8lxU.mjs → chunks/messages-C5NA_r9v.mjs} +7 -0
  35. package/dist/{messages-Cs8zmZ3L.mjs → chunks/messages-C6zgZ5pA.mjs} +7 -0
  36. package/dist/chunks/{messages-ZjUAIWb1.mjs → messages-CAo5ghFI.mjs} +7 -0
  37. package/dist/{messages-D5S1Dnpm.mjs → chunks/messages-CH9qlJ9I.mjs} +7 -0
  38. package/dist/{messages-D7u2bmP2.mjs → chunks/messages-CI0HqAeS.mjs} +7 -0
  39. package/dist/{messages-DH_jBeED.mjs → chunks/messages-CJJtms9k.mjs} +7 -0
  40. package/dist/{messages-CDBLbUOQ.mjs → chunks/messages-CM2hJqk6.mjs} +7 -0
  41. package/dist/chunks/{messages-8IPXkrDl.mjs → messages-CRMiDPIQ.mjs} +7 -0
  42. package/dist/chunks/{messages-Dzzn6XoD.mjs → messages-CWsZuBj1.mjs} +7 -0
  43. package/dist/chunks/{messages-CW4c4cRk.mjs → messages-C_gLHo6A.mjs} +7 -0
  44. package/dist/{messages-CH4hrauY.mjs → chunks/messages-Cbu-NUDn.mjs} +7 -0
  45. package/dist/{messages-RonBBCnh.mjs → chunks/messages-Cjb_MCeh.mjs} +7 -0
  46. package/dist/chunks/{messages-BJ6zrz2j.mjs → messages-ClXYO9Wn.mjs} +7 -0
  47. package/dist/chunks/{messages-CrCYPCk3.mjs → messages-CsH20vhP.mjs} +7 -0
  48. package/dist/{messages-CzK0LEhb.mjs → chunks/messages-CsjAGhzA.mjs} +7 -0
  49. package/dist/chunks/{messages-BZlmVRwn.mjs → messages-Cx7VKFOE.mjs} +7 -0
  50. package/dist/chunks/{messages-0E0AkrNu.mjs → messages-D3JeBwxo.mjs} +7 -0
  51. package/dist/chunks/{messages-D85FqxgY.mjs → messages-D541fieJ.mjs} +7 -0
  52. package/dist/{messages-4v4MuVEc.mjs → chunks/messages-D7XPdglc.mjs} +7 -0
  53. package/dist/{messages-BC8IN4Bf.mjs → chunks/messages-DBhylfvt.mjs} +7 -0
  54. package/dist/chunks/{messages-B8WNljW3.mjs → messages-DCA120lW.mjs} +7 -0
  55. package/dist/chunks/{messages-Cr49Nt3U.mjs → messages-DCf_xZMN.mjs} +7 -0
  56. package/dist/chunks/{messages-VDriF5Qy.mjs → messages-DDwXKCpe.mjs} +7 -0
  57. package/dist/{messages-b1EdvUm0.mjs → chunks/messages-DNKDlxcy.mjs} +7 -0
  58. package/dist/{messages-L_kl2Qvh.mjs → chunks/messages-DPvEjrGK.mjs} +7 -0
  59. package/dist/chunks/{messages-62v-CLC-.mjs → messages-DQ-AkNxA.mjs} +7 -0
  60. package/dist/chunks/{messages-DdK-nFGm.mjs → messages-DVuvkNap.mjs} +7 -0
  61. package/dist/{messages-DnVlmiNT.mjs → chunks/messages-DaglyqUT.mjs} +7 -0
  62. package/dist/{messages-Bm-E4iRC.mjs → chunks/messages-Di0bAfwA.mjs} +7 -0
  63. package/dist/{messages-D1mn7Zd5.mjs → chunks/messages-DuLct0Yr.mjs} +7 -0
  64. package/dist/{messages-8DeO60Oo.mjs → chunks/messages-DzEYYhZh.mjs} +7 -0
  65. package/dist/chunks/{messages-CfiyT2Wi.mjs → messages-DznNGAB2.mjs} +7 -0
  66. package/dist/chunks/{messages-DXktiao_.mjs → messages-DzoIzyu8.mjs} +7 -0
  67. package/dist/{messages-C_4otP7U.mjs → chunks/messages-QYOGmket.mjs} +7 -0
  68. package/dist/chunks/{messages-nefz1S71.mjs → messages-cEjGDAgI.mjs} +7 -0
  69. package/dist/chunks/{messages-jrncnb-H.mjs → messages-ddhvrdpE.mjs} +7 -0
  70. package/dist/chunks/{messages-DzqM3Fel.mjs → messages-mwfNK5nZ.mjs} +7 -0
  71. package/dist/chunks/{messages-Cl6ayUaq.mjs → messages-nG_vNDte.mjs} +7 -0
  72. package/dist/{messages-C4jL-90N.mjs → chunks/messages-tDq3Owh7.mjs} +7 -0
  73. package/dist/{messages-BI43k_BD.mjs → chunks/messages-x6VJVZKx.mjs} +7 -0
  74. package/dist/full.mjs +2 -2
  75. package/dist/locales.mjs +87 -80
  76. package/dist/{messages-D2NOpHn9.mjs → messages-0Pxnqd4N.mjs} +7 -0
  77. package/dist/{messages-GSByFygY.mjs → messages-0ZXYUq7S.mjs} +7 -0
  78. package/dist/{chunks/messages-BUl_Rcnj.mjs → messages-2OD2uUDS.mjs} +9 -2
  79. package/dist/{chunks/messages-CgTq3QhU.mjs → messages-7cEMfYzh.mjs} +7 -0
  80. package/dist/{chunks/messages-DlJbPF2T.mjs → messages-8mwfda1Q.mjs} +7 -0
  81. package/dist/{chunks/messages-D9ndgBnU.mjs → messages-B-FqWsBM.mjs} +7 -0
  82. package/dist/{chunks/messages-B217znr-.mjs → messages-B1jzqWiQ.mjs} +7 -0
  83. package/dist/{chunks/messages-BcpCubnC.mjs → messages-B5wk4Ezz.mjs} +7 -0
  84. package/dist/{messages-DRXWF0PV.mjs → messages-BAZ5Ld8x.mjs} +7 -0
  85. package/dist/{chunks/messages-CRJ_mchV.mjs → messages-BBhGp198.mjs} +7 -0
  86. package/dist/{messages-yHcs38yI.mjs → messages-BC9IjIb7.mjs} +7 -0
  87. package/dist/{messages-Cr94GzbX.mjs → messages-BFEmpeV-.mjs} +7 -0
  88. package/dist/{messages-ucTVgS5G.mjs → messages-BGqzTZy0.mjs} +7 -0
  89. package/dist/{chunks/messages-begYOTgC.mjs → messages-BICs1abK.mjs} +7 -0
  90. package/dist/{messages-DVQvl8Qj.mjs → messages-BJX6rOnd.mjs} +7 -0
  91. package/dist/{messages-Chb7k3Rg.mjs → messages-BL2bXRhN.mjs} +7 -0
  92. package/dist/{chunks/messages-Phkd7XmE.mjs → messages-BMs5qdlH.mjs} +7 -0
  93. package/dist/{messages-Cjjo7yHR.mjs → messages-BRsjUNwB.mjs} +7 -0
  94. package/dist/{messages-D4qqwVgQ.mjs → messages-BSqV8OUR.mjs} +7 -0
  95. package/dist/{messages-DviiFSv2.mjs → messages-BTqu3DfG.mjs} +7 -0
  96. package/dist/{messages-0AbcLMLm.mjs → messages-BXnDEsur.mjs} +7 -0
  97. package/dist/{chunks/messages-CmR9ftc_.mjs → messages-BYcre4-6.mjs} +7 -0
  98. package/dist/{chunks/messages-wmi-iFkH.mjs → messages-BZ9LRJf-.mjs} +7 -0
  99. package/dist/{messages-D00x4S8o.mjs → messages-BgypBy7y.mjs} +7 -0
  100. package/dist/{chunks/messages-96kNZDll.mjs → messages-BsuGf70G.mjs} +7 -0
  101. package/dist/{messages-v3GipbFl.mjs → messages-BwaoF4lQ.mjs} +7 -0
  102. package/dist/{chunks/messages-DDTQgImT.mjs → messages-C1l8_7-y.mjs} +7 -0
  103. package/dist/{chunks/messages-B1FZ8lxU.mjs → messages-C5NA_r9v.mjs} +7 -0
  104. package/dist/{chunks/messages-Cs8zmZ3L.mjs → messages-C6zgZ5pA.mjs} +7 -0
  105. package/dist/{messages-ZjUAIWb1.mjs → messages-CAo5ghFI.mjs} +7 -0
  106. package/dist/{chunks/messages-D5S1Dnpm.mjs → messages-CH9qlJ9I.mjs} +7 -0
  107. package/dist/{chunks/messages-D7u2bmP2.mjs → messages-CI0HqAeS.mjs} +7 -0
  108. package/dist/{chunks/messages-DH_jBeED.mjs → messages-CJJtms9k.mjs} +7 -0
  109. package/dist/{chunks/messages-CDBLbUOQ.mjs → messages-CM2hJqk6.mjs} +7 -0
  110. package/dist/{messages-8IPXkrDl.mjs → messages-CRMiDPIQ.mjs} +7 -0
  111. package/dist/{messages-Dzzn6XoD.mjs → messages-CWsZuBj1.mjs} +7 -0
  112. package/dist/{messages-CW4c4cRk.mjs → messages-C_gLHo6A.mjs} +7 -0
  113. package/dist/{chunks/messages-CH4hrauY.mjs → messages-Cbu-NUDn.mjs} +7 -0
  114. package/dist/{chunks/messages-RonBBCnh.mjs → messages-Cjb_MCeh.mjs} +7 -0
  115. package/dist/{messages-BJ6zrz2j.mjs → messages-ClXYO9Wn.mjs} +7 -0
  116. package/dist/{messages-CrCYPCk3.mjs → messages-CsH20vhP.mjs} +7 -0
  117. package/dist/{chunks/messages-CzK0LEhb.mjs → messages-CsjAGhzA.mjs} +7 -0
  118. package/dist/{messages-BZlmVRwn.mjs → messages-Cx7VKFOE.mjs} +7 -0
  119. package/dist/{messages-0E0AkrNu.mjs → messages-D3JeBwxo.mjs} +7 -0
  120. package/dist/{messages-D85FqxgY.mjs → messages-D541fieJ.mjs} +7 -0
  121. package/dist/{chunks/messages-4v4MuVEc.mjs → messages-D7XPdglc.mjs} +7 -0
  122. package/dist/{chunks/messages-BC8IN4Bf.mjs → messages-DBhylfvt.mjs} +7 -0
  123. package/dist/{messages-B8WNljW3.mjs → messages-DCA120lW.mjs} +7 -0
  124. package/dist/{messages-Cr49Nt3U.mjs → messages-DCf_xZMN.mjs} +7 -0
  125. package/dist/{messages-VDriF5Qy.mjs → messages-DDwXKCpe.mjs} +7 -0
  126. package/dist/{chunks/messages-b1EdvUm0.mjs → messages-DNKDlxcy.mjs} +7 -0
  127. package/dist/{chunks/messages-L_kl2Qvh.mjs → messages-DPvEjrGK.mjs} +7 -0
  128. package/dist/{messages-62v-CLC-.mjs → messages-DQ-AkNxA.mjs} +7 -0
  129. package/dist/{messages-DdK-nFGm.mjs → messages-DVuvkNap.mjs} +7 -0
  130. package/dist/{chunks/messages-DnVlmiNT.mjs → messages-DaglyqUT.mjs} +7 -0
  131. package/dist/{chunks/messages-Bm-E4iRC.mjs → messages-Di0bAfwA.mjs} +7 -0
  132. package/dist/{chunks/messages-D1mn7Zd5.mjs → messages-DuLct0Yr.mjs} +7 -0
  133. package/dist/{chunks/messages-8DeO60Oo.mjs → messages-DzEYYhZh.mjs} +7 -0
  134. package/dist/{messages-CfiyT2Wi.mjs → messages-DznNGAB2.mjs} +7 -0
  135. package/dist/{messages-DXktiao_.mjs → messages-DzoIzyu8.mjs} +7 -0
  136. package/dist/{chunks/messages-C_4otP7U.mjs → messages-QYOGmket.mjs} +7 -0
  137. package/dist/{messages-nefz1S71.mjs → messages-cEjGDAgI.mjs} +7 -0
  138. package/dist/{messages-jrncnb-H.mjs → messages-ddhvrdpE.mjs} +7 -0
  139. package/dist/{messages-DzqM3Fel.mjs → messages-mwfNK5nZ.mjs} +7 -0
  140. package/dist/{messages-Cl6ayUaq.mjs → messages-nG_vNDte.mjs} +7 -0
  141. package/dist/{chunks/messages-C4jL-90N.mjs → messages-tDq3Owh7.mjs} +7 -0
  142. package/dist/{chunks/messages-BI43k_BD.mjs → messages-x6VJVZKx.mjs} +7 -0
  143. package/dist/tools.mjs +443 -338
  144. package/package.json +1 -1
  145. package/src/components/i18n/locales/am/messages.json +7 -0
  146. package/src/components/i18n/locales/ar/messages.json +7 -0
  147. package/src/components/i18n/locales/az/messages.json +7 -0
  148. package/src/components/i18n/locales/bg/messages.json +7 -0
  149. package/src/components/i18n/locales/bn/messages.json +7 -0
  150. package/src/components/i18n/locales/bs/messages.json +7 -0
  151. package/src/components/i18n/locales/cs/messages.json +7 -0
  152. package/src/components/i18n/locales/da/messages.json +7 -0
  153. package/src/components/i18n/locales/de/messages.json +7 -0
  154. package/src/components/i18n/locales/dv/messages.json +7 -0
  155. package/src/components/i18n/locales/el/messages.json +7 -0
  156. package/src/components/i18n/locales/en/messages.json +7 -0
  157. package/src/components/i18n/locales/es/messages.json +7 -0
  158. package/src/components/i18n/locales/et/messages.json +7 -0
  159. package/src/components/i18n/locales/fa/messages.json +7 -0
  160. package/src/components/i18n/locales/fi/messages.json +7 -0
  161. package/src/components/i18n/locales/fil/messages.json +7 -0
  162. package/src/components/i18n/locales/fr/messages.json +7 -0
  163. package/src/components/i18n/locales/gu/messages.json +7 -0
  164. package/src/components/i18n/locales/he/messages.json +7 -0
  165. package/src/components/i18n/locales/hi/messages.json +7 -0
  166. package/src/components/i18n/locales/hr/messages.json +7 -0
  167. package/src/components/i18n/locales/hu/messages.json +7 -0
  168. package/src/components/i18n/locales/hy/messages.json +7 -0
  169. package/src/components/i18n/locales/id/messages.json +7 -0
  170. package/src/components/i18n/locales/it/messages.json +7 -0
  171. package/src/components/i18n/locales/ja/messages.json +7 -0
  172. package/src/components/i18n/locales/ka/messages.json +7 -0
  173. package/src/components/i18n/locales/km/messages.json +7 -0
  174. package/src/components/i18n/locales/kn/messages.json +7 -0
  175. package/src/components/i18n/locales/ko/messages.json +7 -0
  176. package/src/components/i18n/locales/ku/messages.json +7 -0
  177. package/src/components/i18n/locales/lo/messages.json +7 -0
  178. package/src/components/i18n/locales/lt/messages.json +7 -0
  179. package/src/components/i18n/locales/lv/messages.json +7 -0
  180. package/src/components/i18n/locales/mk/messages.json +7 -0
  181. package/src/components/i18n/locales/ml/messages.json +7 -0
  182. package/src/components/i18n/locales/mn/messages.json +7 -0
  183. package/src/components/i18n/locales/mr/messages.json +7 -0
  184. package/src/components/i18n/locales/ms/messages.json +7 -0
  185. package/src/components/i18n/locales/my/messages.json +7 -0
  186. package/src/components/i18n/locales/ne/messages.json +7 -0
  187. package/src/components/i18n/locales/nl/messages.json +7 -0
  188. package/src/components/i18n/locales/no/messages.json +7 -0
  189. package/src/components/i18n/locales/pa/messages.json +7 -0
  190. package/src/components/i18n/locales/pl/messages.json +7 -0
  191. package/src/components/i18n/locales/ps/messages.json +7 -0
  192. package/src/components/i18n/locales/pt/messages.json +7 -0
  193. package/src/components/i18n/locales/ro/messages.json +7 -0
  194. package/src/components/i18n/locales/ru/messages.json +7 -0
  195. package/src/components/i18n/locales/sd/messages.json +7 -0
  196. package/src/components/i18n/locales/si/messages.json +7 -0
  197. package/src/components/i18n/locales/sk/messages.json +7 -0
  198. package/src/components/i18n/locales/sl/messages.json +7 -0
  199. package/src/components/i18n/locales/sq/messages.json +7 -0
  200. package/src/components/i18n/locales/sr/messages.json +7 -0
  201. package/src/components/i18n/locales/sv/messages.json +7 -0
  202. package/src/components/i18n/locales/sw/messages.json +7 -0
  203. package/src/components/i18n/locales/ta/messages.json +7 -0
  204. package/src/components/i18n/locales/te/messages.json +7 -0
  205. package/src/components/i18n/locales/th/messages.json +7 -0
  206. package/src/components/i18n/locales/tr/messages.json +7 -0
  207. package/src/components/i18n/locales/ug/messages.json +7 -0
  208. package/src/components/i18n/locales/uk/messages.json +7 -0
  209. package/src/components/i18n/locales/ur/messages.json +7 -0
  210. package/src/components/i18n/locales/vi/messages.json +7 -0
  211. package/src/components/i18n/locales/yi/messages.json +7 -0
  212. package/src/components/i18n/locales/zh/messages.json +7 -0
  213. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +44 -2
  214. package/src/components/modules/blockEvents/index.ts +1 -3
  215. package/src/components/modules/blockManager/blockManager.ts +16 -0
  216. package/src/components/modules/blockManager/operations.ts +106 -9
  217. package/src/components/modules/blockSelection.ts +2 -0
  218. package/src/components/modules/caret.ts +49 -4
  219. package/src/components/modules/drag/DragController.ts +34 -2
  220. package/src/components/modules/paste/handlers/blok-data-handler.ts +50 -3
  221. package/src/components/modules/toolbar/index.ts +11 -16
  222. package/src/components/modules/ui.ts +22 -0
  223. package/src/components/ui/toolbox.ts +19 -3
  224. package/src/components/utils/notifier/draw.ts +116 -14
  225. package/src/components/utils/notifier/index.ts +31 -4
  226. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +2 -2
  227. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +6 -7
  228. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +2 -2
  229. package/src/components/utils/popover/popover-abstract.ts +2 -0
  230. package/src/components/utils/popover/popover-desktop.ts +39 -2
  231. package/src/stories/Block.stories.ts +21 -0
  232. package/src/stories/EditorModes.stories.ts +19 -0
  233. package/src/stories/InlineToolbar.stories.ts +41 -9
  234. package/src/stories/MarkerColors.stories.ts +48 -52
  235. package/src/stories/Notifier.stories.ts +19 -1
  236. package/src/stories/Placeholder.stories.ts +12 -0
  237. package/src/stories/Popover.stories.ts +26 -0
  238. package/src/stories/Selection.stories.ts +6 -0
  239. package/src/stories/Table.stories.ts +12 -0
  240. package/src/stories/Toolbar.stories.ts +9 -0
  241. package/src/stories/Toolbox.stories.ts +4 -0
  242. package/src/stories/Tooltip.stories.ts +6 -0
  243. package/src/stories/helpers.ts +63 -8
  244. package/src/styles/main.css +46 -0
  245. package/src/tools/header/index.ts +121 -22
  246. package/src/tools/table/index.ts +2 -3
  247. package/src/tools/table/table-add-controls.ts +29 -1
  248. package/src/tools/table/table-cell-blocks.ts +93 -0
  249. package/src/tools/toggle/constants.ts +2 -2
  250. package/src/tools/toggle/dom-builder.ts +32 -4
  251. package/src/tools/toggle/index.ts +26 -4
  252. package/src/tools/toggle/toggle-keyboard.ts +19 -2
  253. package/src/tools/toggle/toggle-lifecycle.ts +1 -0
  254. package/src/tools/toggle/toggle-shortcuts.ts +18 -8
  255. package/types/utils/popover/popover.d.ts +8 -0
  256. package/dist/chunks/index-CZmRzRIX.mjs +0 -78
@@ -81,6 +81,9 @@ export const PlaceholderOnlyOnFocus: Story = {
81
81
  placeholder: DEFAULT_PLACEHOLDER,
82
82
  data: undefined,
83
83
  },
84
+ parameters: {
85
+ chromatic: { delay: 500 },
86
+ },
84
87
  play: async ({ canvasElement, step }) => {
85
88
  await step('Wait for editor to initialize', async () => {
86
89
  await waitFor(
@@ -153,6 +156,9 @@ export const TypeAndClearPlaceholder: Story = {
153
156
  placeholder: DEFAULT_PLACEHOLDER,
154
157
  data: undefined,
155
158
  },
159
+ parameters: {
160
+ chromatic: { delay: 500 },
161
+ },
156
162
  play: async ({ canvasElement, step }) => {
157
163
  await step('Wait for editor to initialize', async () => {
158
164
  await waitFor(
@@ -229,6 +235,9 @@ export const MultipleBlocksPlaceholder: Story = {
229
235
  ],
230
236
  },
231
237
  },
238
+ parameters: {
239
+ chromatic: { delay: 500 },
240
+ },
232
241
  play: async ({ canvasElement, step }) => {
233
242
  await step('Wait for editor to initialize', async () => {
234
243
  await waitFor(
@@ -274,6 +283,9 @@ export const PlaceholderWithToolboxOpen: Story = {
274
283
  placeholder: DEFAULT_PLACEHOLDER,
275
284
  data: undefined,
276
285
  },
286
+ parameters: {
287
+ chromatic: { delay: 500 },
288
+ },
277
289
  play: async ({ canvasElement, step }) => {
278
290
  await step('Wait for editor and toolbar to initialize', async () => {
279
291
  await waitFor(
@@ -56,6 +56,7 @@ const meta: Meta<PopoverArgs> = {
56
56
  tags: ['autodocs'],
57
57
  args: {
58
58
  minHeight: 350,
59
+ width: 450,
59
60
  data: sampleData,
60
61
  },
61
62
  render: createEditor,
@@ -88,6 +89,9 @@ export const ItemHoverState: Story = {
88
89
  args: {
89
90
  data: sampleData,
90
91
  },
92
+ parameters: {
93
+ chromatic: { delay: 500 },
94
+ },
91
95
  play: async ({ canvasElement, step }) => {
92
96
  await step('Wait for editor and toolbar to initialize', async () => {
93
97
  await waitFor(
@@ -151,6 +155,9 @@ export const ItemFocusedState: Story = {
151
155
  args: {
152
156
  data: sampleData,
153
157
  },
158
+ parameters: {
159
+ chromatic: { delay: 500 },
160
+ },
154
161
  play: async ({ canvasElement, step }) => {
155
162
  await step('Wait for editor and toolbar to initialize', async () => {
156
163
  await waitFor(
@@ -230,6 +237,9 @@ export const BlockTunesPopover: Story = {
230
237
  args: {
231
238
  data: sampleData,
232
239
  },
240
+ parameters: {
241
+ chromatic: { delay: 500 },
242
+ },
233
243
  play: async ({ canvasElement, step }) => {
234
244
  await step('Wait for editor and toolbar to initialize', async () => {
235
245
  await waitFor(
@@ -289,6 +299,9 @@ export const ConfirmationState: Story = {
289
299
  args: {
290
300
  data: sampleData,
291
301
  },
302
+ parameters: {
303
+ chromatic: { delay: 500 },
304
+ },
292
305
  play: async ({ canvasElement, step }) => {
293
306
  await step('Wait for editor and toolbar to initialize', async () => {
294
307
  await waitFor(
@@ -382,6 +395,9 @@ export const SearchFiltering: Story = {
382
395
  args: {
383
396
  data: sampleData,
384
397
  },
398
+ parameters: {
399
+ chromatic: { delay: 500 },
400
+ },
385
401
  play: async ({ canvasElement, step }) => {
386
402
  await step('Wait for editor and toolbar to initialize', async () => {
387
403
  await waitFor(
@@ -460,6 +476,9 @@ export const DisabledItem: Story = {
460
476
  args: {
461
477
  data: sampleData,
462
478
  },
479
+ parameters: {
480
+ chromatic: { delay: 500 },
481
+ },
463
482
  play: async ({ canvasElement, step }) => {
464
483
  await step('Wait for editor and toolbar to initialize', async () => {
465
484
  await waitFor(
@@ -538,6 +557,7 @@ export const MobilePopover: Story = {
538
557
  args: {
539
558
  data: sampleData,
540
559
  minHeight: 400,
560
+ width: 0,
541
561
  },
542
562
  parameters: {
543
563
  viewport: {
@@ -545,6 +565,7 @@ export const MobilePopover: Story = {
545
565
  },
546
566
  chromatic: {
547
567
  viewports: [375],
568
+ delay: 500,
548
569
  },
549
570
  },
550
571
  play: async ({ canvasElement, step }) => {
@@ -603,6 +624,7 @@ export const MobileOverlay: Story = {
603
624
  args: {
604
625
  data: sampleData,
605
626
  minHeight: 400,
627
+ width: 0,
606
628
  },
607
629
  parameters: {
608
630
  viewport: {
@@ -610,6 +632,7 @@ export const MobileOverlay: Story = {
610
632
  },
611
633
  chromatic: {
612
634
  viewports: [375],
635
+ delay: 500,
613
636
  },
614
637
  },
615
638
  play: async ({ canvasElement, step }) => {
@@ -680,6 +703,9 @@ export const BlockSettingsConvertToOpen: Story = {
680
703
  header: Header,
681
704
  },
682
705
  },
706
+ parameters: {
707
+ chromatic: { delay: 500 },
708
+ },
683
709
  play: async ({ canvasElement, step }) => {
684
710
  await step('Wait for editor and toolbar to initialize', async () => {
685
711
  await waitFor(
@@ -82,6 +82,9 @@ export const MultiStringSelectionWithLinkInput: Story = {
82
82
  args: {
83
83
  data: multiLineData,
84
84
  },
85
+ parameters: {
86
+ chromatic: { delay: 500 },
87
+ },
85
88
  play: async ({ canvasElement, step }) => {
86
89
  await step('Wait for editor and toolbar to initialize', async () => {
87
90
  await waitFor(
@@ -169,6 +172,9 @@ export const CrossBlockSelectionWithLinkInput: Story = {
169
172
  args: {
170
173
  data: multiBlockData,
171
174
  },
175
+ parameters: {
176
+ chromatic: { delay: 500 },
177
+ },
172
178
  play: async ({ canvasElement, step }) => {
173
179
  await step('Wait for editor and toolbar to initialize', async () => {
174
180
  await waitFor(
@@ -596,6 +596,7 @@ export const ColumnGripsVisible: Story = {
596
596
  ],
597
597
  },
598
598
  },
599
+ parameters: { chromatic: { delay: 500 } },
599
600
  play: async ({ canvasElement, step }) => {
600
601
  await step('Wait for table', async () => {
601
602
  await waitForTable(canvasElement);
@@ -643,6 +644,7 @@ export const RowGripsVisible: Story = {
643
644
  ],
644
645
  },
645
646
  },
647
+ parameters: { chromatic: { delay: 500 } },
646
648
  play: async ({ canvasElement, step }) => {
647
649
  await step('Wait for table', async () => {
648
650
  await waitForTable(canvasElement);
@@ -689,6 +691,7 @@ export const ColumnGripActive: Story = {
689
691
  ],
690
692
  },
691
693
  },
694
+ parameters: { chromatic: { delay: 500 } },
692
695
  play: async ({ canvasElement, step }) => {
693
696
  await step('Wait for table', async () => {
694
697
  await waitForTable(canvasElement);
@@ -736,6 +739,7 @@ export const RowGripActive: Story = {
736
739
  ],
737
740
  },
738
741
  },
742
+ parameters: { chromatic: { delay: 500 } },
739
743
  play: async ({ canvasElement, step }) => {
740
744
  await step('Wait for table', async () => {
741
745
  await waitForTable(canvasElement);
@@ -786,6 +790,7 @@ export const CellFocused: Story = {
786
790
  ],
787
791
  },
788
792
  },
793
+ parameters: { chromatic: { delay: 500 } },
789
794
  play: async ({ canvasElement, step }) => {
790
795
  await step('Wait for table and toolbar', async () => {
791
796
  await waitForTable(canvasElement);
@@ -841,6 +846,7 @@ export const MultiCellSelection: Story = {
841
846
  ],
842
847
  },
843
848
  },
849
+ parameters: { chromatic: { delay: 500 } },
844
850
  play: async ({ canvasElement, step }) => {
845
851
  await step('Wait for table', async () => {
846
852
  await waitForTable(canvasElement);
@@ -881,6 +887,7 @@ export const SelectionPillExpanded: Story = {
881
887
  ],
882
888
  },
883
889
  },
890
+ parameters: { chromatic: { delay: 500 } },
884
891
  play: async ({ canvasElement, step }) => {
885
892
  await step('Wait for table', async () => {
886
893
  await waitForTable(canvasElement);
@@ -972,6 +979,7 @@ export const ScrollOverflow: Story = {
972
979
  ],
973
980
  },
974
981
  },
982
+ parameters: { chromatic: { delay: 500 } },
975
983
  play: async ({ canvasElement, step }) => {
976
984
  await step('Wait for table', async () => {
977
985
  await waitForTable(canvasElement);
@@ -1476,6 +1484,7 @@ export const AddButtonsVisible: Story = {
1476
1484
  ],
1477
1485
  },
1478
1486
  },
1487
+ parameters: { chromatic: { delay: 500 } },
1479
1488
  play: async ({ canvasElement, step }) => {
1480
1489
  await step('Wait for table', async () => {
1481
1490
  await waitForTable(canvasElement);
@@ -1523,6 +1532,7 @@ export const ResizeHandlesVisible: Story = {
1523
1532
  ],
1524
1533
  },
1525
1534
  },
1535
+ parameters: { chromatic: { delay: 500 } },
1526
1536
  play: async ({ canvasElement, step }) => {
1527
1537
  await step('Wait for table', async () => {
1528
1538
  await waitForTable(canvasElement);
@@ -1578,6 +1588,7 @@ export const RowDragInProgress: Story = {
1578
1588
  ],
1579
1589
  },
1580
1590
  },
1591
+ parameters: { chromatic: { delay: 500 } },
1581
1592
  play: async ({ canvasElement, step }) => {
1582
1593
  await step('Wait for table', async () => {
1583
1594
  await waitForTable(canvasElement);
@@ -1629,6 +1640,7 @@ export const ColumnDragInProgress: Story = {
1629
1640
  ],
1630
1641
  },
1631
1642
  },
1643
+ parameters: { chromatic: { delay: 500 } },
1632
1644
  play: async ({ canvasElement, step }) => {
1633
1645
  await step('Wait for table', async () => {
1634
1646
  await waitForTable(canvasElement);
@@ -75,6 +75,9 @@ export const ToolbarVisible: Story = {
75
75
  args: {
76
76
  data: sampleData,
77
77
  },
78
+ parameters: {
79
+ chromatic: { delay: 500 },
80
+ },
78
81
  play: async ({ canvasElement, step }) => {
79
82
  await step('Wait for editor and toolbar to initialize', async () => {
80
83
  await waitFor(
@@ -127,6 +130,9 @@ export const PlusButtonHover: Story = {
127
130
  args: {
128
131
  data: sampleData,
129
132
  },
133
+ parameters: {
134
+ chromatic: { delay: 500 },
135
+ },
130
136
  play: async ({ canvasElement, step }) => {
131
137
  await step('Wait for editor and toolbar to initialize', async () => {
132
138
  await waitFor(
@@ -181,6 +187,9 @@ export const SettingsButtonHover: Story = {
181
187
  args: {
182
188
  data: sampleData,
183
189
  },
190
+ parameters: {
191
+ chromatic: { delay: 500 },
192
+ },
184
193
  play: async ({ canvasElement, step }) => {
185
194
  await step('Wait for editor and toolbar to initialize', async () => {
186
195
  await waitFor(
@@ -62,6 +62,7 @@ const meta: Meta<ToolboxArgs> = {
62
62
  tags: ['autodocs'],
63
63
  args: {
64
64
  minHeight: 300,
65
+ width: 450,
65
66
  data: sampleData,
66
67
  },
67
68
  render: createEditor,
@@ -112,6 +113,9 @@ export const CustomIconsForAllTools: Story = {
112
113
  } as ToolSettings,
113
114
  },
114
115
  },
116
+ parameters: {
117
+ chromatic: { delay: 500 },
118
+ },
115
119
  play: async ({ canvasElement, step }) => {
116
120
  await step('Wait for editor and toolbar to initialize', async () => {
117
121
  await waitFor(
@@ -122,6 +122,9 @@ export const TooltipStates: Story = {
122
122
  delay: 0,
123
123
  },
124
124
  render: createAllStatesDemo,
125
+ parameters: {
126
+ chromatic: { delay: 500 },
127
+ },
125
128
  play: async ({ canvasElement, step }) => {
126
129
  const placements: TooltipArgs['placement'][] = ['bottom', 'top', 'left', 'right'];
127
130
  const triggerDefault = canvasElement.querySelector('[data-blok-testid="tooltip-trigger-default"]') as HTMLElement;
@@ -172,6 +175,9 @@ export const WithHTMLContent: Story = {
172
175
  content: '<strong>Bold</strong> tooltip',
173
176
  delay: 0,
174
177
  },
178
+ parameters: {
179
+ chromatic: { delay: 500 },
180
+ },
175
181
  play: async ({ canvasElement, step }) => {
176
182
  await step('Show tooltip with HTML content', async () => {
177
183
  const trigger = canvasElement.querySelector('[data-blok-testid="tooltip-trigger"]') as HTMLElement;
@@ -354,17 +354,23 @@ export const triggerSelectAll = (element: Element): void => {
354
354
 
355
355
  /**
356
356
  * Selects text within a block, handling DOM normalization (e.g., <b> → <strong>).
357
- * Focuses the contenteditable element and creates a proper selection.
357
+ * Simulates a click on the contenteditable, creates a proper selection, and waits
358
+ * for the debounced selection handler to fire.
359
+ *
360
+ * In headless Chromium (CI), programmatic selections can become collapsed before the
361
+ * 180ms debounced selectionchange handler fires. The simulateClick + focus + explicit
362
+ * delay pattern ensures the selection persists reliably.
363
+ *
358
364
  * @param block - The block wrapper element containing the contenteditable
359
365
  * @param selector - CSS selector for the element to select (e.g., 'strong', 'em', 'a')
360
366
  * @param contentEditableSelector - Selector for the contenteditable element
361
367
  * @returns True if selection was created successfully
362
368
  */
363
- export const selectTextInBlock = (
369
+ export const selectTextInBlock = async (
364
370
  block: Element,
365
371
  selector: string,
366
372
  contentEditableSelector = '[contenteditable="true"]'
367
- ): boolean => {
373
+ ): Promise<boolean> => {
368
374
  const contentEditable = block.querySelector(contentEditableSelector);
369
375
  const targetElement = block.querySelector(selector);
370
376
 
@@ -372,10 +378,8 @@ export const selectTextInBlock = (
372
378
  return false;
373
379
  }
374
380
 
375
- // Focus the contenteditable first to ensure selection is rendered
376
- if (contentEditable instanceof HTMLElement) {
377
- contentEditable.focus();
378
- }
381
+ // Simulate a full click sequence to establish proper focus context
382
+ simulateClick(contentEditable);
379
383
 
380
384
  // Create and apply the selection
381
385
  const range = document.createRange();
@@ -387,9 +391,17 @@ export const selectTextInBlock = (
387
391
  selection?.removeAllRanges();
388
392
  selection?.addRange(range);
389
393
 
390
- // Dispatch selectionchange event to trigger toolbar
394
+ // Re-focus to ensure selection is active after range is set
395
+ if (contentEditable instanceof HTMLElement) {
396
+ contentEditable.focus();
397
+ }
398
+
399
+ // Dispatch selectionchange event to trigger the debounced handler
391
400
  document.dispatchEvent(new Event('selectionchange'));
392
401
 
402
+ // Wait for the debounced selection handler (180ms) plus popover creation time
403
+ await new Promise((resolve) => setTimeout(resolve, 300));
404
+
393
405
  return true;
394
406
  };
395
407
 
@@ -464,3 +476,46 @@ export const waitForPointerEvents = (selector: string, timeout = 3000): Promise<
464
476
  check();
465
477
  });
466
478
  };
479
+
480
+ /**
481
+ * Waits for the inline toolbar to be fully opened (with popover content).
482
+ * Unlike simply checking for the toolbar wrapper element, this verifies that
483
+ * the popover container has been created inside the toolbar, which only happens
484
+ * after the `open()` method runs successfully.
485
+ *
486
+ * @param selector - CSS selector for the inline toolbar wrapper (default: data-blok-testid="inline-toolbar")
487
+ * @param timeout - Maximum time to wait in ms (default: 5000)
488
+ */
489
+ export const waitForInlineToolbarOpen = (
490
+ selector = '[data-blok-testid="inline-toolbar"]',
491
+ timeout = 5000
492
+ ): Promise<Element> => {
493
+ return new Promise((resolve, reject) => {
494
+ const startTime = Date.now();
495
+ const checkInterval = 50;
496
+
497
+ const check = (): void => {
498
+ const toolbar = document.querySelector(selector);
499
+ const popover = toolbar?.querySelector('[data-blok-testid="popover-container"]');
500
+
501
+ if (toolbar && popover) {
502
+ resolve(toolbar);
503
+
504
+ return;
505
+ }
506
+
507
+ if (Date.now() - startTime >= timeout) {
508
+ reject(new Error(
509
+ `Inline toolbar popover not found within ${timeout}ms. ` +
510
+ `Wrapper exists: ${toolbar !== null}, Popover exists: ${popover !== null}`
511
+ ));
512
+
513
+ return;
514
+ }
515
+
516
+ setTimeout(check, checkInterval);
517
+ };
518
+
519
+ check();
520
+ });
521
+ };
@@ -108,6 +108,9 @@
108
108
  --animate-wobble: wobble 400ms;
109
109
  --animate-rotation: rotation 1.2s infinite linear;
110
110
  --animate-notify-bounce-in: notifyBounceIn 600ms 1;
111
+ --animate-notify-slide-in: notifySlideIn 400ms cubic-bezier(0.16, 1, 0.3, 1) both;
112
+ --animate-notify-slide-out: notifySlideOut 250ms cubic-bezier(0.4, 0, 1, 1) both;
113
+ --animate-notify-progress: notifyProgress linear forwards;
111
114
 
112
115
  /* Keyframes */
113
116
  @keyframes fade-in {
@@ -166,6 +169,22 @@
166
169
  70% { transform: scale(0.9); }
167
170
  100% { transform: scale(1); }
168
171
  }
172
+
173
+ @keyframes notifySlideIn {
174
+ 0% { opacity: 0; transform: translateY(16px); }
175
+ 70% { opacity: 1; transform: translateY(-2px); }
176
+ 100% { opacity: 1; transform: translateY(0); }
177
+ }
178
+
179
+ @keyframes notifySlideOut {
180
+ 0% { opacity: 1; transform: translateY(0); }
181
+ 100% { opacity: 0; transform: translateY(8px); }
182
+ }
183
+
184
+ @keyframes notifyProgress {
185
+ 0% { width: 100%; }
186
+ 100% { width: 0%; }
187
+ }
169
188
  }
170
189
 
171
190
  /*
@@ -308,6 +327,19 @@
308
327
  @apply p-0 m-0 min-h-[1.6em];
309
328
  }
310
329
 
330
+ /**
331
+ * Exclude bare 'color' from transition-property inside table cells.
332
+ * StyleManager applies transition-colors (which includes 'color') for smooth
333
+ * selection-state changes, but inside table cells inherited text-color changes
334
+ * trigger a 150ms flash from black → target. Keep all other transition
335
+ * properties so background-color selection highlighting still animates.
336
+ */
337
+ [data-blok-table-cell] [data-blok-element-content] {
338
+ transition-property: background-color, border-color, outline-color,
339
+ text-decoration-color, fill, stroke, --tw-gradient-from,
340
+ --tw-gradient-via, --tw-gradient-to;
341
+ }
342
+
311
343
  /**
312
344
  * Table heading styles
313
345
  * Applied to first row (heading row) and first column (heading column) cells
@@ -324,3 +356,17 @@
324
356
  [data-blok-table-haze][data-blok-table-haze-visible] {
325
357
  @apply opacity-100;
326
358
  }
359
+
360
+ /**
361
+ * Slash search input appearance
362
+ * When the user types "/" to open the toolbox, the contenteditable
363
+ * transforms to look like a search input with a placeholder.
364
+ */
365
+ [data-blok-slash-search] {
366
+ @apply bg-[#F8F8F8] rounded-lg transition-colors duration-150 max-w-[240px];
367
+ }
368
+
369
+ [data-blok-slash-search]::after {
370
+ content: attr(data-blok-slash-search);
371
+ @apply text-gray-text font-medium text-base pointer-events-none;
372
+ }
@@ -23,7 +23,8 @@ import { IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconHeading, IconToggle
23
23
  import { PLACEHOLDER_CLASSES, setupPlaceholder } from '../../components/utils/placeholder';
24
24
  import { translateToolTitle } from '../../components/utils/tools';
25
25
  import { twMerge } from '../../components/utils/tw';
26
- import { ARROW_ICON, ARROW_STYLES, TOGGLE_ATTR, TOGGLE_WRAPPER_STYLES } from '../toggle/constants';
26
+ import { ARROW_ICON, TOGGLE_ATTR, TOGGLE_WRAPPER_STYLES } from '../toggle/constants';
27
+ import { buildArrow } from '../toggle/dom-builder';
27
28
  import { updateArrowState, updateChildrenVisibility } from '../toggle/toggle-lifecycle';
28
29
 
29
30
  /**
@@ -216,6 +217,22 @@ export class Header implements BlockTool {
216
217
  normalized.isToggleable = true;
217
218
  }
218
219
 
220
+ /**
221
+ * Sanitize text to remove any previously saved arrow HTML (backwards compatibility)
222
+ */
223
+ if (normalized.text) {
224
+ const temp = document.createElement('div');
225
+
226
+ temp.innerHTML = normalized.text;
227
+
228
+ const arrowEl = temp.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
229
+
230
+ if (arrowEl) {
231
+ arrowEl.remove();
232
+ normalized.text = temp.innerHTML;
233
+ }
234
+ }
235
+
219
236
  return normalized;
220
237
  }
221
238
 
@@ -238,6 +255,42 @@ export class Header implements BlockTool {
238
255
  }
239
256
  }
240
257
 
258
+ /**
259
+ * Expand the toggle heading (no-op if not toggleable or already expanded).
260
+ * Can be called externally via block.call('expand').
261
+ */
262
+ public expand(): void {
263
+ if (!this._data.isToggleable || this._isOpen) {
264
+ return;
265
+ }
266
+
267
+ this._isOpen = true;
268
+
269
+ if (this._arrowElement && this._element) {
270
+ updateArrowState(this._arrowElement, this._element, this._isOpen);
271
+ }
272
+
273
+ this.updateChildrenVisibility();
274
+ }
275
+
276
+ /**
277
+ * Collapse the toggle heading (no-op if not toggleable or already collapsed).
278
+ * Can be called externally via block.call('collapse').
279
+ */
280
+ public collapse(): void {
281
+ if (!this._data.isToggleable || !this._isOpen) {
282
+ return;
283
+ }
284
+
285
+ this._isOpen = false;
286
+
287
+ if (this._arrowElement && this._element) {
288
+ updateArrowState(this._arrowElement, this._element, this._isOpen);
289
+ }
290
+
291
+ this.updateChildrenVisibility();
292
+ }
293
+
241
294
  /**
242
295
  * Returns header block tunes config
243
296
  *
@@ -357,7 +410,34 @@ export class Header implements BlockTool {
357
410
  * @param data - saved data to merge with current block
358
411
  */
359
412
  public merge(data: HeaderData): void {
360
- this._element.insertAdjacentHTML('beforeend', data.text);
413
+ /**
414
+ * Strip any arrow HTML from incoming data to prevent injection of toggle markup
415
+ */
416
+ const tempDiv = document.createElement('div');
417
+ tempDiv.innerHTML = data.text;
418
+ const arrowInData = tempDiv.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
419
+
420
+ if (arrowInData) {
421
+ arrowInData.remove();
422
+ }
423
+
424
+ const cleanText = tempDiv.innerHTML;
425
+
426
+ /**
427
+ * Strip arrow from current element, append text, then re-add arrow.
428
+ * This ensures text is appended to the content, not interleaved with toggle markup.
429
+ */
430
+ const arrowEl = this._element.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
431
+
432
+ if (arrowEl) {
433
+ arrowEl.remove();
434
+ }
435
+
436
+ this._element.insertAdjacentHTML('beforeend', cleanText);
437
+
438
+ if (arrowEl && this._data.isToggleable) {
439
+ this._element.prepend(arrowEl);
440
+ }
361
441
  }
362
442
 
363
443
  /**
@@ -378,8 +458,20 @@ export class Header implements BlockTool {
378
458
  * @returns saved data
379
459
  */
380
460
  public save(toolsContent: HTMLHeadingElement): HeaderData {
461
+ /**
462
+ * Clone the element and strip the arrow from the clone to read innerHTML
463
+ * without mutating the live DOM — DOM mutations during save would trigger
464
+ * the MutationObserver → didMutated → syncBlockDataToYjs → save() loop.
465
+ */
466
+ const clone = toolsContent.cloneNode(true) as HTMLHeadingElement;
467
+ const arrowEl = clone.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
468
+
469
+ if (arrowEl) {
470
+ arrowEl.remove();
471
+ }
472
+
381
473
  const data: HeaderData = {
382
- text: toolsContent.innerHTML,
474
+ text: clone.innerHTML,
383
475
  level: this.currentLevel.number,
384
476
  };
385
477
 
@@ -426,7 +518,24 @@ export class Header implements BlockTool {
426
518
  * @returns Current data
427
519
  */
428
520
  public get data(): HeaderData {
521
+ /**
522
+ * Strip arrow element before reading innerHTML to avoid capturing toggle markup
523
+ */
524
+ const arrowEl = this._element.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
525
+
526
+ if (arrowEl) {
527
+ arrowEl.remove();
528
+ }
529
+
429
530
  this._data.text = this._element.innerHTML;
531
+
532
+ /**
533
+ * Re-add arrow after reading so the DOM is not mutated
534
+ */
535
+ if (arrowEl && this._data.isToggleable) {
536
+ this._element.prepend(arrowEl);
537
+ }
538
+
430
539
  this._data.level = this.currentLevel.number;
431
540
 
432
541
  return this._data;
@@ -568,25 +677,7 @@ export class Header implements BlockTool {
568
677
  * @returns The arrow element
569
678
  */
570
679
  private buildArrow(): HTMLElement {
571
- const arrow = document.createElement('div');
572
- arrow.className = ARROW_STYLES;
573
- arrow.setAttribute(TOGGLE_ATTR.toggleArrow, '');
574
- arrow.setAttribute('role', 'button');
575
- arrow.setAttribute('tabindex', '-1');
576
- arrow.setAttribute('aria-label', 'Expand');
577
- arrow.contentEditable = 'false';
578
- arrow.innerHTML = ARROW_ICON;
579
-
580
- if (this._isOpen) {
581
- arrow.style.transform = 'rotate(90deg)';
582
- }
583
-
584
- arrow.addEventListener('click', (event: MouseEvent) => {
585
- event.stopPropagation();
586
- this.toggleOpen();
587
- });
588
-
589
- return arrow;
680
+ return buildArrow(this._isOpen, () => this.toggleOpen(), { contentEditableFalse: true });
590
681
  }
591
682
 
592
683
  /**
@@ -596,6 +687,14 @@ export class Header implements BlockTool {
596
687
  private toggleIsToggleable(): void {
597
688
  const wasToggleable = this._data.isToggleable === true;
598
689
 
690
+ /**
691
+ * If disabling toggle, ensure children are visible before removing toggle state
692
+ */
693
+ if (wasToggleable) {
694
+ updateChildrenVisibility(this.api, this.blockId ?? '', true);
695
+ this._isOpen = false;
696
+ }
697
+
599
698
  this.data = {
600
699
  level: this._data.level,
601
700
  text: this._data.text,