@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,1283 @@
1
+
2
+ /**
3
+ * Module UI
4
+ * @type {UI}
5
+ */
6
+ import { Module } from '../__module';
7
+ import { Dom as $, toggleEmptyMark } from '../dom';
8
+ import { debounce, getValidUrl, isEmpty, openTab, throttle } from '../utils';
9
+
10
+ import { SelectionUtils as Selection } from '../selection';
11
+ import { Flipper } from '../flipper';
12
+ import type { Block } from '../block';
13
+ import { mobileScreenBreakpoint } from '../utils';
14
+
15
+ /**
16
+ * Horizontal distance from the content edge where block hover is still detected.
17
+ * Extends to the left for LTR layouts, to the right for RTL.
18
+ */
19
+ const HOVER_ZONE_SIZE = 100;
20
+
21
+ import styles from '../../styles/main.css?inline';
22
+ import { BlockHovered } from '../events/BlockHovered';
23
+ import {
24
+ DATA_ATTR,
25
+ BLOK_INTERFACE_VALUE,
26
+ selectionChangeDebounceTimeout,
27
+ } from '../constants';
28
+ import { BlokMobileLayoutToggled } from '../events';
29
+ import { destroyAnnouncer, registerAnnouncer } from '../utils/announcer';
30
+ /**
31
+ * HTML Elements used for UI
32
+ */
33
+ interface UINodes {
34
+ holder: HTMLElement;
35
+ wrapper: HTMLElement;
36
+ redactor: HTMLElement;
37
+ }
38
+
39
+ /**
40
+ * @class
41
+ * @classdesc Makes Blok UI:
42
+ * <blok-editor>
43
+ * <blok-redactor />
44
+ * <blok-toolbar />
45
+ * <blok-inline-toolbar />
46
+ * </blok-editor>
47
+ * @typedef {UI} UI
48
+ * @property {BlokConfig} config - blok configuration {@link Blok#configuration}
49
+ * @property {object} Blok - available blok modules {@link Blok#moduleInstances}
50
+ * @property {object} nodes -
51
+ * @property {Element} nodes.holder - element where we need to append redactor
52
+ * @property {Element} nodes.wrapper - <blok-editor>
53
+ * @property {Element} nodes.redactor - <blok-redactor>
54
+ */
55
+ export class UI extends Module<UINodes> {
56
+ /**
57
+ * Return Width of center column of Blok
58
+ * @returns {DOMRect}
59
+ */
60
+ public get contentRect(): DOMRect {
61
+ if (this.contentRectCache !== null) {
62
+ return this.contentRectCache;
63
+ }
64
+
65
+ const someBlock = this.nodes.wrapper.querySelector('[data-blok-testid="block-content"]');
66
+
67
+ /**
68
+ * When Blok is not ready, there is no Blocks, so return the default value
69
+ */
70
+ if (!someBlock) {
71
+ return {
72
+ width: 650,
73
+ left: 0,
74
+ right: 0,
75
+ } as DOMRect;
76
+ }
77
+
78
+ this.contentRectCache = someBlock.getBoundingClientRect();
79
+
80
+ return this.contentRectCache;
81
+ }
82
+
83
+ /**
84
+ * Flag that became true on mobile viewport
85
+ * @type {boolean}
86
+ */
87
+ public isMobile = false;
88
+
89
+
90
+ /**
91
+ * Cache for center column rectangle info
92
+ * Invalidates on window resize
93
+ * @type {DOMRect}
94
+ */
95
+ private contentRectCache: DOMRect | null = null;
96
+
97
+ /**
98
+ * Handle window resize only when it finished
99
+ * @type {() => void}
100
+ */
101
+ private resizeDebouncer: () => void = debounce(() => {
102
+ this.windowResize();
103
+
104
+ }, 200);
105
+
106
+ /**
107
+ * Handle selection change to manipulate Inline Toolbar appearance
108
+ */
109
+ private selectionChangeDebounced = debounce(() => {
110
+ this.selectionChanged();
111
+ }, selectionChangeDebounceTimeout);
112
+
113
+ /**
114
+ * Making main interface
115
+ */
116
+ public async prepare(): Promise<void> {
117
+ /**
118
+ * Detect mobile version
119
+ */
120
+ this.setIsMobile();
121
+
122
+ /**
123
+ * Make main UI elements
124
+ */
125
+ this.make();
126
+
127
+ /**
128
+ * Load and append CSS
129
+ */
130
+ this.loadStyles();
131
+
132
+ /**
133
+ * Register this Blok instance with the accessibility announcer
134
+ * for proper multi-instance cleanup
135
+ */
136
+ registerAnnouncer();
137
+ }
138
+
139
+ /**
140
+ * Toggle read-only state
141
+ *
142
+ * If readOnly is true:
143
+ * - removes all listeners from main UI module elements
144
+ *
145
+ * if readOnly is false:
146
+ * - enables all listeners to UI module elements
147
+ * @param {boolean} readOnlyEnabled - "read only" state
148
+ */
149
+ public toggleReadOnly(readOnlyEnabled: boolean): void {
150
+ /**
151
+ * Prepare components based on read-only state
152
+ */
153
+ if (readOnlyEnabled) {
154
+ /**
155
+ * Unbind all events
156
+ *
157
+ */
158
+ this.unbindReadOnlySensitiveListeners();
159
+
160
+ return;
161
+ }
162
+
163
+ const bindListeners = (): void => {
164
+ /**
165
+ * Bind events for the UI elements
166
+ */
167
+ this.bindReadOnlySensitiveListeners();
168
+ };
169
+
170
+ /**
171
+ * Ensure listeners are attached immediately for interactive use.
172
+ */
173
+ bindListeners();
174
+
175
+ const idleCallback = window.requestIdleCallback;
176
+
177
+ if (typeof idleCallback !== 'function') {
178
+ return;
179
+ }
180
+
181
+ /**
182
+ * Re-bind on idle to preserve historical behavior when additional nodes appear later.
183
+ */
184
+ idleCallback(bindListeners, {
185
+ timeout: 2000,
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Check if Blok is empty and set data attribute on wrapper
191
+ */
192
+ public checkEmptiness(): void {
193
+ const { BlockManager } = this.Blok;
194
+
195
+ this.nodes.wrapper.setAttribute(DATA_ATTR.empty, BlockManager.isBlokEmpty ? 'true' : 'false');
196
+ }
197
+
198
+ /**
199
+ * Check if one of Toolbar is opened
200
+ * Used to prevent global keydowns (for example, Enter) conflicts with Enter-on-toolbar
201
+ * @returns {boolean}
202
+ */
203
+ public get someToolbarOpened(): boolean {
204
+ const { Toolbar, BlockSettings, InlineToolbar } = this.Blok;
205
+
206
+ return Boolean(BlockSettings.opened || InlineToolbar.opened || Toolbar.toolbox.opened);
207
+ }
208
+
209
+ /**
210
+ * Check for some Flipper-buttons is under focus
211
+ */
212
+ public get someFlipperButtonFocused(): boolean {
213
+ /**
214
+ * Toolbar has internal module (Toolbox) that has own Flipper,
215
+ * so we check it manually
216
+ */
217
+ if (this.Blok.Toolbar.toolbox.hasFocus()) {
218
+ return true;
219
+ }
220
+
221
+
222
+ return Object.entries(this.Blok).filter(([_moduleName, moduleClass]) => {
223
+ return moduleClass.flipper instanceof Flipper;
224
+ })
225
+ .some(([_moduleName, moduleClass]) => {
226
+ return moduleClass.flipper.hasFocus();
227
+ });
228
+
229
+
230
+ }
231
+
232
+ /**
233
+ * Clean blok`s UI
234
+ */
235
+ public destroy(): void {
236
+ this.nodes.holder.innerHTML = '';
237
+
238
+ this.unbindReadOnlyInsensitiveListeners();
239
+
240
+ // Clean up accessibility announcer
241
+ destroyAnnouncer();
242
+ }
243
+
244
+ /**
245
+ * Close all Blok's toolbars
246
+ */
247
+ public closeAllToolbars(): void {
248
+ const { Toolbar, BlockSettings, InlineToolbar } = this.Blok;
249
+
250
+ BlockSettings.close();
251
+ InlineToolbar.close();
252
+ Toolbar.toolbox.close();
253
+ }
254
+
255
+ /**
256
+ * Event listener for 'mousedown' and 'touchstart' events
257
+ * @param event - TouchEvent or MouseEvent
258
+ */
259
+ private documentTouchedListener = (event: Event): void => {
260
+ this.documentTouched(event);
261
+ };
262
+
263
+ /**
264
+ * Check for mobile mode and save the result
265
+ */
266
+ private setIsMobile(): void {
267
+ const isMobile = window.innerWidth < mobileScreenBreakpoint;
268
+
269
+ if (isMobile !== this.isMobile) {
270
+ /**
271
+ * Dispatch global event
272
+ */
273
+ this.eventsDispatcher.emit(BlokMobileLayoutToggled, {
274
+ isEnabled: this.isMobile,
275
+ });
276
+ }
277
+
278
+ this.isMobile = isMobile;
279
+ }
280
+
281
+ /**
282
+ * Makes Blok interface
283
+ */
284
+ private make(): void {
285
+ /**
286
+ * Element where we need to append Blok
287
+ * @type {Element}
288
+ */
289
+ const holder = this.config.holder;
290
+
291
+ if (!holder) {
292
+ throw new Error('Blok holder is not specified in the configuration.');
293
+ }
294
+
295
+ this.nodes.holder = $.getHolder(holder);
296
+
297
+ /**
298
+ * Create and save main UI elements
299
+ */
300
+ this.nodes.wrapper = $.make('div', [
301
+ 'group',
302
+ 'relative',
303
+ 'box-border',
304
+ 'z-[1]',
305
+ '[&[data-blok-dragging=true]]:cursor-grabbing',
306
+ // SVG defaults
307
+ '[&_svg]:max-h-full',
308
+ '[&_path]:stroke-current',
309
+ // Native selection color
310
+ '[&_::selection]:bg-selection-inline',
311
+ // Hide placeholder when toolbox is opened
312
+ '[&[data-blok-toolbox-opened=true]_[contentEditable=true][data-blok-placeholder]:focus]:before:!opacity-0',
313
+ ...(this.isRtl ? [ '[direction:rtl]' ] : []),
314
+ ]);
315
+ this.nodes.wrapper.setAttribute(DATA_ATTR.interface, BLOK_INTERFACE_VALUE);
316
+ this.nodes.wrapper.setAttribute(DATA_ATTR.editor, '');
317
+ this.nodes.wrapper.setAttribute('data-blok-testid', 'blok-editor');
318
+ if (this.isRtl) {
319
+ this.nodes.wrapper.setAttribute(DATA_ATTR.rtl, 'true');
320
+ }
321
+ this.nodes.redactor = $.make('div', [
322
+ // Narrow mode: add right margin on non-mobile screens
323
+ 'not-mobile:group-data-[blok-narrow=true]:mr-[theme(spacing.narrow-mode-right-padding)]',
324
+ // RTL narrow mode: add left margin instead
325
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:ml-[theme(spacing.narrow-mode-right-padding)]',
326
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:mr-0',
327
+ // Firefox empty contenteditable fix
328
+ '[&_[contenteditable]:empty]:after:content-["\\feff_"]',
329
+ ]);
330
+ this.nodes.redactor.setAttribute(DATA_ATTR.redactor, '');
331
+ this.nodes.redactor.setAttribute('data-blok-testid', 'redactor');
332
+
333
+ /**
334
+ * If Blok has injected into the narrow container, enable Narrow Mode
335
+ * @todo Forced layout. Get rid of this feature
336
+ */
337
+ if (this.nodes.holder.offsetWidth < this.contentRect.width) {
338
+ this.nodes.wrapper.setAttribute(DATA_ATTR.narrow, 'true');
339
+ }
340
+
341
+ /**
342
+ * Set customizable bottom zone height
343
+ */
344
+ this.nodes.redactor.style.paddingBottom = this.config.minHeight + 'px';
345
+
346
+ this.nodes.wrapper.appendChild(this.nodes.redactor);
347
+ this.nodes.holder.appendChild(this.nodes.wrapper);
348
+
349
+ this.bindReadOnlyInsensitiveListeners();
350
+ }
351
+
352
+ /**
353
+ * Appends CSS
354
+ */
355
+ private loadStyles(): void {
356
+ /**
357
+ * Load CSS
358
+ */
359
+
360
+ const styleTagId = 'blok-styles';
361
+
362
+ /**
363
+ * Do not append styles again if they are already on the page
364
+ */
365
+ if ($.get(styleTagId)) {
366
+ return;
367
+ }
368
+
369
+ /**
370
+ * Make tag
371
+ */
372
+ const tag = $.make('style', null, {
373
+ id: styleTagId,
374
+ textContent: styles.toString(),
375
+ });
376
+
377
+ /**
378
+ * If user enabled Content Security Policy, he can pass nonce through the config
379
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce
380
+ */
381
+ if (this.config.style && !isEmpty(this.config.style) && this.config.style.nonce) {
382
+ tag.setAttribute('nonce', this.config.style.nonce);
383
+ }
384
+
385
+ /**
386
+ * Append styles at the top of HEAD tag
387
+ */
388
+ $.prepend(document.head, tag);
389
+ }
390
+
391
+ /**
392
+ * Adds listeners that should work both in read-only and read-write modes
393
+ */
394
+ private bindReadOnlyInsensitiveListeners(): void {
395
+ this.listeners.on(document, 'selectionchange', this.selectionChangeDebounced);
396
+
397
+ this.listeners.on(window, 'resize', this.resizeDebouncer, {
398
+ passive: true,
399
+ });
400
+
401
+ this.listeners.on(this.nodes.redactor, 'mousedown', this.documentTouchedListener, {
402
+ capture: true,
403
+ passive: true,
404
+ });
405
+
406
+ this.listeners.on(this.nodes.redactor, 'touchstart', this.documentTouchedListener, {
407
+ capture: true,
408
+ passive: true,
409
+ });
410
+ }
411
+
412
+ /**
413
+ * Removes listeners that should work both in read-only and read-write modes
414
+ */
415
+ private unbindReadOnlyInsensitiveListeners(): void {
416
+ this.listeners.off(document, 'selectionchange', this.selectionChangeDebounced);
417
+ this.listeners.off(window, 'resize', this.resizeDebouncer);
418
+ this.listeners.off(this.nodes.redactor, 'mousedown', this.documentTouchedListener);
419
+ this.listeners.off(this.nodes.redactor, 'touchstart', this.documentTouchedListener);
420
+ }
421
+
422
+
423
+ /**
424
+ * Adds listeners that should work only in read-only mode
425
+ */
426
+ private bindReadOnlySensitiveListeners(): void {
427
+ this.readOnlyMutableListeners.on(this.nodes.redactor, 'click', (event: Event) => {
428
+ if (event instanceof MouseEvent) {
429
+ this.redactorClicked(event);
430
+ }
431
+ }, false);
432
+
433
+ this.readOnlyMutableListeners.on(document, 'keydown', (event: Event) => {
434
+ if (event instanceof KeyboardEvent) {
435
+ this.documentKeydown(event);
436
+ }
437
+ }, true);
438
+
439
+ this.readOnlyMutableListeners.on(document, 'mousedown', (event: Event) => {
440
+ if (event instanceof MouseEvent) {
441
+ this.documentClicked(event);
442
+ }
443
+ }, true);
444
+
445
+ /**
446
+ * Start watching 'block-hovered' events that is used by Toolbar for moving
447
+ */
448
+ this.watchBlockHoveredEvents();
449
+
450
+ /**
451
+ * We have custom logic for providing placeholders for contenteditable elements.
452
+ * To make it work, we need to have data-blok-empty mark on empty inputs.
453
+ */
454
+ this.enableInputsEmptyMark();
455
+ }
456
+
457
+
458
+ /**
459
+ * Listen redactor mousemove to emit 'block-hovered' event
460
+ */
461
+ private watchBlockHoveredEvents(): void {
462
+ /**
463
+ * Used to not emit the same block multiple times to the 'block-hovered' event on every mousemove.
464
+ * Stores block ID to ensure consistent comparison regardless of how the block was detected.
465
+ */
466
+ const blockHoveredState: { lastHoveredBlockId: string | null } = {
467
+ lastHoveredBlockId: null,
468
+ };
469
+
470
+ const handleBlockHovered = (event: Event): void => {
471
+ if (typeof MouseEvent === 'undefined' || !(event instanceof MouseEvent)) {
472
+ return;
473
+ }
474
+
475
+ const hoveredBlockElement = (event.target as Element | null)?.closest('[data-blok-testid="block-wrapper"]');
476
+
477
+ /**
478
+ * If no block element found directly, try the extended hover zone
479
+ */
480
+ const zoneBlock = !hoveredBlockElement
481
+ ? this.findBlockInHoverZone(event.clientX, event.clientY)
482
+ : null;
483
+
484
+ if (zoneBlock !== null && blockHoveredState.lastHoveredBlockId !== zoneBlock.id) {
485
+ blockHoveredState.lastHoveredBlockId = zoneBlock.id;
486
+
487
+ this.eventsDispatcher.emit(BlockHovered, {
488
+ block: zoneBlock,
489
+ target: zoneBlock.holder,
490
+ });
491
+ }
492
+
493
+ if (zoneBlock !== null) {
494
+ return;
495
+ }
496
+
497
+ if (!hoveredBlockElement) {
498
+ return;
499
+ }
500
+
501
+ const block = this.Blok.BlockManager.getBlockByChildNode(hoveredBlockElement);
502
+
503
+ if (!block) {
504
+ return;
505
+ }
506
+
507
+ /**
508
+ * For multi-block selection, still emit 'block-hovered' event so toolbar can follow the hovered block.
509
+ * The toolbar module will handle the logic of whether to move or not.
510
+ */
511
+ if (blockHoveredState.lastHoveredBlockId === block.id) {
512
+ return;
513
+ }
514
+
515
+ blockHoveredState.lastHoveredBlockId = block.id;
516
+
517
+ this.eventsDispatcher.emit(BlockHovered, {
518
+ block,
519
+ target: event.target as Element,
520
+ });
521
+ };
522
+
523
+ const throttledHandleBlockHovered = throttle(
524
+ handleBlockHovered as (...args: unknown[]) => unknown,
525
+
526
+ 20
527
+ );
528
+
529
+ /**
530
+ * Listen on document to detect hover in the extended zone
531
+ * which is outside the wrapper's bounds.
532
+ * We filter events to only process those over the editor or in the hover zone.
533
+ */
534
+ this.readOnlyMutableListeners.on(document, 'mousemove', (event: Event) => {
535
+ throttledHandleBlockHovered(event);
536
+ }, {
537
+ passive: true,
538
+ });
539
+ }
540
+
541
+ /**
542
+ * Finds a block by vertical position when cursor is in the extended hover zone.
543
+ * The zone extends HOVER_ZONE_SIZE pixels from the content edge (left for LTR, right for RTL).
544
+ * @param clientX - Cursor X position
545
+ * @param clientY - Cursor Y position
546
+ * @returns Block at the vertical position, or null if not in hover zone or no block found
547
+ */
548
+ private findBlockInHoverZone(clientX: number, clientY: number): Block | null {
549
+ const contentRect = this.contentRect;
550
+
551
+ /**
552
+ * For LTR: check if cursor is within hover zone to the left of content
553
+ * For RTL: check if cursor is within hover zone to the right of content
554
+ */
555
+ const isInHoverZone = this.isRtl
556
+ ? clientX > contentRect.right && clientX <= contentRect.right + HOVER_ZONE_SIZE
557
+ : clientX < contentRect.left && clientX >= contentRect.left - HOVER_ZONE_SIZE;
558
+
559
+ if (!isInHoverZone) {
560
+ return null;
561
+ }
562
+
563
+ /**
564
+ * Find block by Y position
565
+ */
566
+ for (const block of this.Blok.BlockManager.blocks) {
567
+ const rect = block.holder.getBoundingClientRect();
568
+
569
+ if (clientY >= rect.top && clientY <= rect.bottom) {
570
+ return block;
571
+ }
572
+ }
573
+
574
+ return null;
575
+ }
576
+
577
+ /**
578
+ * Unbind events that should work only in read-only mode
579
+ */
580
+ private unbindReadOnlySensitiveListeners(): void {
581
+ this.readOnlyMutableListeners.clearAll();
582
+ }
583
+
584
+ /**
585
+ * Resize window handler
586
+ */
587
+ private windowResize(): void {
588
+ /**
589
+ * Invalidate content zone size cached, because it may be changed
590
+ */
591
+ this.contentRectCache = null;
592
+
593
+ /**
594
+ * Detect mobile version
595
+ */
596
+ this.setIsMobile();
597
+ }
598
+
599
+ /**
600
+ * All keydowns on document
601
+ * @param {KeyboardEvent} event - keyboard event
602
+ */
603
+ private documentKeydown(event: KeyboardEvent): void {
604
+ const key = event.key ?? '';
605
+
606
+ switch (key) {
607
+ case 'Enter':
608
+ this.enterPressed(event);
609
+ break;
610
+
611
+ case 'Backspace':
612
+ case 'Delete':
613
+ this.backspacePressed(event);
614
+ break;
615
+
616
+ case 'Escape':
617
+ this.escapePressed(event);
618
+ break;
619
+
620
+ case 'Tab':
621
+ this.tabPressed(event);
622
+ break;
623
+
624
+ default:
625
+ this.defaultBehaviour(event);
626
+ break;
627
+ }
628
+ }
629
+
630
+ /**
631
+ * Handle Tab key press at document level for multi-select indent/outdent
632
+ * @param {KeyboardEvent} event - keyboard event
633
+ */
634
+ private tabPressed(event: KeyboardEvent): void {
635
+ const { BlockSelection } = this.Blok;
636
+
637
+ /**
638
+ * Only handle Tab when blocks are selected (for multi-select indent)
639
+ * Otherwise, let the default behavior handle it (e.g., toolbar navigation)
640
+ */
641
+ if (!BlockSelection.anyBlockSelected) {
642
+ this.defaultBehaviour(event);
643
+
644
+ return;
645
+ }
646
+
647
+ /**
648
+ * Forward to BlockEvents to handle the multi-select indent/outdent.
649
+ * BlockEvents.keydown will call preventDefault if needed.
650
+ */
651
+ this.Blok.BlockEvents.keydown(event);
652
+
653
+ /**
654
+ * When blocks are selected, always prevent default Tab behavior (focus navigation)
655
+ * even if the indent operation couldn't be performed (e.g., mixed block types).
656
+ * This ensures Tab doesn't unexpectedly move focus or trigger single-block indent.
657
+ * We call preventDefault AFTER BlockEvents.keydown so that check for defaultPrevented passes.
658
+ * We also stop propagation to prevent the event from reaching block-level handlers
659
+ * (like ListItem's handleKeyDown) which might try to handle the Tab independently.
660
+ */
661
+ event.preventDefault();
662
+ event.stopPropagation();
663
+ }
664
+
665
+ /**
666
+ * Ignore all other document's keydown events
667
+ * @param {KeyboardEvent} event - keyboard event
668
+ */
669
+ private defaultBehaviour(event: KeyboardEvent): void {
670
+ const { currentBlock } = this.Blok.BlockManager;
671
+ const target = event.target;
672
+ const isTargetElement = target instanceof HTMLElement;
673
+ const keyDownOnBlok = isTargetElement ? target.closest('[data-blok-testid="blok-editor"]') : null;
674
+ const isMetaKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
675
+
676
+ /**
677
+ * Ignore keydowns from inside the BlockSettings popover (e.g., search input)
678
+ * to prevent closing the popover when typing
679
+ */
680
+ if (isTargetElement && this.Blok.BlockSettings.contains(target)) {
681
+ return;
682
+ }
683
+
684
+ /**
685
+ * Handle navigation mode keys even when focus is outside the editor
686
+ * Skip if event was already handled (e.g., by block holder listener)
687
+ */
688
+ if (this.Blok.BlockSelection.navigationModeEnabled && !event.defaultPrevented) {
689
+ this.Blok.BlockEvents.keydown(event);
690
+ }
691
+
692
+ if (this.Blok.BlockSelection.navigationModeEnabled) {
693
+ return;
694
+ }
695
+
696
+ /**
697
+ * When some block is selected, but the caret is not set inside the blok, treat such keydowns as keydown on selected block.
698
+ */
699
+ if (currentBlock !== undefined && keyDownOnBlok === null) {
700
+ this.Blok.BlockEvents.keydown(event);
701
+
702
+ return;
703
+ }
704
+
705
+ /**
706
+ * Ignore keydowns on blok and meta keys
707
+ */
708
+ if (keyDownOnBlok || (currentBlock && isMetaKey)) {
709
+ return;
710
+ }
711
+
712
+ /**
713
+ * Remove all highlights and remove caret
714
+ */
715
+ this.Blok.BlockManager.unsetCurrentBlock();
716
+
717
+ /**
718
+ * Close Toolbar
719
+ */
720
+ this.Blok.Toolbar.close();
721
+ }
722
+
723
+ /**
724
+ * @param {KeyboardEvent} event - keyboard event
725
+ */
726
+ private backspacePressed(event: KeyboardEvent): void {
727
+ /**
728
+ * Ignore backspace/delete from inside the BlockSettings popover (e.g., search input)
729
+ */
730
+ if (this.Blok.BlockSettings.contains(event.target as HTMLElement)) {
731
+ return;
732
+ }
733
+
734
+ const { BlockManager, BlockSelection, Caret } = this.Blok;
735
+
736
+ const selectionExists = Selection.isSelectionExists;
737
+ const selectionCollapsed = Selection.isCollapsed;
738
+
739
+ /**
740
+ * If any block selected and selection doesn't exists on the page (that means no other editable element is focused),
741
+ * remove selected blocks
742
+ */
743
+ const shouldRemoveSelection = BlockSelection.anyBlockSelected && (
744
+ !selectionExists ||
745
+ selectionCollapsed === true ||
746
+ this.Blok.CrossBlockSelection.isCrossBlockSelectionStarted
747
+ );
748
+
749
+ if (!shouldRemoveSelection) {
750
+ return;
751
+ }
752
+
753
+ const selectionPositionIndex = BlockManager.removeSelectedBlocks();
754
+
755
+ if (selectionPositionIndex === undefined) {
756
+ return;
757
+ }
758
+
759
+ const newBlock = BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true);
760
+
761
+ Caret.setToBlock(newBlock, Caret.positions.START);
762
+
763
+ /** Clear selection */
764
+ BlockSelection.clearSelection(event);
765
+
766
+ /**
767
+ * Stop propagations
768
+ * Manipulation with BlockSelections is handled in global backspacePress because they may occur
769
+ * with CMD+A or RectangleSelection and they can be handled on document event
770
+ */
771
+ event.preventDefault();
772
+ event.stopPropagation();
773
+ event.stopImmediatePropagation();
774
+ }
775
+
776
+ /**
777
+ * Escape pressed
778
+ * If some of Toolbar components are opened, then close it otherwise close Toolbar.
779
+ * If focus is in editor content and no toolbars are open, enable navigation mode.
780
+ * @param {Event} event - escape keydown event
781
+ */
782
+ private escapePressed(event: KeyboardEvent): void {
783
+ /**
784
+ * If navigation mode is already enabled, disable it and return
785
+ */
786
+ if (this.Blok.BlockSelection.navigationModeEnabled) {
787
+ this.Blok.BlockSelection.disableNavigationMode(false);
788
+
789
+ return;
790
+ }
791
+
792
+ /**
793
+ * Clear blocks selection by ESC (but not when entering navigation mode)
794
+ */
795
+ if (this.Blok.BlockSelection.anyBlockSelected) {
796
+ this.Blok.BlockSelection.clearSelection(event);
797
+
798
+ return;
799
+ }
800
+
801
+ if (this.Blok.Toolbar.toolbox.opened) {
802
+ this.Blok.Toolbar.toolbox.close();
803
+ this.Blok.BlockManager.currentBlock &&
804
+ this.Blok.Caret.setToBlock(this.Blok.BlockManager.currentBlock, this.Blok.Caret.positions.END);
805
+
806
+ return;
807
+ }
808
+
809
+ if (this.Blok.BlockSettings.opened) {
810
+ this.Blok.BlockSettings.close();
811
+
812
+ return;
813
+ }
814
+
815
+ /**
816
+ * If a nested popover is open (like convert-to dropdown),
817
+ * close only the nested popover, not the entire inline toolbar.
818
+ * We use stopImmediatePropagation to prevent other keydown listeners
819
+ * (like the one on block.holder) from also handling this event.
820
+ */
821
+ if (this.Blok.InlineToolbar.opened && this.Blok.InlineToolbar.hasNestedPopoverOpen) {
822
+ event.preventDefault();
823
+ event.stopPropagation();
824
+ event.stopImmediatePropagation();
825
+ this.Blok.InlineToolbar.closeNestedPopover();
826
+
827
+ return;
828
+ }
829
+
830
+ if (this.Blok.InlineToolbar.opened) {
831
+ this.Blok.InlineToolbar.close();
832
+
833
+ return;
834
+ }
835
+
836
+ /**
837
+ * If focus is inside editor content and no toolbars are open,
838
+ * enable navigation mode for keyboard-based block navigation
839
+ */
840
+ const target = event.target;
841
+ const isTargetElement = target instanceof HTMLElement;
842
+ const isInsideRedactor = isTargetElement && this.nodes.redactor.contains(target);
843
+ const hasCurrentBlock = this.Blok.BlockManager.currentBlock !== undefined;
844
+
845
+ if (isInsideRedactor && hasCurrentBlock) {
846
+ event.preventDefault();
847
+ this.Blok.Toolbar.close();
848
+ this.Blok.BlockSelection.enableNavigationMode();
849
+
850
+ return;
851
+ }
852
+
853
+ this.Blok.Toolbar.close();
854
+ }
855
+
856
+ /**
857
+ * Enter pressed on document
858
+ * @param {KeyboardEvent} event - keyboard event
859
+ */
860
+ private enterPressed(event: KeyboardEvent): void {
861
+ const { BlockManager, BlockSelection, BlockEvents } = this.Blok;
862
+
863
+ if (this.someToolbarOpened) {
864
+ return;
865
+ }
866
+
867
+ /**
868
+ * If navigation mode is enabled, delegate to BlockEvents to handle Enter.
869
+ * This will set the caret at the end of the current block.
870
+ */
871
+ if (BlockSelection.navigationModeEnabled) {
872
+ BlockEvents.keydown(event);
873
+
874
+ return;
875
+ }
876
+
877
+ const hasPointerToBlock = BlockManager.currentBlockIndex >= 0;
878
+
879
+ const selectionExists = Selection.isSelectionExists;
880
+ const selectionCollapsed = Selection.isCollapsed;
881
+
882
+ /**
883
+ * If any block selected and selection doesn't exists on the page (that means no other editable element is focused),
884
+ * remove selected blocks
885
+ */
886
+ if (BlockSelection.anyBlockSelected && (!selectionExists || selectionCollapsed === true)) {
887
+ /** Clear selection */
888
+ BlockSelection.clearSelection(event);
889
+
890
+ /**
891
+ * Stop propagations
892
+ * Manipulation with BlockSelections is handled in global enterPress because they may occur
893
+ * with CMD+A or RectangleSelection
894
+ */
895
+ event.preventDefault();
896
+ event.stopImmediatePropagation();
897
+ event.stopPropagation();
898
+
899
+ return;
900
+ }
901
+
902
+ /**
903
+ * If Caret is not set anywhere, event target on Enter is always Element that we handle
904
+ * In our case it is document.body
905
+ *
906
+ * So, BlockManager points some Block and Enter press is on Body
907
+ * We can create a new block
908
+ */
909
+ if (!this.someToolbarOpened && hasPointerToBlock && (event.target as HTMLElement).tagName === 'BODY') {
910
+ /**
911
+ * Insert the default typed Block
912
+ */
913
+ const newBlock = this.Blok.BlockManager.insert();
914
+
915
+ /**
916
+ * Prevent default enter behaviour to prevent adding a new line (<div><br></div>) to the inserted block
917
+ */
918
+ event.preventDefault();
919
+ this.Blok.Caret.setToBlock(newBlock);
920
+
921
+ /**
922
+ * Move toolbar and show plus button because new Block is empty
923
+ */
924
+ this.Blok.Toolbar.moveAndOpen(newBlock);
925
+ }
926
+
927
+ this.Blok.BlockSelection.clearSelection(event);
928
+ }
929
+
930
+ /**
931
+ * All clicks on document
932
+ * @param {MouseEvent} event - Click event
933
+ */
934
+ private documentClicked(event: MouseEvent): void {
935
+ /**
936
+ * Sometimes we emulate click on some UI elements, for example by Enter on Block Settings button
937
+ * We don't need to handle such events, because they handled in other place.
938
+ */
939
+ if (!event.isTrusted) {
940
+ return;
941
+ }
942
+ /**
943
+ * Close Inline Toolbar when nothing selected
944
+ * Do not fire check on clicks at the Inline Toolbar buttons
945
+ */
946
+ const target = event.target as HTMLElement;
947
+ const clickedInsideOfBlok = this.nodes.holder.contains(target) || Selection.isAtBlok;
948
+ const clickedInsideRedactor = this.nodes.redactor.contains(target);
949
+ const clickedInsideToolbar = this.Blok.Toolbar.contains(target);
950
+ const clickedInsideInlineToolbar = this.Blok.InlineToolbar.containsNode(target);
951
+ const clickedInsideBlokSurface = clickedInsideOfBlok || clickedInsideToolbar;
952
+
953
+ /**
954
+ * Check if click is on Block Settings, Settings Toggler, or Plus Button
955
+ * These elements have their own click handlers and should not trigger default behavior
956
+ */
957
+ const isClickedInsideBlockSettings = this.Blok.BlockSettings.contains(target);
958
+ const isClickedInsideBlockSettingsToggler = this.Blok.Toolbar.nodes.settingsToggler?.contains(target);
959
+ const isClickedInsidePlusButton = this.Blok.Toolbar.nodes.plusButton?.contains(target);
960
+ const doNotProcess = isClickedInsideBlockSettings || isClickedInsideBlockSettingsToggler || isClickedInsidePlusButton;
961
+
962
+ const shouldClearCurrentBlock = !clickedInsideBlokSurface || (!clickedInsideRedactor && !clickedInsideToolbar);
963
+
964
+ /**
965
+ * Don't clear current block when clicking on settings toggler, plus button, or inside block settings
966
+ * These elements need the current block to function properly
967
+ */
968
+ if (shouldClearCurrentBlock && !doNotProcess) {
969
+ /**
970
+ * Clear pointer on BlockManager
971
+ *
972
+ * Current page might contain several instances
973
+ * Click between instances MUST clear focus, pointers and close toolbars
974
+ */
975
+ this.Blok.BlockManager.unsetCurrentBlock();
976
+ this.Blok.Toolbar.close();
977
+ }
978
+
979
+ const shouldCloseBlockSettings = this.Blok.BlockSettings.opened && !doNotProcess;
980
+ if (shouldCloseBlockSettings) {
981
+ this.Blok.BlockSettings.close();
982
+ }
983
+
984
+ if (shouldCloseBlockSettings && clickedInsideRedactor) {
985
+ const clickedBlock = this.Blok.BlockManager.getBlockByChildNode(target);
986
+ this.Blok.Toolbar.moveAndOpen(clickedBlock);
987
+ }
988
+
989
+ /**
990
+ * Clear Selection if user clicked somewhere
991
+ * But preserve selection when clicking on block settings toggler or inside block settings
992
+ * to allow multi-block operations like conversion
993
+ */
994
+ if (!doNotProcess) {
995
+ this.Blok.BlockSelection.clearSelection(event);
996
+ }
997
+
998
+ /**
999
+ * Close Inline Toolbar when clicking outside of it
1000
+ * This handles clicks anywhere outside the inline toolbar,
1001
+ * including inside the editor content area or on page controls
1002
+ */
1003
+ if (this.Blok.InlineToolbar.opened && !clickedInsideInlineToolbar) {
1004
+ this.Blok.InlineToolbar.close();
1005
+ }
1006
+ }
1007
+
1008
+ /**
1009
+ * First touch on blok
1010
+ * Fired before click
1011
+ *
1012
+ * Used to change current block — we need to do it before 'selectionChange' event.
1013
+ * Also:
1014
+ * - Move and show the Toolbar
1015
+ * - Set a Caret
1016
+ * @param event - touch or mouse event
1017
+ */
1018
+ private documentTouched(event: Event): void {
1019
+ const initialTarget = event.target as HTMLElement;
1020
+
1021
+ /**
1022
+ * If click was fired on Blok`s wrapper, try to get clicked node by elementFromPoint method
1023
+ */
1024
+ const clickedNode = (() => {
1025
+ if (initialTarget !== this.nodes.redactor) {
1026
+ return initialTarget;
1027
+ }
1028
+
1029
+ if (event instanceof MouseEvent) {
1030
+ const nodeFromPoint = document.elementFromPoint(event.clientX, event.clientY) as HTMLElement | null;
1031
+
1032
+ return nodeFromPoint ?? initialTarget;
1033
+ }
1034
+
1035
+ if (event instanceof TouchEvent && event.touches.length > 0) {
1036
+ const { clientX, clientY } = event.touches[0];
1037
+ const nodeFromPoint = document.elementFromPoint(clientX, clientY) as HTMLElement | null;
1038
+
1039
+ return nodeFromPoint ?? initialTarget;
1040
+ }
1041
+
1042
+ return initialTarget;
1043
+ })();
1044
+
1045
+ /**
1046
+ * Select clicked Block as Current
1047
+ */
1048
+ try {
1049
+ this.Blok.BlockManager.setCurrentBlockByChildNode(clickedNode);
1050
+ } catch (_e) {
1051
+ /**
1052
+ * If clicked outside first-level Blocks and it is not RectSelection, set Caret to the last empty Block
1053
+ */
1054
+ if (!this.Blok.RectangleSelection.isRectActivated()) {
1055
+ this.Blok.Caret.setToTheLastBlock();
1056
+ }
1057
+ }
1058
+
1059
+ /**
1060
+ * Move and open toolbar
1061
+ * (used for showing Block Settings toggler after opening and closing Inline Toolbar)
1062
+ */
1063
+ if (!this.Blok.ReadOnly.isEnabled && !this.Blok.Toolbar.contains(initialTarget)) {
1064
+ this.Blok.Toolbar.moveAndOpen(undefined, clickedNode);
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * All clicks on the redactor zone
1070
+ * @param {MouseEvent} event - click event
1071
+ * @description
1072
+ * - By clicks on the Blok's bottom zone:
1073
+ * - if last Block is empty, set a Caret to this
1074
+ * - otherwise, add a new empty Block and set a Caret to that
1075
+ */
1076
+ private redactorClicked(event: MouseEvent): void {
1077
+ if (!Selection.isCollapsed) {
1078
+ return;
1079
+ }
1080
+
1081
+ /**
1082
+ * case when user clicks on anchor element
1083
+ * if it is clicked via ctrl key, then we open new window with url
1084
+ */
1085
+ const element = event.target as Element;
1086
+ const ctrlKey = event.metaKey || event.ctrlKey;
1087
+ const shouldOpenAnchorInNewTab = $.isAnchor(element) && ctrlKey;
1088
+
1089
+ if (!shouldOpenAnchorInNewTab) {
1090
+ this.processBottomZoneClick(event);
1091
+
1092
+ return;
1093
+ }
1094
+
1095
+ event.stopImmediatePropagation();
1096
+ event.stopPropagation();
1097
+
1098
+ const href = element.getAttribute('href');
1099
+
1100
+ if (!href) {
1101
+ return;
1102
+ }
1103
+
1104
+ const validUrl = getValidUrl(href);
1105
+
1106
+ openTab(validUrl);
1107
+ }
1108
+
1109
+ /**
1110
+ * Check if user clicks on the Blok's bottom zone:
1111
+ * - set caret to the last block
1112
+ * - or add new empty block
1113
+ * @param event - click event
1114
+ */
1115
+ private processBottomZoneClick(event: MouseEvent): void {
1116
+ const lastBlock = this.Blok.BlockManager.getBlockByIndex(-1);
1117
+
1118
+ const lastBlockBottomCoord = $.offset(lastBlock.holder).bottom;
1119
+ const clickedCoord = event.pageY;
1120
+ const { BlockSelection } = this.Blok;
1121
+ const isClickedBottom = event.target instanceof Element &&
1122
+ event.target.isEqualNode(this.nodes.redactor) &&
1123
+ /**
1124
+ * If there is cross block selection started, target will be equal to redactor so we need additional check
1125
+ */
1126
+ !BlockSelection.anyBlockSelected &&
1127
+
1128
+ /**
1129
+ * Prevent caret jumping (to last block) when clicking between blocks
1130
+ */
1131
+ lastBlockBottomCoord < clickedCoord;
1132
+
1133
+ if (!isClickedBottom) {
1134
+ return;
1135
+ }
1136
+
1137
+ event.stopImmediatePropagation();
1138
+ event.stopPropagation();
1139
+
1140
+ const { BlockManager, Caret, Toolbar } = this.Blok;
1141
+
1142
+ /**
1143
+ * Insert a default-block at the bottom if:
1144
+ * - last-block is not a default-block (Text)
1145
+ * to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table)
1146
+ * - Or, default-block is not empty
1147
+ */
1148
+ if (!BlockManager.lastBlock?.tool.isDefault || !BlockManager.lastBlock?.isEmpty) {
1149
+ BlockManager.insertAtEnd();
1150
+ }
1151
+
1152
+ /**
1153
+ * Set the caret and toolbar to empty Block
1154
+ */
1155
+ Caret.setToTheLastBlock();
1156
+ Toolbar.moveAndOpen(BlockManager.lastBlock);
1157
+ }
1158
+
1159
+ /**
1160
+ * Handle selection changes on mobile devices
1161
+ * Uses for showing the Inline Toolbar
1162
+ */
1163
+ private selectionChanged(): void {
1164
+ const { CrossBlockSelection, BlockSelection } = this.Blok;
1165
+ const focusedElement = Selection.anchorElement;
1166
+
1167
+ if (CrossBlockSelection.isCrossBlockSelectionStarted && BlockSelection.anyBlockSelected) {
1168
+ // Removes all ranges when any Block is selected
1169
+ Selection.get()?.removeAllRanges();
1170
+ }
1171
+
1172
+ /**
1173
+ * Ignore transient selection changes triggered by fake background wrappers (used by inline tools
1174
+ * like Convert) while the Inline Toolbar is already open. Otherwise, the toolbar gets torn down
1175
+ * and re-rendered, which closes nested popovers before a user can click their items.
1176
+ */
1177
+ const hasFakeBackground = document.querySelector('[data-blok-fake-background="true"]') !== null;
1178
+
1179
+ if (hasFakeBackground && this.Blok?.InlineToolbar?.opened) {
1180
+ return;
1181
+ }
1182
+
1183
+ /**
1184
+ * Usual clicks on some controls, for example, Block Tunes Toggler
1185
+ */
1186
+ if (!focusedElement && !Selection.range) {
1187
+ /**
1188
+ * If there is no selected range, close inline toolbar
1189
+ * @todo Make this method more straightforward
1190
+ */
1191
+ this.Blok.InlineToolbar.close();
1192
+ }
1193
+
1194
+ if (!focusedElement) {
1195
+ return;
1196
+ }
1197
+
1198
+ /**
1199
+ * Event can be fired on clicks at non-block-content elements,
1200
+ * for example, at the Inline Toolbar or some Block Tune element.
1201
+ * We also make sure that the closest block belongs to the current blok and not a parent
1202
+ */
1203
+ const closestBlock = focusedElement.closest('[data-blok-testid="block-content"]');
1204
+ const clickedOutsideBlockContent = closestBlock === null || (closestBlock.closest('[data-blok-testid="blok-editor"]') !== this.nodes.wrapper);
1205
+
1206
+ const inlineToolbarEnabledForExternalTool = (focusedElement as HTMLElement).getAttribute('data-blok-inline-toolbar') === 'true';
1207
+ const shouldCloseInlineToolbar = clickedOutsideBlockContent && !this.Blok.InlineToolbar.containsNode(focusedElement);
1208
+
1209
+ /**
1210
+ * If the inline toolbar is already open without a nested popover,
1211
+ * don't close or re-render it. This prevents the toolbar from flickering
1212
+ * when the user closes a nested popover (e.g., via Esc key).
1213
+ *
1214
+ * However, if the selection is now collapsed or empty (e.g., user deleted the selected text),
1215
+ * we should close the inline toolbar since there's nothing to format.
1216
+ *
1217
+ * Important: Don't close the toolbar if a flipper item is focused (user is navigating
1218
+ * with Tab/Arrow keys). In some browsers (webkit), keyboard navigation within the
1219
+ * popover can trigger selectionchange events that make the selection appear empty.
1220
+ */
1221
+ const currentSelection = Selection.get();
1222
+ const selectionIsEmpty = !currentSelection || currentSelection.isCollapsed || Selection.text.length === 0;
1223
+ const hasFlipperFocus = this.Blok.InlineToolbar.hasFlipperFocus;
1224
+
1225
+ if (selectionIsEmpty && this.Blok.InlineToolbar.opened && !hasFlipperFocus) {
1226
+ this.Blok.InlineToolbar.close();
1227
+
1228
+ return;
1229
+ }
1230
+
1231
+ if (this.Blok.InlineToolbar.opened && !this.Blok.InlineToolbar.hasNestedPopoverOpen) {
1232
+ return;
1233
+ }
1234
+
1235
+ if (shouldCloseInlineToolbar) {
1236
+ /**
1237
+ * If new selection is not on Inline Toolbar, we need to close it
1238
+ */
1239
+ this.Blok.InlineToolbar.close();
1240
+ }
1241
+
1242
+ if (clickedOutsideBlockContent && !inlineToolbarEnabledForExternalTool) {
1243
+ /**
1244
+ * Case when we click on external tool elements,
1245
+ * for example some Block Tune element.
1246
+ * If this external content editable element has data-inline-toolbar="true"
1247
+ */
1248
+ return;
1249
+ }
1250
+
1251
+ /**
1252
+ * Set current block when entering to Blok by tab key
1253
+ */
1254
+ if (!this.Blok.BlockManager.currentBlock) {
1255
+ this.Blok.BlockManager.setCurrentBlockByChildNode(focusedElement);
1256
+ }
1257
+
1258
+ void this.Blok.InlineToolbar.tryToShow(true);
1259
+ }
1260
+
1261
+ /**
1262
+ * Blok provides and ability to show placeholders for empty contenteditable elements
1263
+ *
1264
+ * This method watches for input and focus events and toggles 'data-blok-empty' attribute
1265
+ * to workaroud the case, when inputs contains only <br>s and has no visible content
1266
+ * Then, CSS could rely on this attribute to show placeholders
1267
+ */
1268
+ private enableInputsEmptyMark(): void {
1269
+ /**
1270
+ * Toggle data-blok-empty attribute on input depending on its emptiness
1271
+ * @param event - input or focus event
1272
+ */
1273
+ const handleInputOrFocusChange = (event: Event): void => {
1274
+ const input = event.target as HTMLElement;
1275
+
1276
+ toggleEmptyMark(input);
1277
+ };
1278
+
1279
+ this.readOnlyMutableListeners.on(this.nodes.wrapper, 'input', handleInputOrFocusChange);
1280
+ this.readOnlyMutableListeners.on(this.nodes.wrapper, 'focusin', handleInputOrFocusChange);
1281
+ this.readOnlyMutableListeners.on(this.nodes.wrapper, 'focusout', handleInputOrFocusChange);
1282
+ }
1283
+ }