@jackuait/blok 0.4.1-beta.1 → 0.4.1-beta.12

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 (403) hide show
  1. package/README.md +138 -17
  2. package/codemod/README.md +45 -7
  3. package/codemod/migrate-editorjs-to-blok.js +960 -92
  4. package/codemod/test.js +780 -77
  5. package/dist/blok.mjs +5 -2
  6. package/dist/chunks/blok-BU6NwVkN.mjs +13239 -0
  7. package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
  8. package/dist/chunks/i18next-loader-D8GzSwio.mjs +43 -0
  9. package/dist/{index-CEXLTV6f.mjs → chunks/index-C5e_WLFg.mjs} +2 -2
  10. package/dist/chunks/inline-tool-convert-CLUxkCe_.mjs +1990 -0
  11. package/dist/chunks/messages-0tDXLuyH.mjs +48 -0
  12. package/dist/chunks/messages-2_xedlYw.mjs +48 -0
  13. package/dist/chunks/messages-AHESHJm_.mjs +48 -0
  14. package/dist/chunks/messages-B5hdXZwA.mjs +48 -0
  15. package/dist/chunks/messages-B5jGUnOy.mjs +48 -0
  16. package/dist/chunks/messages-B5puUm7R.mjs +48 -0
  17. package/dist/chunks/messages-B66ZSDCJ.mjs +48 -0
  18. package/dist/chunks/messages-B9Oba7sq.mjs +48 -0
  19. package/dist/chunks/messages-BA0rcTCY.mjs +48 -0
  20. package/dist/chunks/messages-BBJgd5jG.mjs +48 -0
  21. package/dist/chunks/messages-BPqWKx5Z.mjs +48 -0
  22. package/dist/chunks/messages-Bdv-IkfG.mjs +48 -0
  23. package/dist/chunks/messages-BeUhMpsr.mjs +48 -0
  24. package/dist/chunks/messages-Bf6Y3_GI.mjs +48 -0
  25. package/dist/chunks/messages-BiExzWJv.mjs +48 -0
  26. package/dist/chunks/messages-BlpqL8vG.mjs +48 -0
  27. package/dist/chunks/messages-BmKCChWZ.mjs +48 -0
  28. package/dist/chunks/messages-Bn253WWC.mjs +48 -0
  29. package/dist/chunks/messages-BrJHUxQL.mjs +48 -0
  30. package/dist/chunks/messages-C5b7hr_E.mjs +48 -0
  31. package/dist/chunks/messages-C7I_AVH2.mjs +48 -0
  32. package/dist/chunks/messages-CJoBtXU6.mjs +48 -0
  33. package/dist/chunks/messages-CQj2JU2j.mjs +48 -0
  34. package/dist/chunks/messages-CUZ1x1QD.mjs +48 -0
  35. package/dist/chunks/messages-CUy1vn-b.mjs +48 -0
  36. package/dist/chunks/messages-CVeWVKsV.mjs +48 -0
  37. package/dist/chunks/messages-CXHd9SUK.mjs +48 -0
  38. package/dist/chunks/messages-CbMyJSzS.mjs +48 -0
  39. package/dist/chunks/messages-CbhuIWRJ.mjs +48 -0
  40. package/dist/chunks/messages-CeCjVKMW.mjs +48 -0
  41. package/dist/chunks/messages-Cj-t1bdy.mjs +48 -0
  42. package/dist/chunks/messages-CkFT2gle.mjs +48 -0
  43. package/dist/chunks/messages-Cm9aLHeX.mjs +48 -0
  44. package/dist/chunks/messages-CnvW8Slp.mjs +48 -0
  45. package/dist/chunks/messages-Cr-RJ7YB.mjs +48 -0
  46. package/dist/chunks/messages-CrsJ1TEJ.mjs +48 -0
  47. package/dist/chunks/messages-Cu08aLS3.mjs +48 -0
  48. package/dist/chunks/messages-CvaqJFN-.mjs +48 -0
  49. package/dist/chunks/messages-CyDU5lz9.mjs +48 -0
  50. package/dist/chunks/messages-CySyfkMU.mjs +48 -0
  51. package/dist/chunks/messages-Cyi2AMmz.mjs +48 -0
  52. package/dist/chunks/messages-D00OjS2n.mjs +48 -0
  53. package/dist/chunks/messages-DDLgIPDF.mjs +48 -0
  54. package/dist/chunks/messages-DMQIHGRj.mjs +48 -0
  55. package/dist/chunks/messages-DOlC_Tty.mjs +48 -0
  56. package/dist/chunks/messages-DV6shA9b.mjs +48 -0
  57. package/dist/chunks/messages-DY94ykcE.mjs +48 -0
  58. package/dist/chunks/messages-DbVquYKN.mjs +48 -0
  59. package/dist/chunks/messages-DcKOuncK.mjs +48 -0
  60. package/dist/chunks/messages-Dg92dXZ5.mjs +48 -0
  61. package/dist/chunks/messages-DnbbyJT3.mjs +48 -0
  62. package/dist/chunks/messages-DteYq0rv.mjs +48 -0
  63. package/dist/chunks/messages-GC2PhgV3.mjs +48 -0
  64. package/dist/chunks/messages-JGsXAReJ.mjs +48 -0
  65. package/dist/chunks/messages-JZUhXTuV.mjs +48 -0
  66. package/dist/chunks/messages-LvFKBBPa.mjs +48 -0
  67. package/dist/chunks/messages-NP1myMGI.mjs +48 -0
  68. package/dist/chunks/messages-Q4kc_ZtL.mjs +48 -0
  69. package/dist/chunks/messages-RvMHb2Ht.mjs +48 -0
  70. package/dist/chunks/messages-ftMcCEuO.mjs +48 -0
  71. package/dist/chunks/messages-o24dK6CU.mjs +48 -0
  72. package/dist/chunks/messages-pA5TvcAj.mjs +48 -0
  73. package/dist/chunks/messages-rRSHQDCX.mjs +48 -0
  74. package/dist/chunks/messages-srxrv8Yh.mjs +48 -0
  75. package/dist/chunks/messages-wdqp4610.mjs +48 -0
  76. package/dist/chunks/messages-zS1AXZ0y.mjs +48 -0
  77. package/dist/chunks/messages-zSzDzXej.mjs +48 -0
  78. package/dist/full.mjs +50 -0
  79. package/dist/locales.mjs +228 -0
  80. package/dist/messages-0tDXLuyH.mjs +48 -0
  81. package/dist/messages-2_xedlYw.mjs +48 -0
  82. package/dist/messages-AHESHJm_.mjs +48 -0
  83. package/dist/messages-B5hdXZwA.mjs +48 -0
  84. package/dist/messages-B5jGUnOy.mjs +48 -0
  85. package/dist/messages-B5puUm7R.mjs +48 -0
  86. package/dist/messages-B66ZSDCJ.mjs +48 -0
  87. package/dist/messages-B9Oba7sq.mjs +48 -0
  88. package/dist/messages-BA0rcTCY.mjs +48 -0
  89. package/dist/messages-BBJgd5jG.mjs +48 -0
  90. package/dist/messages-BPqWKx5Z.mjs +48 -0
  91. package/dist/messages-Bdv-IkfG.mjs +48 -0
  92. package/dist/messages-BeUhMpsr.mjs +48 -0
  93. package/dist/messages-Bf6Y3_GI.mjs +48 -0
  94. package/dist/messages-BiExzWJv.mjs +48 -0
  95. package/dist/messages-BlpqL8vG.mjs +48 -0
  96. package/dist/messages-BmKCChWZ.mjs +48 -0
  97. package/dist/messages-Bn253WWC.mjs +48 -0
  98. package/dist/messages-BrJHUxQL.mjs +48 -0
  99. package/dist/messages-C5b7hr_E.mjs +48 -0
  100. package/dist/messages-C7I_AVH2.mjs +48 -0
  101. package/dist/messages-CJoBtXU6.mjs +48 -0
  102. package/dist/messages-CQj2JU2j.mjs +48 -0
  103. package/dist/messages-CUZ1x1QD.mjs +48 -0
  104. package/dist/messages-CUy1vn-b.mjs +48 -0
  105. package/dist/messages-CVeWVKsV.mjs +48 -0
  106. package/dist/messages-CXHd9SUK.mjs +48 -0
  107. package/dist/messages-CbMyJSzS.mjs +48 -0
  108. package/dist/messages-CbhuIWRJ.mjs +48 -0
  109. package/dist/messages-CeCjVKMW.mjs +48 -0
  110. package/dist/messages-Cj-t1bdy.mjs +48 -0
  111. package/dist/messages-CkFT2gle.mjs +48 -0
  112. package/dist/messages-Cm9aLHeX.mjs +48 -0
  113. package/dist/messages-CnvW8Slp.mjs +48 -0
  114. package/dist/messages-Cr-RJ7YB.mjs +48 -0
  115. package/dist/messages-CrsJ1TEJ.mjs +48 -0
  116. package/dist/messages-Cu08aLS3.mjs +48 -0
  117. package/dist/messages-CvaqJFN-.mjs +48 -0
  118. package/dist/messages-CyDU5lz9.mjs +48 -0
  119. package/dist/messages-CySyfkMU.mjs +48 -0
  120. package/dist/messages-Cyi2AMmz.mjs +48 -0
  121. package/dist/messages-D00OjS2n.mjs +48 -0
  122. package/dist/messages-DDLgIPDF.mjs +48 -0
  123. package/dist/messages-DMQIHGRj.mjs +48 -0
  124. package/dist/messages-DOlC_Tty.mjs +48 -0
  125. package/dist/messages-DV6shA9b.mjs +48 -0
  126. package/dist/messages-DY94ykcE.mjs +48 -0
  127. package/dist/messages-DbVquYKN.mjs +48 -0
  128. package/dist/messages-DcKOuncK.mjs +48 -0
  129. package/dist/messages-Dg92dXZ5.mjs +48 -0
  130. package/dist/messages-DnbbyJT3.mjs +48 -0
  131. package/dist/messages-DteYq0rv.mjs +48 -0
  132. package/dist/messages-GC2PhgV3.mjs +48 -0
  133. package/dist/messages-JGsXAReJ.mjs +48 -0
  134. package/dist/messages-JZUhXTuV.mjs +48 -0
  135. package/dist/messages-LvFKBBPa.mjs +48 -0
  136. package/dist/messages-NP1myMGI.mjs +48 -0
  137. package/dist/messages-Q4kc_ZtL.mjs +48 -0
  138. package/dist/messages-RvMHb2Ht.mjs +48 -0
  139. package/dist/messages-ftMcCEuO.mjs +48 -0
  140. package/dist/messages-o24dK6CU.mjs +48 -0
  141. package/dist/messages-pA5TvcAj.mjs +48 -0
  142. package/dist/messages-rRSHQDCX.mjs +48 -0
  143. package/dist/messages-srxrv8Yh.mjs +48 -0
  144. package/dist/messages-wdqp4610.mjs +48 -0
  145. package/dist/messages-zS1AXZ0y.mjs +48 -0
  146. package/dist/messages-zSzDzXej.mjs +48 -0
  147. package/dist/tools.mjs +3126 -0
  148. package/dist/vendor.LICENSE.txt +26 -225
  149. package/package.json +63 -24
  150. package/src/blok.ts +267 -0
  151. package/src/components/__module.ts +139 -0
  152. package/src/components/block/api.ts +155 -0
  153. package/src/components/block/index.ts +1428 -0
  154. package/src/components/block-tunes/block-tune-delete.ts +51 -0
  155. package/src/components/blocks.ts +352 -0
  156. package/src/components/constants/data-attributes.ts +344 -0
  157. package/src/components/constants.ts +76 -0
  158. package/src/components/core.ts +392 -0
  159. package/src/components/dom.ts +773 -0
  160. package/src/components/domIterator.ts +189 -0
  161. package/src/components/errors/critical.ts +5 -0
  162. package/src/components/events/BlockChanged.ts +16 -0
  163. package/src/components/events/BlockHovered.ts +21 -0
  164. package/src/components/events/BlockSettingsClosed.ts +12 -0
  165. package/src/components/events/BlockSettingsOpened.ts +12 -0
  166. package/src/components/events/BlokMobileLayoutToggled.ts +15 -0
  167. package/src/components/events/FakeCursorAboutToBeToggled.ts +17 -0
  168. package/src/components/events/FakeCursorHaveBeenSet.ts +17 -0
  169. package/src/components/events/HistoryStateChanged.ts +19 -0
  170. package/src/components/events/RedactorDomChanged.ts +14 -0
  171. package/src/components/events/index.ts +46 -0
  172. package/src/components/flipper.ts +497 -0
  173. package/src/components/i18n/i18next-loader.ts +84 -0
  174. package/src/components/i18n/lightweight-i18n.ts +86 -0
  175. package/src/components/i18n/locales/TRANSLATION_GUIDELINES.md +113 -0
  176. package/src/components/i18n/locales/am/messages.json +45 -0
  177. package/src/components/i18n/locales/ar/messages.json +45 -0
  178. package/src/components/i18n/locales/az/messages.json +45 -0
  179. package/src/components/i18n/locales/bg/messages.json +45 -0
  180. package/src/components/i18n/locales/bn/messages.json +45 -0
  181. package/src/components/i18n/locales/bs/messages.json +45 -0
  182. package/src/components/i18n/locales/cs/messages.json +45 -0
  183. package/src/components/i18n/locales/da/messages.json +45 -0
  184. package/src/components/i18n/locales/de/messages.json +45 -0
  185. package/src/components/i18n/locales/dv/messages.json +45 -0
  186. package/src/components/i18n/locales/el/messages.json +45 -0
  187. package/src/components/i18n/locales/en/messages.json +45 -0
  188. package/src/components/i18n/locales/es/messages.json +45 -0
  189. package/src/components/i18n/locales/et/messages.json +45 -0
  190. package/src/components/i18n/locales/fa/messages.json +45 -0
  191. package/src/components/i18n/locales/fi/messages.json +45 -0
  192. package/src/components/i18n/locales/fil/messages.json +45 -0
  193. package/src/components/i18n/locales/fr/messages.json +45 -0
  194. package/src/components/i18n/locales/gu/messages.json +45 -0
  195. package/src/components/i18n/locales/he/messages.json +45 -0
  196. package/src/components/i18n/locales/hi/messages.json +45 -0
  197. package/src/components/i18n/locales/hr/messages.json +45 -0
  198. package/src/components/i18n/locales/hu/messages.json +45 -0
  199. package/src/components/i18n/locales/hy/messages.json +45 -0
  200. package/src/components/i18n/locales/id/messages.json +45 -0
  201. package/src/components/i18n/locales/index.ts +231 -0
  202. package/src/components/i18n/locales/it/messages.json +45 -0
  203. package/src/components/i18n/locales/ja/messages.json +45 -0
  204. package/src/components/i18n/locales/ka/messages.json +45 -0
  205. package/src/components/i18n/locales/km/messages.json +45 -0
  206. package/src/components/i18n/locales/kn/messages.json +45 -0
  207. package/src/components/i18n/locales/ko/messages.json +45 -0
  208. package/src/components/i18n/locales/ku/messages.json +45 -0
  209. package/src/components/i18n/locales/lo/messages.json +45 -0
  210. package/src/components/i18n/locales/lt/messages.json +45 -0
  211. package/src/components/i18n/locales/lv/messages.json +45 -0
  212. package/src/components/i18n/locales/mk/messages.json +45 -0
  213. package/src/components/i18n/locales/ml/messages.json +45 -0
  214. package/src/components/i18n/locales/mn/messages.json +45 -0
  215. package/src/components/i18n/locales/mr/messages.json +45 -0
  216. package/src/components/i18n/locales/ms/messages.json +45 -0
  217. package/src/components/i18n/locales/my/messages.json +45 -0
  218. package/src/components/i18n/locales/ne/messages.json +45 -0
  219. package/src/components/i18n/locales/nl/messages.json +45 -0
  220. package/src/components/i18n/locales/no/messages.json +45 -0
  221. package/src/components/i18n/locales/pa/messages.json +45 -0
  222. package/src/components/i18n/locales/pl/messages.json +45 -0
  223. package/src/components/i18n/locales/ps/messages.json +45 -0
  224. package/src/components/i18n/locales/pt/messages.json +45 -0
  225. package/src/components/i18n/locales/ro/messages.json +45 -0
  226. package/src/components/i18n/locales/ru/messages.json +45 -0
  227. package/src/components/i18n/locales/sd/messages.json +45 -0
  228. package/src/components/i18n/locales/si/messages.json +45 -0
  229. package/src/components/i18n/locales/sk/messages.json +45 -0
  230. package/src/components/i18n/locales/sl/messages.json +45 -0
  231. package/src/components/i18n/locales/sq/messages.json +45 -0
  232. package/src/components/i18n/locales/sr/messages.json +45 -0
  233. package/src/components/i18n/locales/sv/messages.json +45 -0
  234. package/src/components/i18n/locales/sw/messages.json +45 -0
  235. package/src/components/i18n/locales/ta/messages.json +45 -0
  236. package/src/components/i18n/locales/te/messages.json +45 -0
  237. package/src/components/i18n/locales/th/messages.json +45 -0
  238. package/src/components/i18n/locales/tr/messages.json +45 -0
  239. package/src/components/i18n/locales/ug/messages.json +45 -0
  240. package/src/components/i18n/locales/uk/messages.json +45 -0
  241. package/src/components/i18n/locales/ur/messages.json +45 -0
  242. package/src/components/i18n/locales/vi/messages.json +45 -0
  243. package/src/components/i18n/locales/yi/messages.json +45 -0
  244. package/src/components/i18n/locales/zh/messages.json +45 -0
  245. package/src/components/icons/index.ts +242 -0
  246. package/src/components/inline-tools/inline-tool-bold.ts +2213 -0
  247. package/src/components/inline-tools/inline-tool-convert.ts +142 -0
  248. package/src/components/inline-tools/inline-tool-italic.ts +500 -0
  249. package/src/components/inline-tools/inline-tool-link.ts +540 -0
  250. package/src/components/modules/api/blocks.ts +377 -0
  251. package/src/components/modules/api/caret.ts +125 -0
  252. package/src/components/modules/api/events.ts +51 -0
  253. package/src/components/modules/api/history.ts +73 -0
  254. package/src/components/modules/api/i18n.ts +35 -0
  255. package/src/components/modules/api/index.ts +39 -0
  256. package/src/components/modules/api/inlineToolbar.ts +33 -0
  257. package/src/components/modules/api/listeners.ts +56 -0
  258. package/src/components/modules/api/notifier.ts +46 -0
  259. package/src/components/modules/api/readonly.ts +39 -0
  260. package/src/components/modules/api/sanitizer.ts +30 -0
  261. package/src/components/modules/api/saver.ts +52 -0
  262. package/src/components/modules/api/selection.ts +48 -0
  263. package/src/components/modules/api/styles.ts +72 -0
  264. package/src/components/modules/api/toolbar.ts +79 -0
  265. package/src/components/modules/api/tools.ts +16 -0
  266. package/src/components/modules/api/tooltip.ts +67 -0
  267. package/src/components/modules/api/ui.ts +36 -0
  268. package/src/components/modules/blockEvents.ts +1591 -0
  269. package/src/components/modules/blockManager.ts +1356 -0
  270. package/src/components/modules/blockSelection.ts +708 -0
  271. package/src/components/modules/caret.ts +853 -0
  272. package/src/components/modules/crossBlockSelection.ts +329 -0
  273. package/src/components/modules/dragManager.ts +1204 -0
  274. package/src/components/modules/history.ts +1098 -0
  275. package/src/components/modules/i18n.ts +332 -0
  276. package/src/components/modules/index.ts +139 -0
  277. package/src/components/modules/modificationsObserver.ts +147 -0
  278. package/src/components/modules/paste.ts +1092 -0
  279. package/src/components/modules/readonly.ts +136 -0
  280. package/src/components/modules/rectangleSelection.ts +711 -0
  281. package/src/components/modules/renderer.ts +155 -0
  282. package/src/components/modules/saver.ts +283 -0
  283. package/src/components/modules/toolbar/blockSettings.ts +782 -0
  284. package/src/components/modules/toolbar/index.ts +1296 -0
  285. package/src/components/modules/toolbar/inline.ts +956 -0
  286. package/src/components/modules/tools.ts +625 -0
  287. package/src/components/modules/ui.ts +1283 -0
  288. package/src/components/polyfills.ts +113 -0
  289. package/src/components/selection.ts +1179 -0
  290. package/src/components/tools/base.ts +301 -0
  291. package/src/components/tools/block.ts +339 -0
  292. package/src/components/tools/collection.ts +67 -0
  293. package/src/components/tools/factory.ts +138 -0
  294. package/src/components/tools/inline.ts +71 -0
  295. package/src/components/tools/tune.ts +33 -0
  296. package/src/components/ui/toolbox.ts +610 -0
  297. package/src/components/utils/announcer.ts +205 -0
  298. package/src/components/utils/api.ts +20 -0
  299. package/src/components/utils/bem.ts +26 -0
  300. package/src/components/utils/blocks.ts +284 -0
  301. package/src/components/utils/caret.ts +1067 -0
  302. package/src/components/utils/data-model-transform.ts +382 -0
  303. package/src/components/utils/events.ts +117 -0
  304. package/src/components/utils/keyboard.ts +60 -0
  305. package/src/components/utils/listeners.ts +296 -0
  306. package/src/components/utils/mutations.ts +39 -0
  307. package/src/components/utils/notifier/draw.ts +190 -0
  308. package/src/components/utils/notifier/index.ts +66 -0
  309. package/src/components/utils/notifier/types.ts +1 -0
  310. package/src/components/utils/notifier.ts +77 -0
  311. package/src/components/utils/placeholder.ts +140 -0
  312. package/src/components/utils/popover/components/hint/hint.const.ts +10 -0
  313. package/src/components/utils/popover/components/hint/hint.ts +46 -0
  314. package/src/components/utils/popover/components/hint/index.ts +6 -0
  315. package/src/components/utils/popover/components/popover-header/index.ts +2 -0
  316. package/src/components/utils/popover/components/popover-header/popover-header.const.ts +8 -0
  317. package/src/components/utils/popover/components/popover-header/popover-header.ts +80 -0
  318. package/src/components/utils/popover/components/popover-header/popover-header.types.ts +14 -0
  319. package/src/components/utils/popover/components/popover-item/index.ts +13 -0
  320. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +50 -0
  321. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +680 -0
  322. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.const.ts +14 -0
  323. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.ts +136 -0
  324. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +20 -0
  325. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.ts +117 -0
  326. package/src/components/utils/popover/components/popover-item/popover-item.ts +197 -0
  327. package/src/components/utils/popover/components/search-input/index.ts +2 -0
  328. package/src/components/utils/popover/components/search-input/search-input.const.ts +8 -0
  329. package/src/components/utils/popover/components/search-input/search-input.ts +178 -0
  330. package/src/components/utils/popover/components/search-input/search-input.types.ts +59 -0
  331. package/src/components/utils/popover/index.ts +13 -0
  332. package/src/components/utils/popover/popover-abstract.ts +457 -0
  333. package/src/components/utils/popover/popover-desktop.ts +682 -0
  334. package/src/components/utils/popover/popover-inline.ts +338 -0
  335. package/src/components/utils/popover/popover-mobile.ts +201 -0
  336. package/src/components/utils/popover/popover.const.ts +81 -0
  337. package/src/components/utils/popover/utils/popover-states-history.ts +72 -0
  338. package/src/components/utils/promise-queue.ts +43 -0
  339. package/src/components/utils/sanitizer.ts +537 -0
  340. package/src/components/utils/scroll-locker.ts +87 -0
  341. package/src/components/utils/shortcut.ts +231 -0
  342. package/src/components/utils/shortcuts.ts +113 -0
  343. package/src/components/utils/tools.ts +110 -0
  344. package/src/components/utils/tooltip.ts +591 -0
  345. package/src/components/utils/tw.ts +241 -0
  346. package/src/components/utils.ts +1081 -0
  347. package/src/env.d.ts +13 -0
  348. package/src/full.ts +69 -0
  349. package/src/locales.ts +51 -0
  350. package/src/stories/Block.stories.ts +498 -0
  351. package/src/stories/EditorModes.stories.ts +505 -0
  352. package/src/stories/Header.stories.ts +137 -0
  353. package/src/stories/InlineToolbar.stories.ts +498 -0
  354. package/src/stories/List.stories.ts +259 -0
  355. package/src/stories/Notifier.stories.ts +340 -0
  356. package/src/stories/Paragraph.stories.ts +112 -0
  357. package/src/stories/Placeholder.stories.ts +319 -0
  358. package/src/stories/Popover.stories.ts +759 -0
  359. package/src/stories/Selection.stories.ts +250 -0
  360. package/src/stories/StubBlock.stories.ts +156 -0
  361. package/src/stories/Toolbar.stories.ts +223 -0
  362. package/src/stories/Toolbox.stories.ts +166 -0
  363. package/src/stories/Tooltip.stories.ts +198 -0
  364. package/src/stories/helpers.ts +463 -0
  365. package/src/styles/main.css +126 -0
  366. package/src/tools/header/index.ts +647 -0
  367. package/src/tools/index.ts +45 -0
  368. package/src/tools/list/index.ts +1826 -0
  369. package/src/tools/paragraph/index.ts +412 -0
  370. package/src/tools/stub/index.ts +107 -0
  371. package/src/types-internal/blok-modules.d.ts +87 -0
  372. package/src/types-internal/html-janitor.d.ts +28 -0
  373. package/src/types-internal/module-config.d.ts +11 -0
  374. package/src/variants/all-locales.ts +155 -0
  375. package/src/variants/blok-maximum.ts +20 -0
  376. package/src/variants/blok-minimum.ts +243 -0
  377. package/types/api/blocks.d.ts +9 -1
  378. package/types/api/history.d.ts +7 -0
  379. package/types/api/i18n.d.ts +22 -3
  380. package/types/api/selection.d.ts +6 -0
  381. package/types/api/styles.d.ts +23 -10
  382. package/types/configs/blok-config.d.ts +29 -0
  383. package/types/configs/i18n-config.d.ts +52 -2
  384. package/types/configs/i18n-dictionary.d.ts +16 -90
  385. package/types/configs/sanitizer-config.d.ts +25 -1
  386. package/types/data-attributes.d.ts +170 -0
  387. package/types/data-formats/output-data.d.ts +15 -0
  388. package/types/full.d.ts +80 -0
  389. package/types/index.d.ts +30 -13
  390. package/types/locales.d.ts +59 -0
  391. package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
  392. package/types/tools/block-tool.d.ts +11 -2
  393. package/types/tools/header.d.ts +18 -0
  394. package/types/tools/index.d.ts +1 -0
  395. package/types/tools/list.d.ts +91 -0
  396. package/types/tools/paragraph.d.ts +71 -0
  397. package/types/tools/tool-settings.d.ts +99 -6
  398. package/types/tools/tool.d.ts +6 -0
  399. package/types/tools-entry.d.ts +49 -0
  400. package/types/utils/popover/popover-item.d.ts +24 -5
  401. package/types/utils/popover/popover.d.ts +13 -0
  402. package/dist/blok-C8XbyLHh.mjs +0 -25795
  403. package/dist/blok.umd.js +0 -181
@@ -0,0 +1,782 @@
1
+ import { Module } from '../../__module';
2
+ import { Dom as $ } from '../../dom';
3
+ import { SelectionUtils } from '../../selection';
4
+ import type { Block } from '../../block';
5
+ import { Flipper } from '../../flipper';
6
+ import type { MenuConfigItem } from '../../../../types/tools';
7
+ import type { PopoverItemParams } from '../../utils/popover';
8
+ import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemType } from '../../utils/popover';
9
+ import type { PopoverParams } from '@/types/utils/popover/popover';
10
+ import { PopoverEvent } from '@/types/utils/popover/popover-event';
11
+ import { isMobileScreen, keyCodes } from '../../utils';
12
+ import { css as popoverItemCls } from '../../utils/popover/components/popover-item';
13
+ import { BlockSettingsClosed, BlockSettingsOpened, BlokMobileLayoutToggled } from '../../events';
14
+ import { IconReplace, IconCross } from '../../icons';
15
+ import { getConvertibleToolsForBlock, getConvertibleToolsForBlocks } from '../../utils/blocks';
16
+ import { translateToolTitle } from '../../utils/tools';
17
+ import { BlockAPI } from '../../block/api';
18
+ import type { BlockToolAdapter } from '../../tools/block';
19
+
20
+ /**
21
+ * HTML Elements that used for BlockSettings
22
+ */
23
+ interface BlockSettingsNodes {
24
+ /**
25
+ * Block Settings wrapper. Undefined when before "make" method called
26
+ */
27
+ wrapper: HTMLElement | undefined;
28
+ }
29
+
30
+ /**
31
+ * Block Settings
32
+ * @todo Make Block Settings no-module but a standalone class, like Toolbox
33
+ */
34
+ export class BlockSettings extends Module<BlockSettingsNodes> {
35
+ /**
36
+ * Module Events
37
+ */
38
+ public get events(): { opened: typeof BlockSettingsOpened; closed: typeof BlockSettingsClosed } {
39
+ return {
40
+ opened: BlockSettingsOpened,
41
+ closed: BlockSettingsClosed,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Block Settings CSS
47
+ * @deprecated Use data attributes for identification instead
48
+ */
49
+ public get CSS(): { [name: string]: string } {
50
+ return {
51
+ settings: '',
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Opened state
57
+ */
58
+ public opened = false;
59
+
60
+ /**
61
+ * Getter for inner popover's flipper instance
62
+ * @todo remove once BlockSettings becomes standalone non-module class
63
+ */
64
+ public get flipper(): Flipper {
65
+ return this.flipperInstance;
66
+ }
67
+
68
+ /**
69
+ * Page selection utils
70
+ */
71
+ private selection: SelectionUtils = new SelectionUtils();
72
+
73
+ /**
74
+ * Popover instance. There is a util for vertical lists.
75
+ * Null until popover is not initialized
76
+ */
77
+ private popover: Popover | null = null;
78
+
79
+ /**
80
+ * Shared flipper instance used for keyboard navigation in block settings popover
81
+ */
82
+ private readonly flipperInstance: Flipper = new Flipper({
83
+ focusedItemClass: popoverItemCls.focused,
84
+ allowedKeys: [
85
+ keyCodes.TAB,
86
+ keyCodes.UP,
87
+ keyCodes.DOWN,
88
+ keyCodes.ENTER,
89
+ keyCodes.RIGHT,
90
+ keyCodes.LEFT,
91
+ ],
92
+ });
93
+
94
+ /**
95
+ * Stored keydown handler reference to detach when block tunes are closed
96
+ */
97
+ private flipperKeydownHandler: ((event: KeyboardEvent) => void) | null = null;
98
+
99
+ /**
100
+ * Element that listens for keydown events while block tunes are opened
101
+ */
102
+ private flipperKeydownSource: HTMLElement | null = null;
103
+
104
+ /**
105
+ * Panel with block settings with 2 sections:
106
+ * - Tool's Settings
107
+ * - Default Settings [Move, Remove, etc]
108
+ */
109
+ public make(): void {
110
+ this.nodes.wrapper = $.make('div');
111
+ this.nodes.wrapper.setAttribute('data-blok-testid', 'block-tunes-wrapper');
112
+
113
+ this.eventsDispatcher.on(BlokMobileLayoutToggled, this.close);
114
+ }
115
+
116
+ /**
117
+ * Destroys module
118
+ */
119
+ public destroy(): void {
120
+ this.detachFlipperKeydownListener();
121
+ this.removeAllNodes();
122
+ this.listeners.destroy();
123
+ this.eventsDispatcher.off(BlokMobileLayoutToggled, this.close);
124
+ }
125
+
126
+ /**
127
+ * Open Block Settings pane
128
+ * @param targetBlock - near which Block we should open BlockSettings
129
+ * @param trigger - element to position the popover relative to
130
+ */
131
+ public async open(targetBlock?: Block, trigger?: HTMLElement): Promise<void> {
132
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
133
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
134
+
135
+ /**
136
+ * When multiple blocks are selected, use the first selected block as the anchor
137
+ * Otherwise, use the target block or current block
138
+ */
139
+ const block = hasMultipleBlocksSelected
140
+ ? selectedBlocks[0]
141
+ : (targetBlock ?? this.Blok.BlockManager.currentBlock);
142
+
143
+ if (block === undefined) {
144
+ return;
145
+ }
146
+
147
+ /**
148
+ * If block settings contains any inputs, focus will be set there,
149
+ * so we need to save current selection to restore it after block settings is closed
150
+ */
151
+ this.selection.save();
152
+
153
+ /**
154
+ * Highlight content of a Block we are working with
155
+ * For multiple blocks, they should already be selected
156
+ */
157
+ if (!hasMultipleBlocksSelected) {
158
+ this.Blok.BlockSelection.selectBlock(block);
159
+ this.Blok.BlockSelection.clearCache();
160
+ }
161
+
162
+ /** Get tool's settings data - only relevant for single block selection */
163
+ const { toolTunes, commonTunes } = block.getTunes();
164
+
165
+ const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
166
+ const popoverParams: PopoverParams & { flipper?: Flipper } = {
167
+ searchable: false,
168
+ trigger: trigger || this.nodes.wrapper,
169
+ items: await this.getTunesItems(block, commonTunes, toolTunes),
170
+ scopeElement: this.Blok.API.methods.ui.nodes.redactor,
171
+ width: 'auto',
172
+ messages: {
173
+ nothingFound: this.Blok.I18n.t('popover.nothingFound'),
174
+ search: this.Blok.I18n.t('popover.search'),
175
+ },
176
+ };
177
+
178
+ if (PopoverClass === PopoverDesktop) {
179
+ popoverParams.flipper = this.flipperInstance;
180
+ }
181
+
182
+ this.popover = new PopoverClass(popoverParams);
183
+ this.popover.getElement().setAttribute('data-blok-testid', 'block-tunes-popover');
184
+
185
+ this.popover.on(PopoverEvent.Closed, this.onPopoverClose);
186
+
187
+ /**
188
+ * Set opened flag AFTER popover is created to prevent race conditions
189
+ * where close() is called during the async getTunesItems() call
190
+ * when opened=true but popover is still null
191
+ */
192
+ this.opened = true;
193
+
194
+ /** Tell to subscribers that block settings is opened */
195
+ this.eventsDispatcher.emit(this.events.opened);
196
+
197
+ this.popover.show();
198
+ this.attachFlipperKeydownListener(block);
199
+ }
200
+
201
+ /**
202
+ * Returns root block settings element
203
+ */
204
+ public getElement(): HTMLElement | undefined {
205
+ return this.nodes.wrapper;
206
+ }
207
+
208
+ /**
209
+ * Checks if the element is contained in the BlockSettings or its Popover
210
+ * @param element - element to check
211
+ */
212
+ public contains(element: HTMLElement): boolean {
213
+ if (this.nodes.wrapper?.contains(element)) {
214
+ return true;
215
+ }
216
+
217
+ if (this.popover?.hasNode(element)) {
218
+ return true;
219
+ }
220
+
221
+ return false;
222
+ }
223
+
224
+ /**
225
+ * Close Block Settings pane
226
+ */
227
+ public close = (): void => {
228
+ if (!this.opened) {
229
+ return;
230
+ }
231
+
232
+ this.opened = false;
233
+
234
+ /**
235
+ * If selection is at blok on Block Settings closing,
236
+ * it means that caret placed at some editable element inside the Block Settings.
237
+ * Previously we have saved the selection, then open the Block Settings and set caret to the input
238
+ *
239
+ * So, we need to restore selection back to Block after closing the Block Settings
240
+ */
241
+ if (!SelectionUtils.isAtBlok) {
242
+ this.selection.restore();
243
+ }
244
+
245
+ this.selection.clearSaved();
246
+ this.detachFlipperKeydownListener();
247
+
248
+ /**
249
+ * Remove highlighted content of Blocks we are working with
250
+ * Handle both single and multiple block selection
251
+ */
252
+ this.clearBlockSelectionOnClose();
253
+
254
+ /** Tell to subscribers that block settings is closed */
255
+ this.eventsDispatcher.emit(this.events.closed);
256
+
257
+ if (this.popover) {
258
+ this.popover.off(PopoverEvent.Closed, this.onPopoverClose);
259
+ this.popover.destroy();
260
+ this.popover.getElement().remove();
261
+ this.popover = null;
262
+ }
263
+ };
264
+
265
+ /**
266
+ * Returns list of items to be displayed in block tunes menu.
267
+ * Merges tool specific tunes, conversion menu and common tunes in one list in predefined order
268
+ * @param currentBlock – block we are about to open block tunes for
269
+ * @param commonTunes – common tunes
270
+ * @param toolTunes - tool specific tunes
271
+ */
272
+ private async getTunesItems(currentBlock: Block, commonTunes: MenuConfigItem[], toolTunes?: MenuConfigItem[]): Promise<PopoverItemParams[]> {
273
+ const items = [] as MenuConfigItem[];
274
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
275
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
276
+
277
+ /**
278
+ * Only show tool-specific tunes when a single block is selected
279
+ */
280
+ if (!hasMultipleBlocksSelected && toolTunes !== undefined && toolTunes.length > 0) {
281
+ items.push(...toolTunes);
282
+ items.push({
283
+ type: PopoverItemType.Separator,
284
+ });
285
+ }
286
+
287
+ const allBlockTools = Array.from(this.Blok.Tools.blockTools.values());
288
+
289
+ /**
290
+ * Get convertible tools based on selection:
291
+ * - For single block: use existing single-block conversion logic
292
+ * - For multiple blocks: find tools that ALL selected blocks can convert to
293
+ */
294
+ const convertibleTools = hasMultipleBlocksSelected
295
+ ? await getConvertibleToolsForBlocks(
296
+ selectedBlocks.map((block) => new BlockAPI(block)),
297
+ allBlockTools
298
+ )
299
+ : await getConvertibleToolsForBlock(currentBlock, allBlockTools);
300
+
301
+ const convertToItems = convertibleTools.reduce<PopoverItemParams[]>((result, tool) => {
302
+ if (tool.toolbox === undefined) {
303
+ return result;
304
+ }
305
+
306
+ tool.toolbox.forEach((toolboxItem) => {
307
+ result.push({
308
+ icon: toolboxItem.icon,
309
+ title: translateToolTitle(this.Blok.I18n, toolboxItem, tool.name),
310
+ name: toolboxItem.name ?? tool.name,
311
+ closeOnActivate: true,
312
+ onActivate: async () => {
313
+ const { Caret, Toolbar } = this.Blok;
314
+
315
+ const newBlock = await this.convertBlock(
316
+ currentBlock,
317
+ selectedBlocks,
318
+ hasMultipleBlocksSelected,
319
+ tool,
320
+ toolboxItem.data
321
+ );
322
+
323
+ Toolbar.close();
324
+
325
+ if (newBlock) {
326
+ Caret.setToBlock(newBlock, Caret.positions.END);
327
+ }
328
+ },
329
+ });
330
+ });
331
+
332
+ return result;
333
+ }, []);
334
+
335
+ if (convertToItems.length > 0) {
336
+ items.push({
337
+ icon: IconReplace,
338
+ name: 'convert-to',
339
+ title: this.Blok.I18n.t('popover.convertTo'),
340
+ children: {
341
+ items: convertToItems,
342
+ },
343
+ });
344
+ items.push({
345
+ type: PopoverItemType.Separator,
346
+ });
347
+ }
348
+
349
+ /**
350
+ * For single block selection, show all common tunes (delete, move, etc.)
351
+ * For multiple blocks, only show delete option with multi-block delete behavior
352
+ */
353
+ if (!hasMultipleBlocksSelected) {
354
+ items.push(...commonTunes);
355
+ } else {
356
+ items.push({
357
+ icon: IconCross,
358
+ title: this.Blok.I18n.t('blockSettings.delete'),
359
+ name: 'delete',
360
+ closeOnActivate: true,
361
+ onActivate: () => {
362
+ const { BlockManager, Caret, Toolbar } = this.Blok;
363
+ const indexToInsert = BlockManager.removeSelectedBlocks();
364
+
365
+ if (indexToInsert !== undefined && BlockManager.blocks.length === 0) {
366
+ BlockManager.insert();
367
+ }
368
+
369
+ const currentBlock = BlockManager.currentBlock;
370
+
371
+ if (currentBlock) {
372
+ Caret.setToBlock(currentBlock, Caret.positions.END);
373
+ }
374
+
375
+ Toolbar.close();
376
+ },
377
+ });
378
+ }
379
+
380
+ return items;
381
+ }
382
+
383
+ /**
384
+ * Handles popover close event
385
+ */
386
+ private onPopoverClose = (): void => {
387
+ this.close();
388
+ };
389
+
390
+ /**
391
+ * Clears block selection when block settings is closed
392
+ * Handles both single and multiple block selection scenarios
393
+ */
394
+ private clearBlockSelectionOnClose(): void {
395
+ if (this.Blok.CrossBlockSelection.isCrossBlockSelectionStarted) {
396
+ return;
397
+ }
398
+
399
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
400
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
401
+
402
+ if (hasMultipleBlocksSelected) {
403
+ this.Blok.BlockSelection.allBlocksSelected = false;
404
+
405
+ return;
406
+ }
407
+
408
+ const currentBlock = this.Blok.BlockManager.currentBlock;
409
+
410
+ if (currentBlock) {
411
+ this.Blok.BlockSelection.unselectBlock(currentBlock);
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Converts multiple selected blocks to a target tool type.
417
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
418
+ * Otherwise, each block is converted individually and remains as a separate block.
419
+ * @param blocks - array of blocks to convert
420
+ * @param targetToolName - name of the tool to convert to
421
+ * @param toolboxData - optional data overrides for the new blocks
422
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
423
+ */
424
+ private async convertBlock(
425
+ currentBlock: Block,
426
+ selectedBlocks: Block[],
427
+ hasMultipleBlocksSelected: boolean,
428
+ tool: BlockToolAdapter,
429
+ toolboxData?: Record<string, unknown>
430
+ ): Promise<Block | null> {
431
+ const { BlockManager } = this.Blok;
432
+
433
+ if (hasMultipleBlocksSelected) {
434
+ return this.convertMultipleBlocks(selectedBlocks, tool.name, toolboxData);
435
+ }
436
+
437
+ /**
438
+ * Check if we should explode a multi-item block (like List) into separate blocks
439
+ * This happens when converting to a tool that doesn't support multiple items
440
+ */
441
+ const explodableItems = await this.getExplodableItems(currentBlock);
442
+ const shouldExplode = !this.canToolMergeMultipleItems(tool) && explodableItems !== null;
443
+
444
+ if (shouldExplode) {
445
+ return this.convertMultiItemBlockToSeparateBlocks(currentBlock, tool.name, toolboxData);
446
+ }
447
+
448
+ return BlockManager.convert(currentBlock, tool.name, toolboxData);
449
+ }
450
+
451
+ /**
452
+ * Converts multiple selected blocks to a target tool type.
453
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
454
+ * Otherwise, each block is converted individually and remains as a separate block.
455
+ * @param blocks - array of blocks to convert
456
+ * @param targetToolName - name of the tool to convert to
457
+ * @param toolboxData - optional data overrides for the new blocks
458
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
459
+ */
460
+ private async convertMultipleBlocks(
461
+ blocks: Block[],
462
+ targetToolName: string,
463
+ toolboxData?: Record<string, unknown>
464
+ ): Promise<Block | null> {
465
+ const { Tools } = this.Blok;
466
+
467
+ if (blocks.length === 0) {
468
+ return null;
469
+ }
470
+
471
+ /**
472
+ * Check if the target tool's conversion config import function can handle
473
+ * newline-separated content to create multiple items (like lists do).
474
+ * We detect this by checking if the import function returns data with an 'items' array.
475
+ */
476
+ const targetTool = Tools.blockTools.get(targetToolName);
477
+ const shouldMergeIntoSingleBlock = targetTool && this.canToolMergeMultipleItems(targetTool);
478
+
479
+ if (shouldMergeIntoSingleBlock) {
480
+ return this.convertBlocksToSingleMergedBlock(blocks, targetToolName, toolboxData);
481
+ }
482
+
483
+ /**
484
+ * Convert each block individually, maintaining them as separate blocks
485
+ */
486
+ return this.convertBlocksIndividually(blocks, targetToolName, toolboxData);
487
+ }
488
+
489
+ /**
490
+ * Checks if a tool can merge multiple items into a single block.
491
+ * This is determined by testing if the tool's import function creates an 'items' array.
492
+ * @param tool - the target tool adapter
493
+ * @returns true if the tool supports merging multiple items
494
+ */
495
+ private canToolMergeMultipleItems(tool: BlockToolAdapter): boolean {
496
+ const conversionConfig = tool.conversionConfig;
497
+
498
+ if (!conversionConfig?.import) {
499
+ return false;
500
+ }
501
+
502
+ /**
503
+ * Test the import function with a sample multi-line string
504
+ * to see if it creates multiple items
505
+ */
506
+ try {
507
+ const testResult = typeof conversionConfig.import === 'function'
508
+ ? conversionConfig.import('line1\nline2', tool.settings)
509
+ : { [conversionConfig.import]: 'line1\nline2' };
510
+
511
+ return Array.isArray(testResult?.items) && testResult.items.length > 1;
512
+ } catch {
513
+ return false;
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Converts multiple blocks into a single merged block by combining their exported content.
519
+ * Used for tools like lists that can hold multiple items.
520
+ * @param blocks - blocks to convert and merge
521
+ * @param targetToolName - name of the tool to convert to
522
+ * @param toolboxData - optional data overrides
523
+ * @returns the merged block or null if conversion failed
524
+ */
525
+ private async convertBlocksToSingleMergedBlock(
526
+ blocks: Block[],
527
+ targetToolName: string,
528
+ toolboxData?: Record<string, unknown>
529
+ ): Promise<Block | null> {
530
+ const { BlockManager } = this.Blok;
531
+
532
+ /**
533
+ * Export all blocks' content and combine with newlines
534
+ */
535
+ const exportedContents: string[] = [];
536
+
537
+ for (const block of blocks) {
538
+ try {
539
+ const content = await block.exportDataAsString();
540
+
541
+ exportedContents.push(content);
542
+ } catch {
543
+ // Skip blocks that fail to export
544
+ }
545
+ }
546
+
547
+ if (exportedContents.length === 0) {
548
+ return null;
549
+ }
550
+
551
+ /**
552
+ * Convert the first block with combined content
553
+ */
554
+ const firstBlock = blocks[0];
555
+ const combinedContent = exportedContents.join('\n');
556
+
557
+ /**
558
+ * Get the target tool to use its conversion config
559
+ */
560
+ const targetTool = this.Blok.Tools.blockTools.get(targetToolName);
561
+
562
+ if (!targetTool) {
563
+ return null;
564
+ }
565
+
566
+ /**
567
+ * Import the combined content using the target tool's conversion config
568
+ */
569
+ const importedData = typeof targetTool.conversionConfig?.import === 'function'
570
+ ? targetTool.conversionConfig.import(combinedContent, targetTool.settings)
571
+ : { [targetTool.conversionConfig?.import as string]: combinedContent };
572
+
573
+ const newBlockData = toolboxData
574
+ ? Object.assign(importedData, toolboxData)
575
+ : importedData;
576
+
577
+ /**
578
+ * Replace the first block with the new merged block
579
+ */
580
+ const newBlock = BlockManager.replace(firstBlock, targetToolName, newBlockData);
581
+
582
+ /**
583
+ * Remove the remaining blocks (they've been merged into the first one)
584
+ */
585
+ const remainingBlocks = blocks.slice(1);
586
+
587
+ for (const block of remainingBlocks) {
588
+ await BlockManager.removeBlock(block, false);
589
+ }
590
+
591
+ return newBlock;
592
+ }
593
+
594
+ /**
595
+ * Converts blocks individually, keeping them as separate blocks.
596
+ * @param blocks - blocks to convert
597
+ * @param targetToolName - name of the tool to convert to
598
+ * @param toolboxData - optional data overrides
599
+ * @returns the last converted block or null if all conversions failed
600
+ */
601
+ private async convertBlocksIndividually(
602
+ blocks: Block[],
603
+ targetToolName: string,
604
+ toolboxData?: Record<string, unknown>
605
+ ): Promise<Block | null> {
606
+ const { BlockManager } = this.Blok;
607
+ const convertedBlocks: Block[] = [];
608
+
609
+ for (const block of blocks) {
610
+ const convertedBlock = await this.convertBlockSafely(BlockManager, block, targetToolName, toolboxData);
611
+
612
+ if (convertedBlock) {
613
+ convertedBlocks.push(convertedBlock);
614
+ }
615
+ }
616
+
617
+ return convertedBlocks.length > 0
618
+ ? convertedBlocks[convertedBlocks.length - 1]
619
+ : null;
620
+ }
621
+
622
+ /**
623
+ * Safely converts a single block, catching any errors
624
+ * @param blockManager - the block manager instance
625
+ * @param block - block to convert
626
+ * @param targetToolName - name of the tool to convert to
627
+ * @param toolboxData - optional data overrides
628
+ * @returns the converted block or null if conversion failed
629
+ */
630
+ private async convertBlockSafely(
631
+ blockManager: typeof this.Blok.BlockManager,
632
+ block: Block,
633
+ targetToolName: string,
634
+ toolboxData?: Record<string, unknown>
635
+ ): Promise<Block | null> {
636
+ try {
637
+ return await blockManager.convert(block, targetToolName, toolboxData);
638
+ } catch (e) {
639
+ console.warn(`Failed to convert block ${block.id}:`, e);
640
+
641
+ return null;
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Checks if a block contains multiple items that should be exploded into separate blocks
647
+ * when converting to a single-item tool.
648
+ * @param block - block to check
649
+ * @returns array of content strings if block should be exploded, null otherwise
650
+ */
651
+ private async getExplodableItems(block: Block): Promise<string[] | null> {
652
+ try {
653
+ const blockData = await block.data;
654
+
655
+ /**
656
+ * Check if block has an 'items' array with multiple items (like List tool)
657
+ */
658
+ if (!Array.isArray(blockData?.items) || blockData.items.length <= 1) {
659
+ return null;
660
+ }
661
+
662
+ /**
663
+ * Extract content from each item, handling nested items recursively
664
+ */
665
+ const extractContent = (items: Array<{ content?: string; items?: unknown[] }>): string[] => {
666
+ const contents: string[] = [];
667
+
668
+ for (const item of items) {
669
+ if (item.content !== undefined && item.content !== '') {
670
+ contents.push(item.content);
671
+ }
672
+ if (Array.isArray(item.items) && item.items.length > 0) {
673
+ contents.push(...extractContent(item.items as Array<{ content?: string; items?: unknown[] }>));
674
+ }
675
+ }
676
+
677
+ return contents;
678
+ };
679
+
680
+ return extractContent(blockData.items);
681
+ } catch {
682
+ return null;
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Converts a multi-item block (like List) into multiple single-item blocks.
688
+ * Each item becomes a separate block of the target type.
689
+ * @param block - block to convert
690
+ * @param targetToolName - name of the tool to convert to
691
+ * @param toolboxData - optional data overrides
692
+ * @returns the last created block or null if conversion failed
693
+ */
694
+ private async convertMultiItemBlockToSeparateBlocks(
695
+ block: Block,
696
+ targetToolName: string,
697
+ toolboxData?: Record<string, unknown>
698
+ ): Promise<Block | null> {
699
+ const { BlockManager, Tools } = this.Blok;
700
+ const items = await this.getExplodableItems(block);
701
+
702
+ if (!items || items.length === 0) {
703
+ return null;
704
+ }
705
+
706
+ const targetTool = Tools.blockTools.get(targetToolName);
707
+ const conversionImport = targetTool?.conversionConfig?.import;
708
+
709
+ if (!conversionImport) {
710
+ return null;
711
+ }
712
+
713
+ const blockIndex = BlockManager.getBlockIndex(block);
714
+
715
+ /**
716
+ * Remove the original block first
717
+ */
718
+ await BlockManager.removeBlock(block, false);
719
+
720
+ /**
721
+ * Create a new block for each item
722
+ */
723
+ const createdBlocks = items.map((content, index) => {
724
+ /**
725
+ * Import the content using the target tool's conversion config
726
+ */
727
+ const importedData = typeof conversionImport === 'function'
728
+ ? conversionImport(content, targetTool?.settings)
729
+ : { [conversionImport as string]: content };
730
+
731
+ const newBlockData = toolboxData
732
+ ? Object.assign(importedData, toolboxData)
733
+ : importedData;
734
+
735
+ return BlockManager.insert({
736
+ tool: targetToolName,
737
+ data: newBlockData,
738
+ index: blockIndex + index,
739
+ needToFocus: false,
740
+ });
741
+ });
742
+
743
+ return createdBlocks.length > 0 ? createdBlocks[createdBlocks.length - 1] : null;
744
+ }
745
+
746
+ /**
747
+ * Attaches keydown listener to delegate navigation events to the shared flipper
748
+ * @param block - block that owns the currently focused content
749
+ */
750
+ private attachFlipperKeydownListener(block: Block): void {
751
+ this.detachFlipperKeydownListener();
752
+
753
+ const pluginsContent = block?.pluginsContent;
754
+
755
+ if (!(pluginsContent instanceof HTMLElement)) {
756
+ return;
757
+ }
758
+
759
+ this.flipperInstance.setHandleContentEditableTargets(true);
760
+
761
+ this.flipperKeydownHandler = (event: KeyboardEvent) => {
762
+ this.flipperInstance.handleExternalKeydown(event);
763
+ };
764
+
765
+ pluginsContent.addEventListener('keydown', this.flipperKeydownHandler, true);
766
+ this.flipperKeydownSource = pluginsContent;
767
+ }
768
+
769
+ /**
770
+ * Removes keydown listener from the previously active block
771
+ */
772
+ private detachFlipperKeydownListener(): void {
773
+ if (this.flipperKeydownSource !== null && this.flipperKeydownHandler !== null) {
774
+ this.flipperKeydownSource.removeEventListener('keydown', this.flipperKeydownHandler, true);
775
+ }
776
+
777
+ this.flipperInstance.setHandleContentEditableTargets(false);
778
+
779
+ this.flipperKeydownSource = null;
780
+ this.flipperKeydownHandler = null;
781
+ }
782
+ }