@jackuait/blok 0.4.1-beta.5 → 0.4.1-beta.6

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