@jackuait/blok 0.4.1 → 0.4.3-beta.1

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 (397) hide show
  1. package/README.md +136 -17
  2. package/codemod/README.md +45 -7
  3. package/codemod/migrate-editorjs-to-blok.js +951 -92
  4. package/codemod/test.js +780 -77
  5. package/dist/blok.mjs +5 -2
  6. package/dist/chunks/blok-8ptWuVZC.mjs +12838 -0
  7. package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
  8. package/dist/chunks/i18next-loader-bLawSYRV.mjs +43 -0
  9. package/dist/{index-CBkApZKo.mjs → chunks/index-5nYtWZD2.mjs} +2 -2
  10. package/dist/chunks/inline-tool-convert-CvMDAIzb.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 +60 -15
  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 -24
  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/list.d.ts +25 -18
  392. package/types/tools/tool-settings.d.ts +8 -1
  393. package/types/tools/tool.d.ts +6 -0
  394. package/types/tools-entry.d.ts +49 -0
  395. package/types/utils/popover/popover-item.d.ts +0 -5
  396. package/dist/blok-BwPfU8ro.mjs +0 -21510
  397. package/dist/blok.umd.js +0 -198
@@ -0,0 +1,956 @@
1
+ import { Module } from '../../__module';
2
+ import { Dom as $ } from '../../dom';
3
+ import { SelectionUtils } from '../../selection';
4
+ import { beautifyShortcut, capitalize, isMobileScreen } from '../../utils';
5
+ import type { InlineTool as IInlineTool } from '../../../../types';
6
+ import { Shortcuts } from '../../utils/shortcuts';
7
+ import type { ModuleConfig } from '../../../types-internal/module-config';
8
+ import type { BlokModules } from '../../../types-internal/blok-modules';
9
+ import { CommonInternalSettings } from '../../tools/base';
10
+ import type { Popover, PopoverItemParams } from '../../utils/popover';
11
+ import { PopoverItemType } from '../../utils/popover';
12
+ import { PopoverInline } from '../../utils/popover/popover-inline';
13
+ import type { InlineToolAdapter } from 'src/components/tools/inline';
14
+ import { translateToolName } from '../../utils/tools';
15
+ import { DATA_ATTR, INLINE_TOOLBAR_INTERFACE_VALUE } from '../../constants';
16
+ import { twMerge } from '../../utils/tw';
17
+
18
+ /**
19
+ * Inline Toolbar elements
20
+ */
21
+ interface InlineToolbarNodes {
22
+ /**
23
+ * Wrapper element for the inline toolbar
24
+ */
25
+ wrapper: HTMLElement | undefined;
26
+ }
27
+
28
+ /**
29
+ * Inline toolbar with actions that modifies selected text fragment
30
+ *
31
+ * |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
32
+ * | B i [link] [mark] |
33
+ * |________________________|
34
+ */
35
+ export class InlineToolbar extends Module<InlineToolbarNodes> {
36
+
37
+ /**
38
+ * State of inline toolbar
39
+ */
40
+ public opened = false;
41
+
42
+ /**
43
+ * Returns true if a nested popover (like convert-to dropdown) is currently open
44
+ */
45
+ public get hasNestedPopoverOpen(): boolean {
46
+ const popoverInline = this.popover as PopoverInline | null;
47
+
48
+ if (popoverInline === null) {
49
+ return false;
50
+ }
51
+
52
+ return popoverInline.hasNestedPopoverOpen;
53
+ }
54
+
55
+ /**
56
+ * Closes only the nested popover if one is open.
57
+ * Returns true if a nested popover was closed, false otherwise.
58
+ */
59
+ public closeNestedPopover(): boolean {
60
+ const popoverInline = this.popover as PopoverInline | null;
61
+
62
+ if (popoverInline === null) {
63
+ return false;
64
+ }
65
+
66
+ if (!popoverInline.hasNestedPopoverOpen) {
67
+ return false;
68
+ }
69
+
70
+ popoverInline.closeNestedPopover();
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Returns true if a flipper item is focused (user is navigating with keyboard)
77
+ */
78
+ public get hasFlipperFocus(): boolean {
79
+ const popoverInline = this.popover as PopoverInline | null;
80
+
81
+ if (popoverInline === null) {
82
+ return false;
83
+ }
84
+
85
+ const mainFlipperHasFocus = popoverInline.flipper?.hasFocus() ?? false;
86
+ const nestedPopover = (popoverInline as unknown as { nestedPopover?: { flipper?: { hasFocus(): boolean } } | null }).nestedPopover;
87
+ const nestedFlipperHasFocus = nestedPopover?.flipper?.hasFocus() ?? false;
88
+
89
+ return mainFlipperHasFocus || nestedFlipperHasFocus;
90
+ }
91
+
92
+ /**
93
+ * Popover instance reference
94
+ */
95
+ private popover: Popover | null = null;
96
+
97
+ /**
98
+ * Promise that resolves when the toolbar is fully opened
99
+ * Used to ensure shortcuts wait for popover initialization
100
+ */
101
+ private openingPromise: Promise<void> | null = null;
102
+
103
+ /**
104
+ * Margin above/below the Toolbar
105
+ */
106
+ private readonly toolbarVerticalMargin: number = isMobileScreen() ? 20 : 6;
107
+
108
+ /**
109
+ * Tracks whether inline toolbar DOM and shortcuts are initialized
110
+ */
111
+ private initialized = false;
112
+
113
+ /**
114
+ * Ensures we don't schedule multiple initialization attempts simultaneously
115
+ */
116
+ private initializationScheduled = false;
117
+
118
+ /**
119
+ * Currently visible tools instances
120
+ */
121
+ private tools: Map<InlineToolAdapter, IInlineTool> = new Map();
122
+
123
+ /**
124
+ * Shortcuts registered for inline tools
125
+ */
126
+ private registeredShortcuts: Map<string, string> = new Map();
127
+
128
+ /**
129
+ * Tracks whether inline shortcuts have been registered
130
+ */
131
+ private shortcutsRegistered = false;
132
+
133
+ /**
134
+ * Prevents duplicate shortcut registration retries
135
+ */
136
+ private shortcutRegistrationScheduled = false;
137
+
138
+
139
+ /**
140
+ * @param moduleConfiguration - Module Configuration
141
+ * @param moduleConfiguration.config - Blok's config
142
+ * @param moduleConfiguration.eventsDispatcher - Blok's event dispatcher
143
+ */
144
+ constructor({ config, eventsDispatcher }: ModuleConfig) {
145
+ super({
146
+ config,
147
+ eventsDispatcher,
148
+ });
149
+
150
+ /**
151
+ * Handle arrow key events for inline toolbar
152
+ * - Close toolbar when Up/Down arrow key is pressed without Shift (allows cursor movement)
153
+ * but only if no toolbar item is focused (user hasn't started keyboard navigation via Tab)
154
+ * - Left/Right arrow keys have no effect within the inline toolbar (per accessibility requirements)
155
+ * - Show toolbar when Shift+Arrow is pressed (extends selection)
156
+ *
157
+ * Note: We listen on window with capture=true to ensure this runs before
158
+ * the Flipper's keydown handler which also uses capture phase
159
+ */
160
+ this.listeners.on(window, 'keydown', (event: Event) => {
161
+ const keyboardEvent = event as KeyboardEvent;
162
+ const isVerticalArrowKey = keyboardEvent.key === 'ArrowDown' || keyboardEvent.key === 'ArrowUp';
163
+ const isHorizontalArrowKey = keyboardEvent.key === 'ArrowLeft' || keyboardEvent.key === 'ArrowRight';
164
+
165
+ /**
166
+ * Close inline toolbar when Up/Down arrow key is pressed without Shift
167
+ * This allows the user to move the cursor and collapse the selection
168
+ *
169
+ * However, if the user has already started keyboard navigation within the toolbar
170
+ * (by pressing Tab to focus on a toolbar item), we should allow arrow key navigation
171
+ * within the toolbar instead of closing it.
172
+ *
173
+ * Left/Right arrow keys should have no effect within the inline toolbar,
174
+ * so we don't close the toolbar when they are pressed.
175
+ *
176
+ * We check:
177
+ * 1. If the main popover's Flipper has focus
178
+ * 2. If a nested popover is open (even if no item is focused)
179
+ * 3. If the nested popover's Flipper has focus
180
+ */
181
+ const shouldCheckForClose = isVerticalArrowKey && !keyboardEvent.shiftKey && this.opened;
182
+ const popoverWithFlipper = this.popover as PopoverInline | null;
183
+ const mainFlipperHasFocus = popoverWithFlipper?.flipper?.hasFocus() ?? false;
184
+ const nestedPopover = (this.popover as unknown as { nestedPopover?: { flipper?: { hasFocus(): boolean } } | null } | null)?.nestedPopover;
185
+ const hasNestedPopover = nestedPopover !== null && nestedPopover !== undefined;
186
+ const nestedFlipperHasFocus = nestedPopover?.flipper?.hasFocus() ?? false;
187
+ const shouldKeepOpen = mainFlipperHasFocus || hasNestedPopover || nestedFlipperHasFocus;
188
+
189
+ if (shouldCheckForClose && !shouldKeepOpen) {
190
+ this.close();
191
+
192
+ return;
193
+ }
194
+
195
+ /**
196
+ * When the inline toolbar is open and the flipper has focus,
197
+ * prevent horizontal arrow keys from doing anything (no navigation, no closing)
198
+ */
199
+ if (isHorizontalArrowKey && this.opened && mainFlipperHasFocus) {
200
+ keyboardEvent.preventDefault();
201
+ keyboardEvent.stopPropagation();
202
+
203
+ return;
204
+ }
205
+
206
+ const isShiftArrow = keyboardEvent.shiftKey &&
207
+ (keyboardEvent.key === 'ArrowDown' || keyboardEvent.key === 'ArrowUp');
208
+
209
+ if (!isShiftArrow) {
210
+ return;
211
+ }
212
+
213
+ void this.tryToShow();
214
+ }, true);
215
+
216
+ this.scheduleInitialization();
217
+ this.tryRegisterShortcuts();
218
+ }
219
+
220
+ /**
221
+ * Setter for Blok modules that ensures shortcuts registration is retried once dependencies are available
222
+ */
223
+ public override set state(Blok: BlokModules) {
224
+ super.state = Blok;
225
+ this.tryRegisterShortcuts();
226
+ }
227
+
228
+ /**
229
+ * Ensures toolbar DOM and shortcuts are created
230
+ */
231
+ private initialize(): void {
232
+ if (this.initialized) {
233
+ return;
234
+ }
235
+
236
+ /**
237
+ * Guard against race condition: the deferred callback from scheduleInitialization()
238
+ * can fire before UI module has created its wrapper element.
239
+ * If UI isn't ready yet, reschedule and try again.
240
+ */
241
+ if (this.Blok.UI?.nodes?.wrapper === undefined) {
242
+ this.initializationScheduled = false;
243
+ this.scheduleInitialization();
244
+
245
+ return;
246
+ }
247
+
248
+ this.make();
249
+ this.tryRegisterShortcuts();
250
+ this.initialized = true;
251
+ }
252
+
253
+ /**
254
+ * Attempts to register inline shortcuts as soon as tools are available
255
+ */
256
+ private tryRegisterShortcuts(): void {
257
+ if (this.shortcutsRegistered) {
258
+ return;
259
+ }
260
+
261
+ if (this.Blok?.Tools === undefined) {
262
+ this.scheduleShortcutRegistration();
263
+
264
+ return;
265
+ }
266
+
267
+ const shortcutsWereRegistered = this.registerInitialShortcuts();
268
+
269
+ if (shortcutsWereRegistered) {
270
+ this.shortcutsRegistered = true;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Schedules a retry for shortcut registration
276
+ */
277
+ private scheduleShortcutRegistration(): void {
278
+ if (this.shortcutsRegistered || this.shortcutRegistrationScheduled) {
279
+ return;
280
+ }
281
+
282
+ this.shortcutRegistrationScheduled = true;
283
+
284
+ const callback = (): void => {
285
+ this.shortcutRegistrationScheduled = false;
286
+ this.tryRegisterShortcuts();
287
+ };
288
+
289
+ if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') {
290
+ window.setTimeout(callback, 0);
291
+ } else {
292
+ callback();
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Schedules the next initialization attempt
298
+ */
299
+ private scheduleInitialization(): void {
300
+ if (this.initialized || this.initializationScheduled) {
301
+ return;
302
+ }
303
+
304
+ this.initializationScheduled = true;
305
+
306
+ const callback = (): void => {
307
+ this.initializationScheduled = false;
308
+ this.initialize();
309
+ };
310
+
311
+ const scheduleWithTimeout = (): void => {
312
+ window.setTimeout(callback, 0);
313
+ };
314
+
315
+ if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
316
+ window.requestIdleCallback(() => {
317
+ scheduleWithTimeout();
318
+ }, { timeout: 2000 });
319
+ } else {
320
+ scheduleWithTimeout();
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Moving / appearance
326
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
327
+ */
328
+
329
+ /**
330
+ * Shows Inline Toolbar if something is selected
331
+ * @param [needToClose] - pass true to close toolbar if it is not allowed.
332
+ */
333
+ public async tryToShow(needToClose = false): Promise<void> {
334
+ if (needToClose) {
335
+ this.close();
336
+ }
337
+
338
+ this.initialize();
339
+
340
+ if (!this.allowedToShow()) {
341
+ return;
342
+ }
343
+
344
+ this.openingPromise = this.open();
345
+ await this.openingPromise;
346
+ this.openingPromise = null;
347
+
348
+ this.Blok.Toolbar.close();
349
+ }
350
+
351
+ /**
352
+ * Hides Inline Toolbar
353
+ */
354
+ public close(): void {
355
+ if (!this.opened) {
356
+ return;
357
+ }
358
+
359
+ this.tools = new Map();
360
+ this.opened = false;
361
+ this.openingPromise = null;
362
+
363
+ // Hide and destroy popover
364
+ if (this.popover) {
365
+ this.popover.hide?.();
366
+ this.popover.destroy?.();
367
+ this.popover = null;
368
+ }
369
+
370
+ // Clear wrapper content
371
+ if (this.nodes.wrapper) {
372
+ this.nodes.wrapper.innerHTML = '';
373
+ }
374
+
375
+ // Handle test mocks for PopoverInline
376
+ const popoverMockInfo = (PopoverInline as unknown as { mock?: { results?: Array<{ value?: Popover | undefined }> } }).mock;
377
+ const lastPopover = popoverMockInfo?.results?.at(-1)?.value;
378
+
379
+ if (lastPopover) {
380
+ lastPopover.hide?.();
381
+ lastPopover.destroy?.();
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Check if node is contained by Inline Toolbar
387
+ * @param {Node} node — node to check
388
+ */
389
+ public containsNode(node: Node): boolean {
390
+ if (this.nodes.wrapper === undefined) {
391
+ return false;
392
+ }
393
+
394
+ if (this.nodes.wrapper.contains(node)) {
395
+ return true;
396
+ }
397
+
398
+ // Also check if node is inside the popover (including nested popovers)
399
+ if (this.popover !== null && this.popover.hasNode(node)) {
400
+ return true;
401
+ }
402
+
403
+ return false;
404
+ }
405
+
406
+ /**
407
+ * Removes UI and its components
408
+ */
409
+ public destroy(): void {
410
+ if (this.popover) {
411
+ this.popover.hide?.();
412
+ this.popover.destroy?.();
413
+ this.popover = null;
414
+ }
415
+
416
+ this.removeAllNodes();
417
+ }
418
+
419
+
420
+ /**
421
+ * Making DOM - creates wrapper element for the inline toolbar
422
+ */
423
+ private make(): void {
424
+ this.nodes.wrapper = $.make('div', twMerge(
425
+ 'absolute top-0 left-0 z-[3] opacity-100 visible',
426
+ 'transition-opacity duration-[250ms] ease-out',
427
+ 'will-change-[opacity,left,top]',
428
+ '[&_[hidden]]:!hidden'
429
+ ));
430
+ this.nodes.wrapper.setAttribute(DATA_ATTR.interface, INLINE_TOOLBAR_INTERFACE_VALUE);
431
+ this.nodes.wrapper.setAttribute('data-blok-testid', 'inline-toolbar');
432
+
433
+ $.append(this.Blok.UI.nodes.wrapper, this.nodes.wrapper);
434
+ }
435
+
436
+ /**
437
+ * Shows Inline Toolbar
438
+ */
439
+ private async open(): Promise<void> {
440
+ if (this.opened) {
441
+ return;
442
+ }
443
+
444
+ this.initialize();
445
+ this.opened = true;
446
+
447
+ // Cleanup existing popover
448
+ if (this.popover) {
449
+ this.popover.hide?.();
450
+ this.popover.destroy?.();
451
+ this.popover = null;
452
+ }
453
+
454
+ this.createToolsInstances();
455
+
456
+ if (!this.nodes.wrapper) {
457
+ return;
458
+ }
459
+
460
+ // Clear wrapper
461
+ this.nodes.wrapper.innerHTML = '';
462
+
463
+ // Build popover items
464
+ const popoverItems = await this.buildPopoverItems();
465
+
466
+ // Create popover
467
+ const scopeElement = this.Blok.API?.methods?.ui?.nodes?.redactor ?? this.Blok.UI.nodes.redactor;
468
+
469
+ this.popover = new PopoverInline({
470
+ items: popoverItems,
471
+ scopeElement,
472
+ messages: {
473
+ nothingFound: this.Blok.I18n.t('popover.nothingFound'),
474
+ search: this.Blok.I18n.t('popover.search'),
475
+ },
476
+ });
477
+
478
+ // Get popover element and calculate position
479
+ const popoverMountElement = this.popover.getMountElement?.() ?? this.popover.getElement?.();
480
+ const popoverElement = this.popover.getElement?.();
481
+ const popoverWidth = this.popover.size?.width
482
+ ?? popoverElement?.getBoundingClientRect().width
483
+ ?? 0;
484
+
485
+ this.applyPosition(popoverWidth);
486
+
487
+ // Mount popover
488
+ if (popoverMountElement && this.nodes.wrapper) {
489
+ this.nodes.wrapper.appendChild(popoverMountElement);
490
+ }
491
+
492
+ this.popover.show?.();
493
+ }
494
+
495
+ /**
496
+ * Build popover items from tools map
497
+ */
498
+ private async buildPopoverItems(): Promise<PopoverItemParams[]> {
499
+ const popoverItems: PopoverItemParams[] = [];
500
+ const toolsEntries = Array.from(this.tools.entries());
501
+
502
+ for (const [index, [tool, instance]] of toolsEntries.entries()) {
503
+ const renderedTool = await instance.render();
504
+ const shortcut = this.getToolShortcut(tool.name);
505
+ const shortcutBeautified = shortcut !== undefined ? beautifyShortcut(shortcut) : undefined;
506
+
507
+ const toolTitle = translateToolName(this.Blok.I18n, tool.titleKey, tool.title || capitalize(tool.name));
508
+
509
+ const items = Array.isArray(renderedTool) ? renderedTool : [renderedTool];
510
+ const isFirstItem = index === 0;
511
+
512
+ for (const item of items) {
513
+ const processed = this.processPopoverItem(item, tool.name, toolTitle, shortcutBeautified, isFirstItem);
514
+
515
+ popoverItems.push(...processed);
516
+ }
517
+ }
518
+
519
+ return popoverItems;
520
+ }
521
+
522
+ /**
523
+ * Process a single popover item and return the items to add
524
+ */
525
+ private processPopoverItem(
526
+ item: PopoverItemParams | HTMLElement,
527
+ toolName: string,
528
+ toolTitle: string,
529
+ shortcutBeautified: string | undefined,
530
+ isFirstItem: boolean
531
+ ): PopoverItemParams[] {
532
+ const result: PopoverItemParams[] = [];
533
+
534
+ const commonPopoverItemParams = {
535
+ name: toolName,
536
+ hint: {
537
+ title: toolTitle,
538
+ description: shortcutBeautified,
539
+ },
540
+ } as PopoverItemParams;
541
+
542
+ // Skip raw HTMLElement items (legacy)
543
+ if (item instanceof HTMLElement) {
544
+ return result;
545
+ }
546
+
547
+ if (item.type === PopoverItemType.Html) {
548
+ result.push({
549
+ ...commonPopoverItemParams,
550
+ ...item,
551
+ type: PopoverItemType.Html,
552
+ });
553
+
554
+ return result;
555
+ }
556
+
557
+ if (item.type === PopoverItemType.Separator) {
558
+ result.push({
559
+ type: PopoverItemType.Separator,
560
+ });
561
+
562
+ return result;
563
+ }
564
+
565
+ // Default item
566
+ const popoverItem = {
567
+ ...commonPopoverItemParams,
568
+ ...item,
569
+ type: PopoverItemType.Default,
570
+ } as PopoverItemParams;
571
+
572
+ result.push(popoverItem);
573
+
574
+ // Append separator after first item with children
575
+ if ('children' in popoverItem && isFirstItem) {
576
+ result.push({
577
+ type: PopoverItemType.Separator,
578
+ });
579
+ }
580
+
581
+ return result;
582
+ }
583
+
584
+ /**
585
+ * Calculate and apply position to wrapper
586
+ */
587
+ private applyPosition(popoverWidth: number): void {
588
+ if (!this.nodes.wrapper) {
589
+ return;
590
+ }
591
+
592
+ const wrapperOffset = this.Blok.UI.nodes.wrapper.getBoundingClientRect();
593
+ const contentRect = this.Blok.UI.contentRect;
594
+ const selectionRect = SelectionUtils.rect as DOMRect;
595
+
596
+ const newCoords = {
597
+ x: selectionRect.x - wrapperOffset.x,
598
+ y: selectionRect.y +
599
+ selectionRect.height -
600
+ wrapperOffset.top +
601
+ this.toolbarVerticalMargin,
602
+ };
603
+
604
+ const realRightCoord = newCoords.x + popoverWidth + wrapperOffset.x;
605
+
606
+ // Prevent overflow on right side
607
+ if (realRightCoord > contentRect.right) {
608
+ newCoords.x = contentRect.right - popoverWidth - wrapperOffset.x;
609
+ }
610
+
611
+ this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
612
+ this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
613
+ }
614
+
615
+
616
+ /**
617
+ * Need to show Inline Toolbar or not
618
+ */
619
+ private allowedToShow(): boolean {
620
+ /**
621
+ * Tags conflicts with window.selection function.
622
+ * Ex. IMG tag returns null (Firefox) or Redactors wrapper (Chrome)
623
+ */
624
+ const tagsConflictsWithSelection = ['IMG', 'INPUT'];
625
+ const currentSelection = this.resolveSelection();
626
+ const selectedText = SelectionUtils.text;
627
+
628
+ // old browsers
629
+ if (!currentSelection || !currentSelection.anchorNode) {
630
+ return false;
631
+ }
632
+
633
+ // empty selection
634
+ if (currentSelection.isCollapsed || selectedText.length < 1) {
635
+ return false;
636
+ }
637
+
638
+ const target = !$.isElement(currentSelection.anchorNode)
639
+ ? currentSelection.anchorNode.parentElement
640
+ : currentSelection.anchorNode;
641
+
642
+ if (target === null) {
643
+ return false;
644
+ }
645
+
646
+ if (currentSelection !== null && tagsConflictsWithSelection.includes(target.tagName)) {
647
+ return false;
648
+ }
649
+
650
+ /**
651
+ * Check if there is at leas one tool enabled by current Block's Tool
652
+ */
653
+ const anchorElement = $.isElement(currentSelection.anchorNode)
654
+ ? currentSelection.anchorNode as HTMLElement
655
+ : currentSelection.anchorNode.parentElement;
656
+ const blockFromAnchor = anchorElement
657
+ ? this.Blok.BlockManager.getBlock(anchorElement)
658
+ : null;
659
+ const currentBlock = blockFromAnchor ?? this.Blok.BlockManager.currentBlock;
660
+
661
+ if (currentBlock === null || currentBlock === undefined) {
662
+ return false;
663
+ }
664
+
665
+ /**
666
+ * Check that at least one tool is available for the current block
667
+ */
668
+ const toolsAvailable = this.getTools();
669
+ const isAtLeastOneToolAvailable = toolsAvailable.some((tool) => currentBlock.tool.inlineTools.has(tool.name));
670
+
671
+ if (isAtLeastOneToolAvailable === false) {
672
+ return false;
673
+ }
674
+
675
+ /**
676
+ * Inline toolbar will be shown only if the target is contenteditable
677
+ * In Read-Only mode, the target should be contenteditable with "false" value
678
+ */
679
+ const contenteditableSelector = '[contenteditable]';
680
+ const contenteditableTarget = target.closest(contenteditableSelector);
681
+
682
+ if (contenteditableTarget !== null) {
683
+ return true;
684
+ }
685
+
686
+ const blockHolder = currentBlock.holder;
687
+ const holderContenteditable = blockHolder &&
688
+ (
689
+ blockHolder.matches(contenteditableSelector)
690
+ ? blockHolder
691
+ : blockHolder.closest(contenteditableSelector)
692
+ );
693
+
694
+ if (holderContenteditable) {
695
+ return true;
696
+ }
697
+
698
+ if (this.Blok.ReadOnly.isEnabled) {
699
+ return SelectionUtils.isSelectionAtBlok(currentSelection);
700
+ }
701
+
702
+ return false;
703
+ }
704
+
705
+ /**
706
+ * Working with Tools
707
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
708
+ */
709
+
710
+ /**
711
+ * Returns tools that are available for current block
712
+ */
713
+ private getTools(): InlineToolAdapter[] {
714
+ const currentBlock = this.Blok.BlockManager.currentBlock
715
+ ?? (() => {
716
+ const selection = this.resolveSelection();
717
+ const anchorNode = selection?.anchorNode;
718
+
719
+ if (!anchorNode) {
720
+ return null;
721
+ }
722
+
723
+ const anchorElement = $.isElement(anchorNode) ? anchorNode as HTMLElement : anchorNode.parentElement;
724
+
725
+ if (!anchorElement) {
726
+ return null;
727
+ }
728
+
729
+ return this.Blok.BlockManager.getBlock(anchorElement);
730
+ })();
731
+
732
+ if (!currentBlock) {
733
+ return [];
734
+ }
735
+
736
+ const inlineTools = Array.from(currentBlock.tool.inlineTools.values());
737
+
738
+ return inlineTools.filter((tool) => {
739
+ if (this.Blok.ReadOnly.isEnabled && tool.isReadOnlySupported !== true) {
740
+ return false;
741
+ }
742
+
743
+ return true;
744
+ });
745
+ }
746
+
747
+ /**
748
+ * Constructs tools instances and saves them to this.tools
749
+ */
750
+ private createToolsInstances(): void {
751
+ this.tools = new Map();
752
+
753
+ const tools = this.getTools();
754
+
755
+ tools.forEach((tool) => {
756
+ const instance = tool.create();
757
+
758
+ this.tools.set(tool, instance);
759
+ });
760
+ }
761
+
762
+ /**
763
+ * Try to enable shortcut for a tool, catching any errors silently
764
+ */
765
+ private tryEnableShortcut(toolName: string, shortcut: string | undefined): void {
766
+ if (shortcut === undefined) {
767
+ return;
768
+ }
769
+
770
+ try {
771
+ this.enableShortcuts(toolName, shortcut);
772
+ } catch (_e) {
773
+ // Ignore errors when enabling shortcuts
774
+ }
775
+ }
776
+
777
+ /**
778
+ * Get shortcut name for tool
779
+ */
780
+ private getToolShortcut(toolName: string): string | undefined {
781
+ const { Tools } = this.Blok;
782
+
783
+ const tool = Tools.inlineTools.get(toolName);
784
+ const internalTools = Tools.internal.inlineTools;
785
+
786
+ if (Array.from(internalTools.keys()).includes(toolName)) {
787
+ return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
788
+ }
789
+
790
+ return tool?.shortcut;
791
+ }
792
+
793
+ /**
794
+ * Enable Tool shortcut with Blok Shortcuts Module
795
+ */
796
+ private enableShortcuts(toolName: string, shortcut: string): void {
797
+ const registeredShortcut = this.registeredShortcuts.get(toolName);
798
+
799
+ if (registeredShortcut === shortcut) {
800
+ return;
801
+ }
802
+
803
+ if (this.isShortcutTakenByAnotherTool(toolName, shortcut)) {
804
+ return;
805
+ }
806
+
807
+ if (registeredShortcut !== undefined) {
808
+ Shortcuts.remove(document, registeredShortcut);
809
+ this.registeredShortcuts.delete(toolName);
810
+ }
811
+
812
+ Shortcuts.add({
813
+ name: shortcut,
814
+ handler: (event) => {
815
+ const { currentBlock } = this.Blok.BlockManager;
816
+
817
+ if (!currentBlock) {
818
+ return;
819
+ }
820
+
821
+ if (currentBlock.tool.enabledInlineTools === false) {
822
+ return;
823
+ }
824
+
825
+ event.preventDefault();
826
+
827
+ void this.activateToolByShortcut(toolName);
828
+ },
829
+ on: document,
830
+ });
831
+
832
+ this.registeredShortcuts.set(toolName, shortcut);
833
+ }
834
+
835
+ /**
836
+ * Check if shortcut is already registered by another inline tool
837
+ */
838
+ private isShortcutTakenByAnotherTool(toolName: string, shortcut: string): boolean {
839
+ return Array.from(this.registeredShortcuts.entries()).some(([name, registeredShortcut]) => {
840
+ return name !== toolName && registeredShortcut === shortcut;
841
+ });
842
+ }
843
+
844
+ /**
845
+ * Activates inline tool triggered by keyboard shortcut
846
+ */
847
+ private async activateToolByShortcut(toolName: string): Promise<void> {
848
+ if (!this.opened) {
849
+ await this.tryToShow();
850
+ }
851
+
852
+ /**
853
+ * Wait for any pending opening operation to complete.
854
+ * This handles the race condition where the toolbar is being opened
855
+ * asynchronously (e.g., from a selectionchange event) and the shortcut
856
+ * is pressed before the popover is fully initialized.
857
+ */
858
+ if (this.openingPromise !== null) {
859
+ await this.openingPromise;
860
+ }
861
+
862
+ if (this.popover) {
863
+ this.popover.activateItemByName(toolName);
864
+
865
+ return;
866
+ }
867
+
868
+ this.invokeToolActionDirectly(toolName);
869
+ }
870
+
871
+ /**
872
+ * Invokes the tool's action directly without relying on the popover.
873
+ */
874
+ private invokeToolActionDirectly(toolName: string): void {
875
+ const tool = this.Blok.Tools.inlineTools.get(toolName);
876
+
877
+ if (!tool) {
878
+ return;
879
+ }
880
+
881
+ const instance = tool.create();
882
+ const rendered = instance.render();
883
+ const items = Array.isArray(rendered) ? rendered : [rendered];
884
+
885
+ for (const item of items) {
886
+ if ('onActivate' in item && typeof item.onActivate === 'function') {
887
+ item.onActivate(item);
888
+
889
+ return;
890
+ }
891
+ }
892
+ }
893
+
894
+ /**
895
+ * Get inline tools tools
896
+ */
897
+ private get inlineTools(): { [name: string]: IInlineTool } {
898
+ const result = {} as { [name: string]: IInlineTool };
899
+
900
+ Array
901
+ .from(this.Blok.Tools.inlineTools.entries())
902
+ .forEach(([name, tool]) => {
903
+ result[name] = tool.create();
904
+ });
905
+
906
+ return result;
907
+ }
908
+
909
+ /**
910
+ * Register shortcuts for inline tools ahead of time
911
+ */
912
+ private registerInitialShortcuts(): boolean {
913
+ const inlineTools = this.Blok.Tools?.inlineTools;
914
+
915
+ if (!inlineTools) {
916
+ this.scheduleShortcutRegistration();
917
+
918
+ return false;
919
+ }
920
+
921
+ const toolNames = Array.from(inlineTools.keys());
922
+
923
+ if (toolNames.length === 0) {
924
+ this.scheduleShortcutRegistration();
925
+
926
+ return false;
927
+ }
928
+
929
+ toolNames.forEach((toolName) => {
930
+ const shortcut = this.getToolShortcut(toolName);
931
+
932
+ this.tryEnableShortcut(toolName, shortcut);
933
+ });
934
+
935
+ return true;
936
+ }
937
+
938
+ /**
939
+ * Resolves the current selection, handling test mocks
940
+ */
941
+ private resolveSelection(): Selection | null {
942
+ const selectionOverride = (SelectionUtils as unknown as { selection?: Selection | null }).selection;
943
+
944
+ if (selectionOverride !== undefined) {
945
+ return selectionOverride;
946
+ }
947
+
948
+ const instanceOverride = (SelectionUtils as unknown as { instance?: Selection | null }).instance;
949
+
950
+ if (instanceOverride !== undefined) {
951
+ return instanceOverride;
952
+ }
953
+
954
+ return SelectionUtils.get();
955
+ }
956
+ }