@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,1375 @@
1
+ /**
2
+ * Contains keyboard and mouse events bound on each Block by Block Manager
3
+ */
4
+ import { Module } from '../__module';
5
+ import { delay, isIosDevice, keyCodes } from '../utils';
6
+ import { SelectionUtils } from '../selection';
7
+ import { Flipper } from '../flipper';
8
+ import type { Block } from '../block';
9
+ import { areBlocksMergeable } from '../utils/blocks';
10
+ import { findNbspAfterEmptyInline, focus, isCaretAtEndOfInput, isCaretAtStartOfInput } from '../utils/caret';
11
+
12
+ const KEYBOARD_EVENT_KEY_TO_KEY_CODE_MAP: Record<string, number> = {
13
+ Backspace: keyCodes.BACKSPACE,
14
+ Delete: keyCodes.DELETE,
15
+ Enter: keyCodes.ENTER,
16
+ Tab: keyCodes.TAB,
17
+ ArrowDown: keyCodes.DOWN,
18
+ ArrowRight: keyCodes.RIGHT,
19
+ ArrowUp: keyCodes.UP,
20
+ ArrowLeft: keyCodes.LEFT,
21
+ };
22
+
23
+ const PRINTABLE_SPECIAL_KEYS = new Set(['Enter', 'Process', 'Spacebar', 'Space', 'Dead']);
24
+ const EDITABLE_INPUT_SELECTOR = '[contenteditable="true"], textarea, input';
25
+
26
+ /**
27
+ * Checks if the keyboard event is a block movement shortcut (Cmd/Ctrl+Shift+Arrow)
28
+ * @param event - keyboard event
29
+ * @param direction - 'up' or 'down'
30
+ * @returns true if this is a block movement shortcut
31
+ */
32
+ const isBlockMovementShortcut = (event: KeyboardEvent, direction: 'up' | 'down'): boolean => {
33
+ const targetKey = direction === 'up' ? 'ArrowUp' : 'ArrowDown';
34
+
35
+ return event.key === targetKey &&
36
+ event.shiftKey &&
37
+ (event.ctrlKey || event.metaKey);
38
+ };
39
+
40
+ /**
41
+ *
42
+ */
43
+ export class BlockEvents extends Module {
44
+ /**
45
+ * All keydowns on Block
46
+ * @param {KeyboardEvent} event - keydown
47
+ */
48
+ public keydown(event: KeyboardEvent): void {
49
+ /**
50
+ * Handle navigation mode keys first
51
+ */
52
+ if (this.handleNavigationModeKeys(event)) {
53
+ return;
54
+ }
55
+
56
+ /**
57
+ * Handle Escape key to enable navigation mode
58
+ */
59
+ if (event.key === 'Escape') {
60
+ this.handleEscapeToEnableNavigation(event);
61
+
62
+ return;
63
+ }
64
+
65
+ /**
66
+ * Run common method for all keydown events
67
+ */
68
+ this.beforeKeydownProcessing(event);
69
+
70
+ if (this.handleSelectedBlocksDeletion(event)) {
71
+ return;
72
+ }
73
+
74
+ /**
75
+ * If event was already handled by something (e.g. tool), we should not handle it
76
+ */
77
+ if (event.defaultPrevented) {
78
+ return;
79
+ }
80
+
81
+ const keyCode = this.getKeyCode(event);
82
+
83
+ /**
84
+ * Fire keydown processor by normalized keyboard code
85
+ */
86
+ switch (keyCode) {
87
+ case keyCodes.BACKSPACE:
88
+ this.backspace(event);
89
+ break;
90
+
91
+ case keyCodes.DELETE:
92
+ this.delete(event);
93
+ break;
94
+
95
+ case keyCodes.ENTER:
96
+ this.enter(event);
97
+ break;
98
+
99
+ case keyCodes.DOWN:
100
+ case keyCodes.RIGHT:
101
+ this.arrowRightAndDown(event);
102
+ break;
103
+
104
+ case keyCodes.UP:
105
+ case keyCodes.LEFT:
106
+ this.arrowLeftAndUp(event);
107
+ break;
108
+
109
+ case keyCodes.TAB:
110
+ this.tabPressed(event);
111
+ break;
112
+ }
113
+
114
+ /**
115
+ * We check for "key" here since on different keyboard layouts "/" can be typed as "Shift + 7" etc
116
+ * @todo probably using "beforeInput" event would be better here
117
+ */
118
+ if (event.key === '/' && !event.ctrlKey && !event.metaKey) {
119
+ this.slashPressed(event);
120
+ }
121
+
122
+ /**
123
+ * If user pressed "Ctrl + /" or "Cmd + /" — open Block Settings
124
+ * We check for "code" here since on different keyboard layouts there can be different keys in place of Slash.
125
+ */
126
+ if (event.code === 'Slash' && (event.ctrlKey || event.metaKey)) {
127
+ event.preventDefault();
128
+ this.commandSlashPressed();
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Tries to delete selected blocks when remove keys pressed.
134
+ * @param event - keyboard event
135
+ * @returns true if event was handled
136
+ */
137
+ private handleSelectedBlocksDeletion(event: KeyboardEvent): boolean {
138
+ const { BlockSelection, BlockManager, Caret, BlockSettings } = this.Blok;
139
+
140
+ /**
141
+ * Ignore delete/backspace from inside the BlockSettings popover (e.g., search input)
142
+ */
143
+ if (BlockSettings.contains(event.target as HTMLElement)) {
144
+ return false;
145
+ }
146
+
147
+ const isRemoveKey = event.key === 'Backspace' || event.key === 'Delete';
148
+ const selectionExists = SelectionUtils.isSelectionExists;
149
+ const selectionCollapsed = SelectionUtils.isCollapsed === true;
150
+ const shouldHandleSelectionDeletion = isRemoveKey &&
151
+ BlockSelection.anyBlockSelected &&
152
+ (!selectionExists || selectionCollapsed);
153
+
154
+ if (!shouldHandleSelectionDeletion) {
155
+ return false;
156
+ }
157
+
158
+ const selectionPositionIndex = BlockManager.removeSelectedBlocks();
159
+
160
+ if (selectionPositionIndex !== undefined) {
161
+ const insertedBlock = BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true);
162
+
163
+ Caret.setToBlock(insertedBlock, Caret.positions.START);
164
+ }
165
+
166
+ BlockSelection.clearSelection(event);
167
+
168
+ event.preventDefault();
169
+ event.stopImmediatePropagation();
170
+ event.stopPropagation();
171
+
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Handles Escape key press to enable navigation mode.
177
+ * Called when user presses Escape while editing a block.
178
+ * @param event - keyboard event
179
+ */
180
+ private handleEscapeToEnableNavigation(event: KeyboardEvent): void {
181
+ const { BlockSelection, BlockSettings, InlineToolbar, Toolbar } = this.Blok;
182
+
183
+ /**
184
+ * If any toolbar is open, let the UI module handle closing it
185
+ */
186
+ if (BlockSettings.opened || InlineToolbar.opened || Toolbar.toolbox.opened) {
187
+ return;
188
+ }
189
+
190
+ /**
191
+ * If blocks are selected, let the UI module handle clearing selection
192
+ */
193
+ if (BlockSelection.anyBlockSelected) {
194
+ return;
195
+ }
196
+
197
+ /**
198
+ * Enable navigation mode
199
+ */
200
+ event.preventDefault();
201
+ Toolbar.close();
202
+ BlockSelection.enableNavigationMode();
203
+ }
204
+
205
+ /**
206
+ * Handles keyboard events when navigation mode is active.
207
+ * In navigation mode:
208
+ * - ArrowUp/ArrowDown: navigate between blocks
209
+ * - Enter: exit navigation mode and focus the block for editing
210
+ * - Escape: exit navigation mode without focusing
211
+ * @param event - keyboard event
212
+ * @returns true if event was handled
213
+ */
214
+ private handleNavigationModeKeys(event: KeyboardEvent): boolean {
215
+ const { BlockSelection } = this.Blok;
216
+
217
+ if (!BlockSelection.navigationModeEnabled) {
218
+ return false;
219
+ }
220
+
221
+ const key = event.key;
222
+
223
+ switch (key) {
224
+ case 'ArrowDown':
225
+ event.preventDefault();
226
+ event.stopPropagation();
227
+ BlockSelection.navigateNext();
228
+
229
+ return true;
230
+
231
+ case 'ArrowUp':
232
+ event.preventDefault();
233
+ event.stopPropagation();
234
+ BlockSelection.navigatePrevious();
235
+
236
+ return true;
237
+
238
+ case 'Enter':
239
+ event.preventDefault();
240
+ event.stopPropagation();
241
+ event.stopImmediatePropagation();
242
+ BlockSelection.disableNavigationMode(true);
243
+
244
+ return true;
245
+
246
+ case 'Escape':
247
+ event.preventDefault();
248
+ event.stopPropagation();
249
+ BlockSelection.disableNavigationMode(false);
250
+
251
+ return true;
252
+
253
+ default:
254
+ /**
255
+ * Any other key exits navigation mode and allows normal input
256
+ */
257
+ if (this.isPrintableKeyEvent(event)) {
258
+ BlockSelection.disableNavigationMode(true);
259
+ }
260
+
261
+ return false;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Fires on keydown before event processing
267
+ * @param {KeyboardEvent} event - keydown
268
+ */
269
+ public beforeKeydownProcessing(event: KeyboardEvent): void {
270
+ /**
271
+ * Do not close Toolbox on Tabs or on Enter with opened Toolbox
272
+ */
273
+ if (!this.needToolbarClosing(event)) {
274
+ return;
275
+ }
276
+
277
+ /**
278
+ * When user type something:
279
+ * - close Toolbar
280
+ * - clear block highlighting
281
+ */
282
+ if (!this.isPrintableKeyEvent(event)) {
283
+ return;
284
+ }
285
+
286
+ this.Blok.Toolbar.close();
287
+
288
+ /**
289
+ * Allow to use shortcuts with selected blocks
290
+ * @type {boolean}
291
+ */
292
+ const isShortcut = event.ctrlKey || event.metaKey || event.altKey || event.shiftKey;
293
+
294
+ if (isShortcut) {
295
+ return;
296
+ }
297
+
298
+ this.Blok.BlockSelection.clearSelection(event);
299
+ }
300
+
301
+ /**
302
+ * Key up on Block:
303
+ * - shows Inline Toolbar if something selected
304
+ * - shows conversion toolbar with 85% of block selection
305
+ * @param {KeyboardEvent} event - keyup event
306
+ */
307
+ public keyup(event: KeyboardEvent): void {
308
+ /**
309
+ * If shift key was pressed some special shortcut is used (eg. cross block selection via shift + arrows)
310
+ */
311
+ if (event.shiftKey) {
312
+ return;
313
+ }
314
+
315
+ /**
316
+ * Check if blok is empty on each keyup and add special css class to wrapper
317
+ */
318
+ this.Blok.UI.checkEmptiness();
319
+ }
320
+
321
+ /**
322
+ * Regex patterns for detecting list shortcuts.
323
+ * Matches patterns like "1. ", "1) ", "2. ", etc. at the start of text
324
+ * Captures remaining content after the shortcut in group 2
325
+ */
326
+ private static readonly ORDERED_LIST_PATTERN = /^(\d+)[.)]\s([\s\S]*)$/;
327
+
328
+ /**
329
+ * Regex pattern for detecting checklist shortcuts.
330
+ * Matches patterns like "[] ", "[ ] ", "[x] ", "[X] " at the start of text
331
+ * Captures remaining content after the shortcut in group 2
332
+ */
333
+ private static readonly CHECKLIST_PATTERN = /^\[(x|X| )?\]\s([\s\S]*)$/;
334
+
335
+ /**
336
+ * Regex pattern for detecting bulleted list shortcuts.
337
+ * Matches patterns like "- " or "* " at the start of text
338
+ * Captures remaining content after the shortcut in group 1
339
+ */
340
+ private static readonly UNORDERED_LIST_PATTERN = /^[-*]\s([\s\S]*)$/;
341
+
342
+ /**
343
+ * Input event handler for Block
344
+ * Detects markdown-like shortcuts for auto-converting to lists
345
+ * @param {InputEvent} event - input event
346
+ */
347
+ public input(event: InputEvent): void {
348
+ /**
349
+ * Only handle insertText events (typing) that end with a space
350
+ */
351
+ if (event.inputType !== 'insertText' || event.data !== ' ') {
352
+ return;
353
+ }
354
+
355
+ this.handleListShortcut();
356
+ }
357
+
358
+ /**
359
+ * Check if current block content matches a list shortcut pattern
360
+ * and convert to appropriate list type.
361
+ * Supports conversion even when there's existing text after the shortcut.
362
+ * Preserves HTML content and maintains caret position.
363
+ */
364
+ private handleListShortcut(): void {
365
+ const { BlockManager, Tools } = this.Blok;
366
+ const currentBlock = BlockManager.currentBlock;
367
+
368
+ if (!currentBlock) {
369
+ return;
370
+ }
371
+
372
+ /**
373
+ * Only convert default blocks (paragraphs)
374
+ */
375
+ if (!currentBlock.tool.isDefault) {
376
+ return;
377
+ }
378
+
379
+ /**
380
+ * Check if list tool is available
381
+ */
382
+ const listTool = Tools.blockTools.get('list');
383
+
384
+ if (!listTool) {
385
+ return;
386
+ }
387
+
388
+ const currentInput = currentBlock.currentInput;
389
+
390
+ if (!currentInput) {
391
+ return;
392
+ }
393
+
394
+ /**
395
+ * Use textContent to match the shortcut pattern
396
+ */
397
+ const textContent = currentInput.textContent || '';
398
+
399
+ /**
400
+ * Get the depth from the block holder if it was previously a nested list item
401
+ * This preserves nesting when converting back to a list
402
+ */
403
+ const depthAttr = currentBlock.holder.getAttribute('data-blok-depth');
404
+ const depth = depthAttr ? parseInt(depthAttr, 10) : 0;
405
+
406
+ /**
407
+ * Check for checklist pattern (e.g., "[] ", "[ ] ", "[x] ", "[X] ")
408
+ */
409
+ const checklistMatch = BlockEvents.CHECKLIST_PATTERN.exec(textContent);
410
+
411
+ if (checklistMatch) {
412
+ /**
413
+ * Determine if the checkbox should be checked
414
+ * [x] or [X] means checked, [] or [ ] means unchecked
415
+ */
416
+ const isChecked = checklistMatch[1]?.toLowerCase() === 'x';
417
+
418
+ /**
419
+ * Extract remaining content (group 2) and calculate shortcut length
420
+ * Shortcut length: "[" + optional char + "]" + " " = 3 or 4 chars
421
+ */
422
+ const shortcutLength = checklistMatch[1] !== undefined ? 4 : 3;
423
+ const remainingHtml = this.extractRemainingHtml(currentInput, shortcutLength);
424
+ const caretOffset = this.getCaretOffset(currentInput) - shortcutLength;
425
+
426
+ const newBlock = BlockManager.replace(currentBlock, 'list', {
427
+ text: remainingHtml,
428
+ style: 'checklist',
429
+ checked: isChecked,
430
+ ...(depth > 0 ? { depth } : {}),
431
+ });
432
+
433
+ this.setCaretAfterConversion(newBlock, caretOffset);
434
+
435
+ return;
436
+ }
437
+
438
+ /**
439
+ * Check for unordered/bulleted list pattern (e.g., "- " or "* ")
440
+ */
441
+ const unorderedMatch = BlockEvents.UNORDERED_LIST_PATTERN.exec(textContent);
442
+
443
+ if (unorderedMatch) {
444
+ /**
445
+ * Extract remaining content (group 1) and calculate shortcut length
446
+ * Shortcut length: "-" or "*" + " " = 2 chars
447
+ */
448
+ const shortcutLength = 2;
449
+ const remainingHtml = this.extractRemainingHtml(currentInput, shortcutLength);
450
+ const caretOffset = this.getCaretOffset(currentInput) - shortcutLength;
451
+
452
+ const newBlock = BlockManager.replace(currentBlock, 'list', {
453
+ text: remainingHtml,
454
+ style: 'unordered',
455
+ checked: false,
456
+ ...(depth > 0 ? { depth } : {}),
457
+ });
458
+
459
+ this.setCaretAfterConversion(newBlock, caretOffset);
460
+
461
+ return;
462
+ }
463
+
464
+ /**
465
+ * Check for ordered list pattern (e.g., "1. " or "1) ")
466
+ */
467
+ const orderedMatch = BlockEvents.ORDERED_LIST_PATTERN.exec(textContent);
468
+
469
+ if (!orderedMatch) {
470
+ return;
471
+ }
472
+
473
+ /**
474
+ * Extract the starting number from the pattern
475
+ */
476
+ const startNumber = parseInt(orderedMatch[1], 10);
477
+
478
+ /**
479
+ * Extract remaining content (group 2) and calculate shortcut length
480
+ * Shortcut length: number digits + "." or ")" + " " = orderedMatch[1].length + 2
481
+ */
482
+ const shortcutLength = orderedMatch[1].length + 2;
483
+ const remainingHtml = this.extractRemainingHtml(currentInput, shortcutLength);
484
+ const caretOffset = this.getCaretOffset(currentInput) - shortcutLength;
485
+
486
+ /**
487
+ * Convert to ordered list with the captured start number
488
+ */
489
+ const listData: { text: string; style: string; checked: boolean; start?: number; depth?: number } = {
490
+ text: remainingHtml,
491
+ style: 'ordered',
492
+ checked: false,
493
+ };
494
+
495
+ // Only include start if it's not 1 (the default)
496
+ if (startNumber !== 1) {
497
+ listData.start = startNumber;
498
+ }
499
+
500
+ // Preserve depth if the block was previously nested
501
+ if (depth > 0) {
502
+ listData.depth = depth;
503
+ }
504
+
505
+ const newBlock = BlockManager.replace(currentBlock, 'list', listData);
506
+
507
+ this.setCaretAfterConversion(newBlock, caretOffset);
508
+ }
509
+
510
+ /**
511
+ * Extract HTML content after a shortcut prefix
512
+ * @param input - the input element
513
+ * @param shortcutLength - length of the shortcut in text characters
514
+ * @returns HTML string with the content after the shortcut
515
+ */
516
+ private extractRemainingHtml(input: HTMLElement, shortcutLength: number): string {
517
+ const innerHTML = input.innerHTML || '';
518
+
519
+ /**
520
+ * Create a temporary element to manipulate the HTML
521
+ */
522
+ const temp = document.createElement('div');
523
+
524
+ temp.innerHTML = innerHTML;
525
+
526
+ /**
527
+ * Walk through text nodes and collect nodes to modify
528
+ */
529
+ const walker = document.createTreeWalker(temp, NodeFilter.SHOW_TEXT, null);
530
+ const nodesToModify = this.collectNodesToModify(walker, shortcutLength);
531
+
532
+ /**
533
+ * Apply modifications
534
+ */
535
+ for (const { node, removeCount } of nodesToModify) {
536
+ const text = node.textContent || '';
537
+
538
+ if (removeCount >= text.length) {
539
+ node.remove();
540
+ } else {
541
+ node.textContent = text.slice(removeCount);
542
+ }
543
+ }
544
+
545
+ return temp.innerHTML;
546
+ }
547
+
548
+ /**
549
+ * Collect text nodes that need modification to remove shortcut characters
550
+ * @param walker - TreeWalker for text nodes
551
+ * @param charsToRemove - total characters to remove
552
+ * @returns array of nodes with their removal counts
553
+ */
554
+ private collectNodesToModify(
555
+ walker: TreeWalker,
556
+ charsToRemove: number
557
+ ): Array<{ node: Text; removeCount: number }> {
558
+ const result: Array<{ node: Text; removeCount: number }> = [];
559
+
560
+ if (charsToRemove <= 0 || !walker.nextNode()) {
561
+ return result;
562
+ }
563
+
564
+ const textNode = walker.currentNode as Text;
565
+ const nodeLength = textNode.textContent?.length || 0;
566
+
567
+ if (nodeLength <= charsToRemove) {
568
+ result.push({ node: textNode, removeCount: nodeLength });
569
+
570
+ return result.concat(this.collectNodesToModify(walker, charsToRemove - nodeLength));
571
+ }
572
+
573
+ result.push({ node: textNode, removeCount: charsToRemove });
574
+
575
+ return result;
576
+ }
577
+
578
+ /**
579
+ * Get the current caret offset within the input element
580
+ * @param input - the input element
581
+ * @returns offset in text characters from the start
582
+ */
583
+ private getCaretOffset(input: HTMLElement): number {
584
+ const selection = window.getSelection();
585
+
586
+ if (!selection || selection.rangeCount === 0) {
587
+ return 0;
588
+ }
589
+
590
+ const range = selection.getRangeAt(0);
591
+
592
+ /**
593
+ * Create a range from start of input to current caret position
594
+ */
595
+ const preCaretRange = document.createRange();
596
+
597
+ preCaretRange.selectNodeContents(input);
598
+ preCaretRange.setEnd(range.startContainer, range.startOffset);
599
+
600
+ /**
601
+ * Get the text length up to the caret
602
+ */
603
+ return preCaretRange.toString().length;
604
+ }
605
+
606
+ /**
607
+ * Set caret position in the new block after conversion
608
+ * @param block - the new block
609
+ * @param offset - desired caret offset in text characters
610
+ */
611
+ private setCaretAfterConversion(block: Block, offset: number): void {
612
+ const { Caret } = this.Blok;
613
+
614
+ /**
615
+ * If offset is 0 or negative, set to start
616
+ */
617
+ if (offset <= 0) {
618
+ Caret.setToBlock(block, Caret.positions.START);
619
+
620
+ return;
621
+ }
622
+
623
+ /**
624
+ * Set caret to the specific offset
625
+ */
626
+ Caret.setToBlock(block, Caret.positions.DEFAULT, offset);
627
+ }
628
+
629
+ /**
630
+ * Copying selected blocks
631
+ * Before putting to the clipboard we sanitize all blocks and then copy to the clipboard
632
+ * @param {ClipboardEvent} event - clipboard event
633
+ */
634
+ public handleCommandC(event: ClipboardEvent): void {
635
+ const { BlockSelection } = this.Blok;
636
+
637
+ if (!BlockSelection.anyBlockSelected) {
638
+ return;
639
+ }
640
+
641
+ // Copy Selected Blocks
642
+ void BlockSelection.copySelectedBlocks(event);
643
+ }
644
+
645
+ /**
646
+ * Copy and Delete selected Blocks
647
+ * @param {ClipboardEvent} event - clipboard event
648
+ */
649
+ public handleCommandX(event: ClipboardEvent): void {
650
+ const { BlockSelection, BlockManager, Caret } = this.Blok;
651
+
652
+ if (!BlockSelection.anyBlockSelected) {
653
+ return;
654
+ }
655
+
656
+ BlockSelection.copySelectedBlocks(event).then(() => {
657
+ const selectionPositionIndex = BlockManager.removeSelectedBlocks();
658
+
659
+ /**
660
+ * Insert default block in place of removed ones
661
+ */
662
+ if (selectionPositionIndex !== undefined) {
663
+ const insertedBlock = BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true);
664
+
665
+ Caret.setToBlock(insertedBlock, Caret.positions.START);
666
+ }
667
+
668
+ /** Clear selection */
669
+ BlockSelection.clearSelection(event);
670
+ })
671
+ .catch(() => {
672
+ // Handle copy operation failure silently
673
+ });
674
+ }
675
+
676
+ /**
677
+ * Tab pressed inside a Block.
678
+ * @param {KeyboardEvent} event - keydown
679
+ */
680
+ private tabPressed(event: KeyboardEvent): void {
681
+ const { InlineToolbar, Caret } = this.Blok;
682
+
683
+ const isFlipperActivated = InlineToolbar.opened;
684
+
685
+ if (isFlipperActivated) {
686
+ return;
687
+ }
688
+
689
+ const isNavigated = event.shiftKey ? Caret.navigatePrevious(true) : Caret.navigateNext(true);
690
+
691
+ /**
692
+ * If we have next Block/input to focus, then focus it. Otherwise, leave native Tab behaviour
693
+ */
694
+ if (isNavigated) {
695
+ event.preventDefault();
696
+ }
697
+ }
698
+
699
+ /**
700
+ * '/' + 'command' keydown inside a Block
701
+ */
702
+ private commandSlashPressed(): void {
703
+ if (this.Blok.BlockSelection.selectedBlocks.length > 1) {
704
+ return;
705
+ }
706
+
707
+ this.activateBlockSettings();
708
+ }
709
+
710
+ /**
711
+ * '/' keydown inside a Block
712
+ * @param event - keydown
713
+ */
714
+ private slashPressed(event: KeyboardEvent): void {
715
+ const wasEventTriggeredInsideBlok = this.Blok.UI.nodes.wrapper.contains(event.target as Node);
716
+
717
+ if (!wasEventTriggeredInsideBlok) {
718
+ return;
719
+ }
720
+
721
+ const currentBlock = this.Blok.BlockManager.currentBlock;
722
+ const canOpenToolbox = currentBlock?.isEmpty;
723
+
724
+ /**
725
+ * @todo Handle case when slash pressed when several blocks are selected
726
+ */
727
+
728
+ /**
729
+ * Toolbox will be opened only if Block is empty
730
+ */
731
+ if (!canOpenToolbox) {
732
+ return;
733
+ }
734
+
735
+ /**
736
+ * The Toolbox will be opened with immediate focus on the Search input,
737
+ * and '/' will be added in the search input by default — we need to prevent it and add '/' manually
738
+ */
739
+ event.preventDefault();
740
+ this.Blok.Caret.insertContentAtCaretPosition('/');
741
+
742
+ this.activateToolbox();
743
+ }
744
+
745
+ /**
746
+ * ENTER pressed on block
747
+ * @param {KeyboardEvent} event - keydown
748
+ */
749
+ private enter(event: KeyboardEvent): void {
750
+ const { BlockManager, UI } = this.Blok;
751
+ const currentBlock = BlockManager.currentBlock;
752
+
753
+ if (currentBlock === undefined) {
754
+ return;
755
+ }
756
+
757
+ /**
758
+ * Don't handle Enter keydowns when Tool sets enableLineBreaks to true.
759
+ * Uses for Tools like <code> where line breaks should be handled by default behaviour.
760
+ */
761
+ if (currentBlock.tool.isLineBreaksEnabled) {
762
+ return;
763
+ }
764
+
765
+ /**
766
+ * Opened Toolbars uses Flipper with own Enter handling
767
+ * Allow split block when no one button in Flipper is focused
768
+ */
769
+ if (UI.someToolbarOpened && UI.someFlipperButtonFocused) {
770
+ return;
771
+ }
772
+
773
+ /**
774
+ * Allow to create line breaks by Shift+Enter
775
+ *
776
+ * Note. On iOS devices, Safari automatically treats enter after a period+space (". |") as Shift+Enter
777
+ * (it used for capitalizing of the first letter of the next sentence)
778
+ * We don't need to lead soft line break in this case — new block should be created
779
+ */
780
+ if (event.shiftKey && !isIosDevice) {
781
+ return;
782
+ }
783
+
784
+ /**
785
+ * If enter has been pressed at the start of the text, just insert paragraph Block above
786
+ */
787
+ const blockToFocus = (() => {
788
+ if (currentBlock.currentInput !== undefined && isCaretAtStartOfInput(currentBlock.currentInput) && !currentBlock.hasMedia) {
789
+ this.Blok.BlockManager.insertDefaultBlockAtIndex(this.Blok.BlockManager.currentBlockIndex);
790
+
791
+ return currentBlock;
792
+ }
793
+
794
+ /**
795
+ * If caret is at very end of the block, just append the new block without splitting
796
+ * to prevent unnecessary dom mutation observing
797
+ */
798
+ if (currentBlock.currentInput && isCaretAtEndOfInput(currentBlock.currentInput)) {
799
+ return this.Blok.BlockManager.insertDefaultBlockAtIndex(this.Blok.BlockManager.currentBlockIndex + 1);
800
+ }
801
+
802
+ /**
803
+ * Split the Current Block into two blocks
804
+ * Renew local current node after split
805
+ */
806
+ return this.Blok.BlockManager.split();
807
+ })();
808
+
809
+ this.Blok.Caret.setToBlock(blockToFocus);
810
+
811
+ /**
812
+ * Show Toolbar
813
+ */
814
+ this.Blok.Toolbar.moveAndOpen(blockToFocus);
815
+
816
+ event.preventDefault();
817
+ }
818
+
819
+ /**
820
+ * Handle backspace keydown on Block
821
+ * @param {KeyboardEvent} event - keydown
822
+ */
823
+ private backspace(event: KeyboardEvent): void {
824
+ const { BlockManager, Caret } = this.Blok;
825
+ const { currentBlock, previousBlock } = BlockManager;
826
+
827
+ if (currentBlock === undefined) {
828
+ return;
829
+ }
830
+
831
+ /**
832
+ * If some fragment is selected, leave native behaviour
833
+ */
834
+ if (!SelectionUtils.isCollapsed) {
835
+ return;
836
+ }
837
+
838
+ /**
839
+ * If caret is not at the start, leave native behaviour
840
+ */
841
+ if (!currentBlock.currentInput || !isCaretAtStartOfInput(currentBlock.currentInput)) {
842
+ return;
843
+ }
844
+
845
+ /**
846
+ * All the cases below have custom behaviour, so we don't need a native one
847
+ */
848
+ event.preventDefault();
849
+ this.Blok.Toolbar.close();
850
+
851
+ const isFirstInputFocused = currentBlock.currentInput === currentBlock.firstInput;
852
+
853
+ /**
854
+ * For example, caret at the start of the Quote second input (caption) — just navigate previous input
855
+ */
856
+ if (!isFirstInputFocused) {
857
+ Caret.navigatePrevious();
858
+
859
+ return;
860
+ }
861
+
862
+ /**
863
+ * Backspace at the start of the first Block should do nothing
864
+ */
865
+ if (previousBlock === null) {
866
+ return;
867
+ }
868
+
869
+ /**
870
+ * If prev Block is empty, it should be removed just like a character
871
+ */
872
+ if (previousBlock.isEmpty) {
873
+ void BlockManager.removeBlock(previousBlock);
874
+
875
+ return;
876
+ }
877
+
878
+ /**
879
+ * If current Block is empty, just remove it and set cursor to the previous Block (like we're removing line break char)
880
+ */
881
+ if (currentBlock.isEmpty) {
882
+ void BlockManager.removeBlock(currentBlock);
883
+
884
+ const newCurrentBlock = BlockManager.currentBlock;
885
+
886
+ newCurrentBlock && Caret.setToBlock(newCurrentBlock, Caret.positions.END);
887
+
888
+ return;
889
+ }
890
+
891
+ const bothBlocksMergeable = areBlocksMergeable(previousBlock, currentBlock);
892
+
893
+ /**
894
+ * If Blocks could be merged, do it
895
+ * Otherwise, just navigate previous block
896
+ */
897
+ if (bothBlocksMergeable) {
898
+ this.mergeBlocks(previousBlock, currentBlock);
899
+ } else {
900
+ Caret.setToBlock(previousBlock, Caret.positions.END);
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Handles delete keydown on Block
906
+ * Removes char after the caret.
907
+ * If caret is at the end of the block, merge next block with current
908
+ * @param {KeyboardEvent} event - keydown
909
+ */
910
+ private delete(event: KeyboardEvent): void {
911
+ const { BlockManager, Caret } = this.Blok;
912
+ const { currentBlock, nextBlock } = BlockManager;
913
+
914
+ if (currentBlock === undefined) {
915
+ return;
916
+ }
917
+
918
+ /**
919
+ * If some fragment is selected, leave native behaviour
920
+ */
921
+ if (!SelectionUtils.isCollapsed) {
922
+ return;
923
+ }
924
+
925
+ /**
926
+ * If caret is not at the end, leave native behaviour
927
+ */
928
+ if (!currentBlock.currentInput || !isCaretAtEndOfInput(currentBlock.currentInput)) {
929
+ return;
930
+ }
931
+
932
+ /**
933
+ * All the cases below have custom behaviour, so we don't need a native one
934
+ */
935
+ event.preventDefault();
936
+ this.Blok.Toolbar.close();
937
+
938
+ const isLastInputFocused = currentBlock.currentInput === currentBlock.lastInput;
939
+
940
+ /**
941
+ * For example, caret at the end of the Quote first input (quote text) — just navigate next input (caption)
942
+ */
943
+ if (!isLastInputFocused) {
944
+ Caret.navigateNext();
945
+
946
+ return;
947
+ }
948
+
949
+ /**
950
+ * Delete at the end of the last Block should do nothing
951
+ */
952
+ if (nextBlock === null) {
953
+ return;
954
+ }
955
+
956
+ /**
957
+ * If next Block is empty, it should be removed just like a character
958
+ */
959
+ if (nextBlock.isEmpty) {
960
+ void BlockManager.removeBlock(nextBlock);
961
+
962
+ return;
963
+ }
964
+
965
+ /**
966
+ * If current Block is empty, just remove it and set cursor to the next Block (like we're removing line break char)
967
+ */
968
+ if (currentBlock.isEmpty) {
969
+ void BlockManager.removeBlock(currentBlock);
970
+
971
+ Caret.setToBlock(nextBlock, Caret.positions.START);
972
+
973
+ return;
974
+ }
975
+
976
+ const bothBlocksMergeable = areBlocksMergeable(currentBlock, nextBlock);
977
+
978
+ /**
979
+ * If Blocks could be merged, do it
980
+ * Otherwise, just navigate to the next block
981
+ */
982
+ if (bothBlocksMergeable) {
983
+ this.mergeBlocks(currentBlock, nextBlock);
984
+ } else {
985
+ Caret.setToBlock(nextBlock, Caret.positions.START);
986
+ }
987
+ }
988
+
989
+ /**
990
+ * Merge passed Blocks
991
+ * @param targetBlock - to which Block we want to merge
992
+ * @param blockToMerge - what Block we want to merge
993
+ */
994
+ private mergeBlocks(targetBlock: Block, blockToMerge: Block): void {
995
+ const { BlockManager, Toolbar } = this.Blok;
996
+
997
+ if (targetBlock.lastInput === undefined) {
998
+ return;
999
+ }
1000
+
1001
+ focus(targetBlock.lastInput, false);
1002
+
1003
+ BlockManager
1004
+ .mergeBlocks(targetBlock, blockToMerge)
1005
+ .then(() => {
1006
+ Toolbar.close();
1007
+ })
1008
+ .catch(() => {
1009
+ // Error handling for mergeBlocks
1010
+ });
1011
+ }
1012
+
1013
+ /**
1014
+ * Handle right and down keyboard keys
1015
+ * @param {KeyboardEvent} event - keyboard event
1016
+ */
1017
+ private arrowRightAndDown(event: KeyboardEvent): void {
1018
+ const keyCode = this.getKeyCode(event);
1019
+
1020
+ if (keyCode === null) {
1021
+ return;
1022
+ }
1023
+
1024
+ /**
1025
+ * Skip handling if this is a block movement shortcut (Cmd/Ctrl+Shift+Down)
1026
+ * Let the shortcut system handle it instead
1027
+ */
1028
+ if (isBlockMovementShortcut(event, 'down')) {
1029
+ return;
1030
+ }
1031
+
1032
+ const isFlipperCombination = Flipper.usedKeys.includes(keyCode) &&
1033
+ (!event.shiftKey || keyCode === keyCodes.TAB);
1034
+
1035
+ /**
1036
+ * Arrows might be handled on toolbars by flipper
1037
+ * Check for Flipper.usedKeys to allow navigate by DOWN and disallow by RIGHT
1038
+ */
1039
+ if (this.Blok.UI.someToolbarOpened && isFlipperCombination) {
1040
+ return;
1041
+ }
1042
+
1043
+ /**
1044
+ * Close Toolbar when user moves cursor, but keep toolbars open if the user
1045
+ * is extending selection with the Shift key so inline interactions remain available.
1046
+ */
1047
+ if (!event.shiftKey) {
1048
+ this.Blok.Toolbar.close();
1049
+ this.Blok.InlineToolbar.close();
1050
+ }
1051
+
1052
+ const selection = SelectionUtils.get();
1053
+
1054
+ if (selection?.anchorNode && !this.Blok.BlockSelection.anyBlockSelected) {
1055
+ this.Blok.BlockManager.setCurrentBlockByChildNode(selection.anchorNode);
1056
+ }
1057
+
1058
+ const { currentBlock } = this.Blok.BlockManager;
1059
+ const eventTarget = event.target as HTMLElement | null;
1060
+ const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1061
+ const fallbackInputCandidates: Array<HTMLElement | undefined | null> = [
1062
+ currentBlock?.inputs.find((input) => eventTarget !== null && input.contains(eventTarget)),
1063
+ currentBlock?.inputs.find((input) => activeElement !== null && input.contains(activeElement)),
1064
+ eventTarget?.closest(EDITABLE_INPUT_SELECTOR) as HTMLElement | null,
1065
+ activeElement?.closest(EDITABLE_INPUT_SELECTOR) as HTMLElement | null,
1066
+ ];
1067
+ const caretInput = currentBlock?.currentInput ?? fallbackInputCandidates.find((candidate): candidate is HTMLElement => {
1068
+ return candidate instanceof HTMLElement;
1069
+ });
1070
+ const caretAtEnd = caretInput !== undefined ? isCaretAtEndOfInput(caretInput) : undefined;
1071
+ const shouldEnableCBS = caretAtEnd || this.Blok.BlockSelection.anyBlockSelected;
1072
+
1073
+ const isShiftDownKey = event.shiftKey && keyCode === keyCodes.DOWN;
1074
+
1075
+ if (isShiftDownKey && shouldEnableCBS) {
1076
+ this.Blok.CrossBlockSelection.toggleBlockSelectedState();
1077
+
1078
+ return;
1079
+ }
1080
+
1081
+ if (isShiftDownKey) {
1082
+ void this.Blok.InlineToolbar.tryToShow();
1083
+ }
1084
+
1085
+ const isPlainRightKey = keyCode === keyCodes.RIGHT && !event.shiftKey && !this.isRtl;
1086
+
1087
+ const nbpsTarget = isPlainRightKey && caretInput instanceof HTMLElement
1088
+ ? findNbspAfterEmptyInline(caretInput)
1089
+ : null;
1090
+
1091
+ if (nbpsTarget !== null) {
1092
+ SelectionUtils.setCursor(nbpsTarget.node as unknown as HTMLElement, nbpsTarget.offset);
1093
+ event.preventDefault();
1094
+
1095
+ return;
1096
+ }
1097
+
1098
+ /**
1099
+ * Determine navigation type based on key pressed:
1100
+ * - Arrow Down: use vertical navigation (Notion-style line-by-line)
1101
+ * - Arrow Right: use horizontal navigation (character-by-character)
1102
+ */
1103
+ const isDownKey = keyCode === keyCodes.DOWN;
1104
+ const isRightKey = keyCode === keyCodes.RIGHT && !this.isRtl;
1105
+
1106
+ const isNavigated = (() => {
1107
+ if (isDownKey) {
1108
+ /**
1109
+ * Arrow Down: Notion-style vertical navigation
1110
+ * Only navigate to next block when caret is at the last line
1111
+ */
1112
+ return this.Blok.Caret.navigateVerticalNext();
1113
+ }
1114
+
1115
+ if (isRightKey) {
1116
+ /**
1117
+ * Arrow Right: horizontal navigation
1118
+ * Navigate to next block when caret is at the end of input
1119
+ */
1120
+ return this.Blok.Caret.navigateNext();
1121
+ }
1122
+
1123
+ return false;
1124
+ })();
1125
+
1126
+ if (isNavigated) {
1127
+ /**
1128
+ * Default behaviour moves cursor by 1 character, we need to prevent it
1129
+ */
1130
+ event.preventDefault();
1131
+
1132
+ return;
1133
+ }
1134
+
1135
+ /**
1136
+ * After caret is set, update Block input index
1137
+ */
1138
+ delay(() => {
1139
+ /** Check currentBlock for case when user moves selection out of Blok */
1140
+ if (this.Blok.BlockManager.currentBlock) {
1141
+ this.Blok.BlockManager.currentBlock.updateCurrentInput();
1142
+ }
1143
+
1144
+ }, 20)();
1145
+
1146
+ /**
1147
+ * Clear blocks selection by arrows
1148
+ */
1149
+ this.Blok.BlockSelection.clearSelection(event);
1150
+ }
1151
+
1152
+ /**
1153
+ * Handle left and up keyboard keys
1154
+ * @param {KeyboardEvent} event - keyboard event
1155
+ */
1156
+ private arrowLeftAndUp(event: KeyboardEvent): void {
1157
+ /**
1158
+ * Arrows might be handled on toolbars by flipper
1159
+ * Check for Flipper.usedKeys to allow navigate by UP and disallow by LEFT
1160
+ */
1161
+ const toolbarOpened = this.Blok.UI.someToolbarOpened;
1162
+
1163
+ const keyCode = this.getKeyCode(event);
1164
+
1165
+ if (keyCode === null) {
1166
+ return;
1167
+ }
1168
+
1169
+ /**
1170
+ * Skip handling if this is a block movement shortcut (Cmd/Ctrl+Shift+Up)
1171
+ * Let the shortcut system handle it instead
1172
+ */
1173
+ if (isBlockMovementShortcut(event, 'up')) {
1174
+ return;
1175
+ }
1176
+
1177
+ if (toolbarOpened && Flipper.usedKeys.includes(keyCode) && (!event.shiftKey || keyCode === keyCodes.TAB)) {
1178
+ return;
1179
+ }
1180
+
1181
+ if (toolbarOpened) {
1182
+ this.Blok.UI.closeAllToolbars();
1183
+ }
1184
+
1185
+ /**
1186
+ * Close Toolbar when user moves cursor, but preserve it for Shift-based selection changes.
1187
+ */
1188
+ if (!event.shiftKey) {
1189
+ this.Blok.Toolbar.close();
1190
+ this.Blok.InlineToolbar.close();
1191
+ }
1192
+
1193
+ const selection = window.getSelection();
1194
+
1195
+ if (selection?.anchorNode && !this.Blok.BlockSelection.anyBlockSelected) {
1196
+ this.Blok.BlockManager.setCurrentBlockByChildNode(selection.anchorNode);
1197
+ }
1198
+
1199
+ const { currentBlock } = this.Blok.BlockManager;
1200
+ const eventTarget = event.target as HTMLElement | null;
1201
+ const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1202
+ const fallbackInputCandidates: Array<HTMLElement | undefined | null> = [
1203
+ currentBlock?.inputs.find((input) => eventTarget !== null && input.contains(eventTarget)),
1204
+ currentBlock?.inputs.find((input) => activeElement !== null && input.contains(activeElement)),
1205
+ eventTarget?.closest(EDITABLE_INPUT_SELECTOR) as HTMLElement | null,
1206
+ activeElement?.closest(EDITABLE_INPUT_SELECTOR) as HTMLElement | null,
1207
+ ];
1208
+ const caretInput = currentBlock?.currentInput ?? fallbackInputCandidates.find((candidate): candidate is HTMLElement => {
1209
+ return candidate instanceof HTMLElement;
1210
+ });
1211
+ const caretAtStart = caretInput !== undefined ? isCaretAtStartOfInput(caretInput) : undefined;
1212
+ const shouldEnableCBS = caretAtStart || this.Blok.BlockSelection.anyBlockSelected;
1213
+
1214
+ const isShiftUpKey = event.shiftKey && keyCode === keyCodes.UP;
1215
+
1216
+ if (isShiftUpKey && shouldEnableCBS) {
1217
+ this.Blok.CrossBlockSelection.toggleBlockSelectedState(false);
1218
+
1219
+ return;
1220
+ }
1221
+
1222
+ if (isShiftUpKey) {
1223
+ void this.Blok.InlineToolbar.tryToShow();
1224
+ }
1225
+
1226
+ /**
1227
+ * Determine navigation type based on key pressed:
1228
+ * - Arrow Up: use vertical navigation (Notion-style line-by-line)
1229
+ * - Arrow Left: use horizontal navigation (character-by-character)
1230
+ */
1231
+ const isUpKey = keyCode === keyCodes.UP;
1232
+ const isLeftKey = keyCode === keyCodes.LEFT && !this.isRtl;
1233
+
1234
+ const isNavigated = (() => {
1235
+ if (isUpKey) {
1236
+ /**
1237
+ * Arrow Up: Notion-style vertical navigation
1238
+ * Only navigate to previous block when caret is at the first line
1239
+ */
1240
+ return this.Blok.Caret.navigateVerticalPrevious();
1241
+ }
1242
+
1243
+ if (isLeftKey) {
1244
+ /**
1245
+ * Arrow Left: horizontal navigation
1246
+ * Navigate to previous block when caret is at the start of input
1247
+ */
1248
+ return this.Blok.Caret.navigatePrevious();
1249
+ }
1250
+
1251
+ return false;
1252
+ })();
1253
+
1254
+ if (isNavigated) {
1255
+ /**
1256
+ * Default behaviour moves cursor by 1 character, we need to prevent it
1257
+ */
1258
+ event.preventDefault();
1259
+
1260
+ return;
1261
+ }
1262
+
1263
+ /**
1264
+ * After caret is set, update Block input index
1265
+ */
1266
+ delay(() => {
1267
+ /** Check currentBlock for case when user ends selection out of Blok and then press arrow-key */
1268
+ if (this.Blok.BlockManager.currentBlock) {
1269
+ this.Blok.BlockManager.currentBlock.updateCurrentInput();
1270
+ }
1271
+
1272
+ }, 20)();
1273
+
1274
+ /**
1275
+ * Clear blocks selection by arrows
1276
+ */
1277
+ this.Blok.BlockSelection.clearSelection(event);
1278
+ }
1279
+
1280
+ /**
1281
+ * Cases when we need to close Toolbar
1282
+ * @param {KeyboardEvent} event - keyboard event
1283
+ */
1284
+ private needToolbarClosing(event: KeyboardEvent): boolean {
1285
+ const keyCode = this.getKeyCode(event);
1286
+ const isEnter = keyCode === keyCodes.ENTER;
1287
+ const isTab = keyCode === keyCodes.TAB;
1288
+ const toolboxItemSelected = (isEnter && this.Blok.Toolbar.toolbox.opened);
1289
+ const blockSettingsItemSelected = (isEnter && this.Blok.BlockSettings.opened);
1290
+ const inlineToolbarItemSelected = (isEnter && this.Blok.InlineToolbar.opened);
1291
+ const flippingToolbarItems = isTab;
1292
+
1293
+ /**
1294
+ * Do not close Toolbar in cases:
1295
+ * 1. ShiftKey pressed (or combination with shiftKey)
1296
+ * 2. When Toolbar is opened and Tab leafs its Tools
1297
+ * 3. When Toolbar's component is opened and some its item selected
1298
+ */
1299
+ return !(event.shiftKey ||
1300
+ flippingToolbarItems ||
1301
+ toolboxItemSelected ||
1302
+ blockSettingsItemSelected ||
1303
+ inlineToolbarItemSelected
1304
+ );
1305
+ }
1306
+
1307
+ /**
1308
+ * If Toolbox is not open, then just open it and show plus button
1309
+ */
1310
+ private activateToolbox(): void {
1311
+ if (!this.Blok.Toolbar.opened) {
1312
+ this.Blok.Toolbar.moveAndOpen();
1313
+ } // else Flipper will leaf through it
1314
+
1315
+ this.Blok.Toolbar.toolbox.open();
1316
+ }
1317
+
1318
+ /**
1319
+ * Open Toolbar and show BlockSettings before flipping Tools
1320
+ */
1321
+ private activateBlockSettings(): void {
1322
+ if (!this.Blok.Toolbar.opened) {
1323
+ this.Blok.Toolbar.moveAndOpen();
1324
+ }
1325
+
1326
+ /**
1327
+ * If BlockSettings is not open, then open BlockSettings
1328
+ * Next Tab press will leaf Settings Buttons
1329
+ */
1330
+ if (!this.Blok.BlockSettings.opened) {
1331
+ /**
1332
+ * @todo Debug the case when we set caret to some block, hovering another block
1333
+ * — wrong settings will be opened.
1334
+ * To fix it, we should refactor the Block Settings module — make it a standalone class, like the Toolbox
1335
+ */
1336
+ void Promise
1337
+ .resolve(this.Blok.BlockSettings.open())
1338
+ .catch(() => {
1339
+ // Error handling for BlockSettings.open
1340
+ });
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * Convert KeyboardEvent.key or code to the legacy numeric keyCode
1346
+ * @param event - keyboard event
1347
+ */
1348
+ private getKeyCode(event: KeyboardEvent): number | null {
1349
+ const keyFromEvent = event.key && KEYBOARD_EVENT_KEY_TO_KEY_CODE_MAP[event.key];
1350
+
1351
+ if (keyFromEvent !== undefined && typeof keyFromEvent === 'number') {
1352
+ return keyFromEvent;
1353
+ }
1354
+
1355
+ const codeFromEvent = event.code && KEYBOARD_EVENT_KEY_TO_KEY_CODE_MAP[event.code];
1356
+
1357
+ if (codeFromEvent !== undefined && typeof codeFromEvent === 'number') {
1358
+ return codeFromEvent;
1359
+ }
1360
+
1361
+ return null;
1362
+ }
1363
+
1364
+ /**
1365
+ * Detect whether KeyDown should be treated as printable input
1366
+ * @param event - keyboard event
1367
+ */
1368
+ private isPrintableKeyEvent(event: KeyboardEvent): boolean {
1369
+ if (!event.key) {
1370
+ return false;
1371
+ }
1372
+
1373
+ return event.key.length === 1 || PRINTABLE_SPECIAL_KEYS.has(event.key);
1374
+ }
1375
+ }