@jackuait/blok 0.4.1-beta.4 → 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-XWGz4gev.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 +26 -225
  149. package/package.json +49 -23
  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 +23 -10
  381. package/types/configs/blok-config.d.ts +29 -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 +6 -5
  399. package/dist/blok-B870U2fw.mjs +0 -25803
  400. package/dist/blok.umd.js +0 -181
@@ -0,0 +1,853 @@
1
+ import { SelectionUtils as Selection } from '../selection';
2
+ import { Module } from '../__module';
3
+ import type { Block } from '../block';
4
+ import { getCaretXPosition, isCaretAtEndOfInput, isCaretAtFirstLine, isCaretAtLastLine, isCaretAtStartOfInput, setCaretAtXPosition } from '../utils/caret';
5
+ import { Dom as $ } from '../dom';
6
+
7
+ const ASCII_MAX_CODE_POINT = 0x7f;
8
+
9
+ /**
10
+ * Determines whether the provided text is comprised only of punctuation and whitespace characters.
11
+ * @param text - text to check
12
+ */
13
+ const isPunctuationOnly = (text: string): boolean => {
14
+ for (const character of text) {
15
+ if (character.trim().length === 0) {
16
+ continue;
17
+ }
18
+
19
+ if (character >= '0' && character <= '9') {
20
+ return false;
21
+ }
22
+
23
+ if (character.toLowerCase() !== character.toUpperCase()) {
24
+ return false;
25
+ }
26
+
27
+ const codePoint = character.codePointAt(0);
28
+
29
+ if (typeof codePoint === 'number' && codePoint > ASCII_MAX_CODE_POINT) {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ return true;
35
+ };
36
+
37
+ const collectTextNodes = (node: Node): Text[] => {
38
+ if (node.nodeType === Node.TEXT_NODE) {
39
+ return [ node as Text ];
40
+ }
41
+
42
+ if (!node.hasChildNodes?.()) {
43
+ return [];
44
+ }
45
+
46
+ return Array.from(node.childNodes).flatMap((child) => collectTextNodes(child));
47
+ };
48
+
49
+ /**
50
+ * Finds last text node suitable for placing caret near the end of the element.
51
+ *
52
+ * Prefers nodes that contain more than just punctuation so caret remains inside formatting nodes
53
+ * whenever possible.
54
+ * @param root - element to search within
55
+ */
56
+ const findLastMeaningfulTextNode = (root: HTMLElement): Text | null => {
57
+ const textNodes = collectTextNodes(root);
58
+
59
+ if (textNodes.length === 0) {
60
+ return null;
61
+ }
62
+
63
+ const lastTextNode = textNodes[textNodes.length - 1];
64
+ const lastMeaningfulNode = [ ...textNodes ]
65
+ .reverse()
66
+ .find((node) => !isPunctuationOnly(node.textContent ?? '')) ?? null;
67
+
68
+ if (
69
+ lastMeaningfulNode &&
70
+ lastMeaningfulNode !== lastTextNode &&
71
+ isPunctuationOnly(lastTextNode.textContent ?? '') &&
72
+ lastMeaningfulNode.parentNode !== root
73
+ ) {
74
+ return lastMeaningfulNode;
75
+ }
76
+
77
+ return lastTextNode;
78
+ };
79
+
80
+ /**
81
+ * Caret
82
+ * Contains methods for working Caret
83
+ * @todo get rid of this module and separate it for utility functions
84
+ */
85
+ export class Caret extends Module {
86
+ /**
87
+ * Allowed caret positions in input
88
+ * @static
89
+ * @returns {{START: string, END: string, DEFAULT: string}}
90
+ */
91
+ public get positions(): { START: string; END: string; DEFAULT: string } {
92
+ return {
93
+ START: 'start',
94
+ END: 'end',
95
+ DEFAULT: 'default',
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Data attributes used by Caret Module
101
+ */
102
+ private static get DATA_ATTR(): { shadowCaret: string } {
103
+ return {
104
+ shadowCaret: 'data-blok-shadow-caret',
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Method gets Block instance and puts caret to the text node with offset
110
+ * There two ways that method applies caret position:
111
+ * - first found text node: sets at the beginning, but you can pass an offset
112
+ * - last found text node: sets at the end of the node. Also, you can customize the behaviour
113
+ * @param {Block} block - Block class
114
+ * @param {string} position - position where to set caret.
115
+ * If default - leave default behaviour and apply offset if it's passed
116
+ * @param {number} offset - caret offset regarding to the block content
117
+ */
118
+ public setToBlock(block: Block, position: string = this.positions.DEFAULT, offset = 0): void {
119
+ const { BlockManager, BlockSelection } = this.Blok;
120
+
121
+ /**
122
+ * Clear previous selection since we possible will select the new Block
123
+ */
124
+ BlockSelection.clearSelection();
125
+
126
+ /**
127
+ * If Block is not focusable, just select (highlight) it
128
+ */
129
+ if (!block.focusable) {
130
+ /**
131
+ * Hide current cursor
132
+ */
133
+ window.getSelection()?.removeAllRanges();
134
+
135
+ /**
136
+ * Highlight Block
137
+ */
138
+ BlockSelection.selectBlock(block);
139
+ BlockManager.currentBlock = block;
140
+
141
+ return;
142
+ }
143
+
144
+ const getElement = (): HTMLElement | undefined => {
145
+ if (position === this.positions.START) {
146
+ return block.firstInput;
147
+ }
148
+
149
+ if (position === this.positions.END) {
150
+ return block.lastInput;
151
+ }
152
+
153
+ return block.currentInput;
154
+ };
155
+
156
+ const element = getElement();
157
+
158
+ if (!element) {
159
+ return;
160
+ }
161
+
162
+ const getNodeAndOffset = (el: HTMLElement): { node: Node | null; offset: number } => {
163
+ if (position === this.positions.START) {
164
+ return {
165
+ node: $.getDeepestNode(el, false),
166
+ offset: 0,
167
+ };
168
+ }
169
+
170
+ if (position === this.positions.END) {
171
+ return this.resolveEndPositionNode(el);
172
+ }
173
+
174
+ const { node, offset: nodeOffset } = $.getNodeByOffset(el, offset);
175
+
176
+ if (node) {
177
+ return {
178
+ node,
179
+ offset: nodeOffset,
180
+ };
181
+ }
182
+
183
+ // case for empty block's input
184
+ return {
185
+ node: $.getDeepestNode(el, false),
186
+ offset: 0,
187
+ };
188
+ };
189
+
190
+ const { node: nodeToSet, offset: offsetToSet } = getNodeAndOffset(element);
191
+
192
+ if (!nodeToSet) {
193
+ return;
194
+ }
195
+
196
+ this.set(nodeToSet as HTMLElement, offsetToSet);
197
+
198
+ BlockManager.setCurrentBlockByChildNode(block.holder);
199
+
200
+ BlockManager.currentBlock!.currentInput = element;
201
+ }
202
+
203
+ /**
204
+ * Calculates the node and offset when caret should be placed near element's end.
205
+ * @param {HTMLElement} el - element to inspect
206
+ */
207
+ private resolveEndPositionNode(el: HTMLElement): { node: Node | null; offset: number } {
208
+ const nodeToSet = $.getDeepestNode(el, true);
209
+
210
+ if (nodeToSet instanceof HTMLElement && $.isNativeInput(nodeToSet)) {
211
+ return {
212
+ node: nodeToSet,
213
+ offset: $.getContentLength(nodeToSet),
214
+ };
215
+ }
216
+
217
+ const meaningfulTextNode = findLastMeaningfulTextNode(el);
218
+
219
+ if (meaningfulTextNode) {
220
+ return {
221
+ node: meaningfulTextNode,
222
+ offset: $.getContentLength(meaningfulTextNode),
223
+ };
224
+ }
225
+
226
+ if (nodeToSet) {
227
+ return {
228
+ node: nodeToSet,
229
+ offset: $.getContentLength(nodeToSet),
230
+ };
231
+ }
232
+
233
+ return {
234
+ node: null,
235
+ offset: 0,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Set caret to the current input of current Block.
241
+ * @param {HTMLElement} input - input where caret should be set
242
+ * @param {string} position - position of the caret.
243
+ * If default - leave default behaviour and apply offset if it's passed
244
+ * @param {number} offset - caret offset regarding to the text node
245
+ */
246
+ public setToInput(input: HTMLElement, position: string = this.positions.DEFAULT, offset = 0): void {
247
+ const { currentBlock } = this.Blok.BlockManager;
248
+
249
+ this.setCaretToInputPosition(input, position, offset);
250
+
251
+ if (currentBlock) {
252
+ currentBlock.currentInput = input;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Sets caret to a block at a specific X position (horizontal coordinate).
258
+ * Used for Notion-style vertical navigation to preserve horizontal caret position.
259
+ * @param block - Block to set caret in
260
+ * @param targetX - Target X coordinate, or null to use start/end position
261
+ * @param atFirstLine - If true, place caret on first line; if false, place on last line
262
+ */
263
+ public setToBlockAtXPosition(block: Block, targetX: number | null, atFirstLine: boolean): void {
264
+ const { BlockManager, BlockSelection } = this.Blok;
265
+
266
+ BlockSelection.clearSelection();
267
+
268
+ if (!block.focusable) {
269
+ window.getSelection()?.removeAllRanges();
270
+ BlockSelection.selectBlock(block);
271
+ BlockManager.currentBlock = block;
272
+
273
+ return;
274
+ }
275
+
276
+ const element = atFirstLine ? block.firstInput : block.lastInput;
277
+
278
+ if (!element) {
279
+ return;
280
+ }
281
+
282
+ if (targetX !== null) {
283
+ setCaretAtXPosition(element, targetX, atFirstLine);
284
+ } else {
285
+ const position = atFirstLine ? this.positions.START : this.positions.END;
286
+
287
+ this.setCaretToInputPosition(element, position, 0);
288
+ }
289
+
290
+ BlockManager.setCurrentBlockByChildNode(block.holder);
291
+ BlockManager.currentBlock!.currentInput = element;
292
+ }
293
+
294
+ /**
295
+ * Sets caret to an input at a specific X position (horizontal coordinate).
296
+ * Used for Notion-style vertical navigation to preserve horizontal caret position.
297
+ * @param input - Input element to set caret in
298
+ * @param targetX - Target X coordinate, or null to use start/end position
299
+ * @param atFirstLine - If true, place caret on first line; if false, place on last line
300
+ */
301
+ public setToInputAtXPosition(input: HTMLElement, targetX: number | null, atFirstLine: boolean): void {
302
+ const { currentBlock } = this.Blok.BlockManager;
303
+
304
+ if (targetX !== null) {
305
+ setCaretAtXPosition(input, targetX, atFirstLine);
306
+ } else {
307
+ const position = atFirstLine ? this.positions.START : this.positions.END;
308
+
309
+ this.setCaretToInputPosition(input, position, 0);
310
+ }
311
+
312
+ if (currentBlock) {
313
+ currentBlock.currentInput = input;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Internal method to handle caret positioning within an input
319
+ * @param input - the input element
320
+ * @param position - position type (START, END, DEFAULT)
321
+ * @param offset - character offset for DEFAULT position
322
+ */
323
+ private setCaretToInputPosition(input: HTMLElement, position: string, offset: number): void {
324
+ if (position === this.positions.START) {
325
+ this.setCaretToStart(input);
326
+
327
+ return;
328
+ }
329
+
330
+ if (position === this.positions.END) {
331
+ this.setCaretToEnd(input);
332
+
333
+ return;
334
+ }
335
+
336
+ // DEFAULT position: use getNodeByOffset to find the correct node for the given offset
337
+ // This properly handles multi-node content and clamps out-of-bounds offsets
338
+ this.setCaretToOffset(input, offset);
339
+ }
340
+
341
+ /**
342
+ * Sets caret to the start of an input
343
+ * @param input - the input element
344
+ */
345
+ private setCaretToStart(input: HTMLElement): void {
346
+ const nodeToSet = $.getDeepestNode(input, false);
347
+
348
+ if (!nodeToSet) {
349
+ return;
350
+ }
351
+
352
+ this.set(nodeToSet as HTMLElement, 0);
353
+ }
354
+
355
+ /**
356
+ * Sets caret to the end of an input
357
+ * @param input - the input element
358
+ */
359
+ private setCaretToEnd(input: HTMLElement): void {
360
+ const nodeToSet = $.getDeepestNode(input, true);
361
+
362
+ if (!nodeToSet) {
363
+ return;
364
+ }
365
+
366
+ this.set(nodeToSet as HTMLElement, $.getContentLength(nodeToSet));
367
+ }
368
+
369
+ /**
370
+ * Sets caret at a specific offset within an input
371
+ * Falls back to end position if offset resolution fails
372
+ * @param input - the input element
373
+ * @param offset - the character offset
374
+ */
375
+ private setCaretToOffset(input: HTMLElement, offset: number): void {
376
+ const { node, offset: nodeOffset } = $.getNodeByOffset(input, offset);
377
+
378
+ if (node) {
379
+ this.set(node as HTMLElement, nodeOffset);
380
+
381
+ return;
382
+ }
383
+
384
+ // Fallback to end of input for empty inputs or when offset resolution fails
385
+ this.setCaretToEnd(input);
386
+ }
387
+
388
+ /**
389
+ * Creates Document Range and sets caret to the element with offset
390
+ * @param {HTMLElement} element - target node.
391
+ * @param {number} offset - offset
392
+ */
393
+ public set(element: HTMLElement, offset = 0): void {
394
+ const scrollOffset = 30;
395
+ const { top, bottom } = Selection.setCursor(element, offset);
396
+ const { innerHeight } = window;
397
+
398
+ /**
399
+ * If new cursor position is not visible, scroll to it
400
+ */
401
+ if (top < 0) {
402
+ window.scrollBy(0, top - scrollOffset);
403
+
404
+ return;
405
+ }
406
+
407
+ if (bottom > innerHeight) {
408
+ window.scrollBy(0, bottom - innerHeight + scrollOffset);
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Set Caret to the last Block
414
+ * If last block is not empty, append another empty block
415
+ */
416
+ public setToTheLastBlock(): void {
417
+ const lastBlock = this.Blok.BlockManager.lastBlock;
418
+
419
+ if (!lastBlock) {
420
+ return;
421
+ }
422
+
423
+ /**
424
+ * If last block is empty and it is an defaultBlock, set to that.
425
+ * Otherwise, append new empty block and set to that
426
+ */
427
+ if (lastBlock.tool.isDefault && lastBlock.isEmpty) {
428
+ this.setToBlock(lastBlock);
429
+ } else {
430
+ const newBlock = this.Blok.BlockManager.insertAtEnd();
431
+
432
+ this.setToBlock(newBlock);
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Extract content fragment of current Block from Caret position to the end of the Block
438
+ */
439
+ public extractFragmentFromCaretPosition(): void | DocumentFragment {
440
+ const selection = Selection.get();
441
+
442
+ if (!selection || !selection.rangeCount) {
443
+ return;
444
+ }
445
+
446
+ const selectRange = selection.getRangeAt(0);
447
+ const currentBlock = this.Blok.BlockManager.currentBlock;
448
+
449
+ if (!currentBlock) {
450
+ return;
451
+ }
452
+
453
+ const currentBlockInput = currentBlock.currentInput;
454
+
455
+ selectRange.deleteContents();
456
+
457
+ if (!currentBlockInput) {
458
+ return;
459
+ }
460
+
461
+ if ($.isNativeInput(currentBlockInput)) {
462
+ /**
463
+ * If input is native text input we need to use it's value
464
+ * Text before the caret stays in the input,
465
+ * while text after the caret is returned as a fragment to be inserted after the block.
466
+ */
467
+ const input = currentBlockInput as HTMLInputElement | HTMLTextAreaElement;
468
+ const newFragment = document.createDocumentFragment();
469
+ const selectionStart = input.selectionStart ?? 0;
470
+
471
+ const inputRemainingText = input.value.substring(0, selectionStart);
472
+ const fragmentText = input.value.substring(selectionStart);
473
+
474
+ newFragment.textContent = fragmentText;
475
+ input.value = inputRemainingText;
476
+
477
+ return newFragment;
478
+ }
479
+
480
+ const range = selectRange.cloneRange();
481
+
482
+ range.selectNodeContents(currentBlockInput);
483
+ range.setStart(selectRange.endContainer, selectRange.endOffset);
484
+
485
+ return range.extractContents();
486
+ }
487
+
488
+ /**
489
+ * Set's caret to the next Block or Tool`s input
490
+ * Before moving caret, we should check if caret position is at the end of Plugins node
491
+ * Using {@link Dom#getDeepestNode} to get a last node and match with current selection
492
+ * @param {boolean} force - pass true to skip check for caret position
493
+ */
494
+ public navigateNext(force = false): boolean {
495
+ const { BlockManager } = this.Blok;
496
+ const { currentBlock, nextBlock } = BlockManager;
497
+
498
+ if (currentBlock === undefined) {
499
+ return false;
500
+ }
501
+
502
+ const { nextInput, currentInput } = currentBlock;
503
+ const isAtEnd = currentInput !== undefined ? isCaretAtEndOfInput(currentInput) : undefined;
504
+
505
+ /**
506
+ * We should jump to the next block if:
507
+ * - 'force' is true (Tab-navigation)
508
+ * - caret is at the end of the current block
509
+ * - block does not contain any inputs (e.g. to allow go next when Delimiter is focused)
510
+ */
511
+ const navigationAllowed = force || isAtEnd || !currentBlock.focusable;
512
+
513
+ /** If next Tool`s input exists, focus on it. Otherwise set caret to the next Block */
514
+ if (nextInput && navigationAllowed) {
515
+ this.setToInput(nextInput, this.positions.START);
516
+
517
+ return true;
518
+ }
519
+
520
+ const getBlockToNavigate = (): Block | null => {
521
+ if (nextBlock !== null) {
522
+ return nextBlock;
523
+ }
524
+
525
+ /**
526
+ * This code allows to exit from the last non-initial tool:
527
+ * https://github.com/codex-team/editor.js/issues/1103
528
+ */
529
+
530
+ /**
531
+ * 1. If there is a last block and it is default, do nothing
532
+ * 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing
533
+ * (https://github.com/codex-team/editor.js/issues/1414)
534
+ */
535
+ if (currentBlock.tool.isDefault || !navigationAllowed) {
536
+ return null;
537
+ }
538
+
539
+ /**
540
+ * If there is no nextBlock, but currentBlock is not default,
541
+ * insert new default block at the end and navigate to it
542
+ */
543
+ return BlockManager.insertAtEnd() as Block;
544
+ };
545
+
546
+ const blockToNavigate = getBlockToNavigate();
547
+
548
+ if (blockToNavigate !== null && navigationAllowed) {
549
+ this.setToBlock(blockToNavigate, this.positions.START);
550
+
551
+ return true;
552
+ }
553
+
554
+ return false;
555
+ }
556
+
557
+ /**
558
+ * Set's caret to the previous Tool`s input or Block
559
+ * Before moving caret, we should check if caret position is start of the Plugins node
560
+ * Using {@link Dom#getDeepestNode} to get a last node and match with current selection
561
+ * @param {boolean} force - pass true to skip check for caret position
562
+ */
563
+ public navigatePrevious(force = false): boolean {
564
+ const { currentBlock, previousBlock } = this.Blok.BlockManager;
565
+
566
+ if (!currentBlock) {
567
+ return false;
568
+ }
569
+
570
+ const { previousInput, currentInput } = currentBlock;
571
+
572
+ /**
573
+ * We should jump to the previous block if:
574
+ * - 'force' is true (Tab-navigation)
575
+ * - caret is at the start of the current block
576
+ * - block does not contain any inputs (e.g. to allow go back when Delimiter is focused)
577
+ */
578
+ const caretAtStart = currentInput !== undefined ? isCaretAtStartOfInput(currentInput) : undefined;
579
+ const navigationAllowed = force || caretAtStart || !currentBlock.focusable;
580
+
581
+ /** If previous Tool`s input exists, focus on it. Otherwise set caret to the previous Block */
582
+ if (previousInput && navigationAllowed) {
583
+ this.setToInput(previousInput, this.positions.END);
584
+
585
+ return true;
586
+ }
587
+
588
+ if (previousBlock !== null && navigationAllowed) {
589
+ this.setToBlock(previousBlock as Block, this.positions.END);
590
+
591
+ return true;
592
+ }
593
+
594
+ return false;
595
+ }
596
+
597
+ /**
598
+ * Helper method to navigate to a target block.
599
+ * If target is focusable, sets caret to it; otherwise selects it.
600
+ * @param targetBlock - Block to navigate to (or null)
601
+ * @param atFirstLine - If true, place caret at first line; if false, at last line
602
+ * @returns {boolean} - true if navigation occurred
603
+ */
604
+ private navigateToBlock(targetBlock: Block | null, atFirstLine: boolean): boolean {
605
+ if (targetBlock === null) {
606
+ return false;
607
+ }
608
+
609
+ const { BlockManager, BlockSelection } = this.Blok;
610
+
611
+ BlockSelection.clearSelection();
612
+
613
+ if (targetBlock.focusable) {
614
+ this.setToBlockAtXPosition(targetBlock, null, atFirstLine);
615
+ } else {
616
+ BlockSelection.selectBlock(targetBlock);
617
+ BlockManager.currentBlock = targetBlock;
618
+ }
619
+
620
+ return true;
621
+ }
622
+
623
+ /**
624
+ * Navigates to the next block using vertical (Arrow Down) navigation.
625
+ * Implements Notion-style behavior: line-by-line within block, then jump to next block.
626
+ * Preserves horizontal caret position when moving between blocks.
627
+ * @returns {boolean} - true if navigation to next block occurred
628
+ */
629
+ public navigateVerticalNext(): boolean {
630
+ const { BlockManager } = this.Blok;
631
+ const { currentBlock, nextBlock } = BlockManager;
632
+
633
+ if (currentBlock === undefined) {
634
+ return false;
635
+ }
636
+
637
+ /**
638
+ * For non-focusable blocks (images, embeds, contentless), navigate to next block
639
+ */
640
+ if (!currentBlock.focusable) {
641
+ return this.navigateToBlock(nextBlock, true);
642
+ }
643
+
644
+ /**
645
+ * For empty blocks, jump immediately to the next block
646
+ */
647
+ if (currentBlock.isEmpty) {
648
+ return this.navigateToBlock(nextBlock, true);
649
+ }
650
+
651
+ const { currentInput } = currentBlock;
652
+
653
+ /**
654
+ * Check if caret is at the last line - if not, let browser handle line navigation
655
+ */
656
+ const isAtLastLine = currentInput !== undefined ? isCaretAtLastLine(currentInput) : true;
657
+
658
+ if (!isAtLastLine) {
659
+ return false;
660
+ }
661
+
662
+ /**
663
+ * Save the current caret X position before navigation
664
+ */
665
+ const caretX = getCaretXPosition();
666
+
667
+ /**
668
+ * Navigate to next input within the block first
669
+ */
670
+ const { nextInput } = currentBlock;
671
+
672
+ if (nextInput) {
673
+ this.setToInputAtXPosition(nextInput, caretX, true);
674
+
675
+ return true;
676
+ }
677
+
678
+ /**
679
+ * Navigate to next block, preserving horizontal position
680
+ */
681
+ if (nextBlock !== null) {
682
+ this.setToBlockAtXPosition(nextBlock, caretX, true);
683
+
684
+ return true;
685
+ }
686
+
687
+ /**
688
+ * At the last block - check if we should create a new block
689
+ */
690
+ const isAtEnd = currentInput !== undefined ? isCaretAtEndOfInput(currentInput) : true;
691
+
692
+ if (!currentBlock.tool.isDefault && isAtEnd) {
693
+ const newBlock = BlockManager.insertAtEnd() as Block;
694
+
695
+ this.setToBlock(newBlock, this.positions.START);
696
+
697
+ return true;
698
+ }
699
+
700
+ return false;
701
+ }
702
+
703
+ /**
704
+ * Navigates to the previous block using vertical (Arrow Up) navigation.
705
+ * Implements Notion-style behavior: line-by-line within block, then jump to previous block.
706
+ * Preserves horizontal caret position when moving between blocks.
707
+ * @returns {boolean} - true if navigation to previous block occurred
708
+ */
709
+ public navigateVerticalPrevious(): boolean {
710
+ const { BlockManager } = this.Blok;
711
+ const { currentBlock, previousBlock } = BlockManager;
712
+
713
+ if (currentBlock === undefined) {
714
+ return false;
715
+ }
716
+
717
+ /**
718
+ * For non-focusable blocks (images, embeds, contentless), navigate to previous block
719
+ */
720
+ if (!currentBlock.focusable) {
721
+ return this.navigateToBlock(previousBlock, false);
722
+ }
723
+
724
+ /**
725
+ * For empty blocks, jump immediately to the previous block
726
+ */
727
+ if (currentBlock.isEmpty) {
728
+ return this.navigateToBlock(previousBlock, false);
729
+ }
730
+
731
+ const { currentInput } = currentBlock;
732
+
733
+ /**
734
+ * Check if caret is at the first line - if not, let browser handle line navigation
735
+ */
736
+ const isAtFirstLine = currentInput !== undefined ? isCaretAtFirstLine(currentInput) : true;
737
+
738
+ if (!isAtFirstLine) {
739
+ return false;
740
+ }
741
+
742
+ /**
743
+ * Save the current caret X position before navigation
744
+ */
745
+ const caretX = getCaretXPosition();
746
+
747
+ /**
748
+ * Navigate to previous input within the block first
749
+ */
750
+ const { previousInput } = currentBlock;
751
+
752
+ if (previousInput) {
753
+ this.setToInputAtXPosition(previousInput, caretX, false);
754
+
755
+ return true;
756
+ }
757
+
758
+ /**
759
+ * Navigate to previous block, preserving horizontal position
760
+ */
761
+ if (previousBlock !== null) {
762
+ this.setToBlockAtXPosition(previousBlock as Block, caretX, false);
763
+
764
+ return true;
765
+ }
766
+
767
+ return false;
768
+ }
769
+
770
+ /**
771
+ * Inserts shadow element after passed element where caret can be placed
772
+ * @param {Element} element - element after which shadow caret should be inserted
773
+ */
774
+ public createShadow(element: Element): void {
775
+ const shadowCaret = document.createElement('span');
776
+
777
+ shadowCaret.setAttribute(Caret.DATA_ATTR.shadowCaret, '');
778
+ shadowCaret.setAttribute('data-blok-testid', 'shadow-caret');
779
+ element.insertAdjacentElement('beforeend', shadowCaret);
780
+ }
781
+
782
+ /**
783
+ * Restores caret position
784
+ * @param {HTMLElement} element - element where caret should be restored
785
+ */
786
+ public restoreCaret(element: HTMLElement): void {
787
+ const shadowCaret = element.querySelector('[data-blok-testid="shadow-caret"]');
788
+
789
+ if (!shadowCaret) {
790
+ return;
791
+ }
792
+
793
+ /**
794
+ * After we set the caret to the required place
795
+ * we need to clear shadow caret
796
+ *
797
+ * - make new range
798
+ * - select shadowed span
799
+ * - use extractContent to remove it from DOM
800
+ */
801
+ const sel = new Selection();
802
+
803
+ sel.expandToTag(shadowCaret as HTMLElement);
804
+
805
+ const newRange = document.createRange();
806
+
807
+ newRange.selectNode(shadowCaret);
808
+ newRange.extractContents();
809
+ }
810
+
811
+ /**
812
+ * Inserts passed content at caret position
813
+ * @param {string} content - content to insert
814
+ */
815
+ public insertContentAtCaretPosition(content: string): void {
816
+ const fragment = document.createDocumentFragment();
817
+ const wrapper = document.createElement('div');
818
+ const selection = Selection.get();
819
+ const range = Selection.range;
820
+
821
+ if (!selection || !range) {
822
+ return;
823
+ }
824
+
825
+ wrapper.innerHTML = content;
826
+
827
+ Array.from(wrapper.childNodes).forEach((child: Node) => fragment.appendChild(child));
828
+
829
+ /**
830
+ * If there is no child node, append empty one
831
+ */
832
+ if (fragment.childNodes.length === 0) {
833
+ fragment.appendChild(new Text());
834
+ }
835
+
836
+ const lastChild = fragment.lastChild as ChildNode;
837
+
838
+ range.deleteContents();
839
+ range.insertNode(fragment);
840
+
841
+ /** Cross-browser caret insertion */
842
+ const newRange = document.createRange();
843
+
844
+ const nodeToSetCaret = lastChild.nodeType === Node.TEXT_NODE ? lastChild : lastChild.firstChild;
845
+
846
+ if (nodeToSetCaret !== null && nodeToSetCaret.textContent !== null) {
847
+ newRange.setStart(nodeToSetCaret, nodeToSetCaret.textContent.length);
848
+ }
849
+
850
+ selection.removeAllRanges();
851
+ selection.addRange(newRange);
852
+ }
853
+ }