@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,1311 @@
1
+ import { Module } from '../../__module';
2
+ import { Dom as $ } from '../../dom';
3
+ import { getUserOS, isMobileScreen, log } from '../../utils';
4
+ import { hide, onHover } from '../../utils/tooltip';
5
+ import type { ModuleConfig } from '../../../types-internal/module-config';
6
+ import { Block } from '../../block';
7
+ import { Toolbox, ToolboxEvent } from '../../ui/toolbox';
8
+ import { IconMenu, IconPlus } from '../../icons';
9
+ import { BlockHovered } from '../../events/BlockHovered';
10
+ import { BlockSettingsClosed } from '../../events/BlockSettingsClosed';
11
+ import { BlockSettingsOpened } from '../../events/BlockSettingsOpened';
12
+ import type { BlockChangedPayload } from '../../events/BlockChanged';
13
+ import { BlockChanged } from '../../events/BlockChanged';
14
+ import { twJoin } from '../../utils/tw';
15
+ import { DATA_ATTR } from '../../constants';
16
+ import { SelectionUtils } from '../../selection';
17
+
18
+ /**
19
+ * @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set)
20
+ * - make Block Settings a standalone module
21
+ * @todo - Keyboard-only mode bug:
22
+ * press Tab, flip to the Checkbox. press Enter (block will be added), Press Tab
23
+ * (Block Tunes will be opened with Move up focused), press Enter, press Tab ———— both Block Tunes and Toolbox will be opened
24
+ * @todo TEST CASE - show toggler after opening and closing the Inline Toolbar
25
+ */
26
+
27
+ /**
28
+ * HTML Elements used for Toolbar UI
29
+ */
30
+ interface ToolbarNodes {
31
+ wrapper: HTMLElement | undefined;
32
+ content: HTMLElement | undefined;
33
+ actions: HTMLElement | undefined;
34
+
35
+ plusButton: HTMLElement | undefined;
36
+ settingsToggler: HTMLElement | undefined;
37
+ }
38
+
39
+ /**
40
+ * Threshold in pixels to distinguish between a click and a drag
41
+ */
42
+ const DRAG_THRESHOLD = 1000;
43
+ /**
44
+ *
45
+ *«Toolbar» is the node that moves up/down over current block
46
+ *
47
+ *______________________________________ Toolbar ____________________________________________
48
+ *| |
49
+ *| ..................... Content ......................................................... |
50
+ *| . ........ Block Actions ........... |
51
+ *| . . [Open Settings] . |
52
+ *| . [Plus Button] [Toolbox: {Tool1}, {Tool2}] . . |
53
+ *| . . [Settings Panel] . |
54
+ *| . .................................. |
55
+ *| ....................................................................................... |
56
+ *| |
57
+ *|___________________________________________________________________________________________|
58
+ *
59
+ *
60
+ *Toolbox — its an Element contains tools buttons. Can be shown by Plus Button.
61
+ *
62
+ *_______________ Toolbox _______________
63
+ *| |
64
+ *| [Header] [Image] [List] [Quote] ... |
65
+ *|_______________________________________|
66
+ *
67
+ *
68
+ *Settings Panel — is an Element with block settings:
69
+ *
70
+ *____ Settings Panel ____
71
+ *| ...................... |
72
+ *| . Tool Settings . |
73
+ *| ...................... |
74
+ *| . Default Settings . |
75
+ *| ...................... |
76
+ *|________________________|
77
+ * @class
78
+ * @classdesc Toolbar module
79
+ */
80
+ /**
81
+ * @property {object} nodes - Toolbar nodes
82
+ * @property {Element} nodes.wrapper - Toolbar main element
83
+ * @property {Element} nodes.content - Zone with Plus button and toolbox.
84
+ * @property {Element} nodes.actions - Zone with Block Settings and Remove Button
85
+ * @property {Element} nodes.blockActionsButtons - Zone with Block Buttons: [Settings]
86
+ * @property {Element} nodes.plusButton - Button that opens or closes Toolbox
87
+ * @property {Element} nodes.toolbox - Container for tools
88
+ * @property {Element} nodes.settingsToggler - open/close Settings Panel button
89
+ * @property {Element} nodes.settings - Settings Panel
90
+ * @property {Element} nodes.pluginSettings - Plugin Settings section of Settings Panel
91
+ * @property {Element} nodes.defaultSettings - Default Settings section of Settings Panel
92
+ */
93
+ export class Toolbar extends Module<ToolbarNodes> {
94
+ /**
95
+ * Block near which we display the Toolbox
96
+ */
97
+ private hoveredBlock: Block | null = null;
98
+
99
+ /**
100
+ * The actual element being hovered (could be a nested element like a list item)
101
+ */
102
+ private hoveredTarget: Element | null = null;
103
+
104
+ /**
105
+ * Toolbox class instance
106
+ * It will be created in requestIdleCallback so it can be null in some period of time
107
+ */
108
+ private toolboxInstance: Toolbox | null = null;
109
+
110
+ /**
111
+ * Mouse position when mousedown occurred on settings toggler
112
+ * Used to distinguish between click and drag
113
+ */
114
+ private settingsTogglerMouseDownPosition: { x: number; y: number } | null = null;
115
+
116
+ /**
117
+ * Mouse position when mousedown occurred on plus button
118
+ * Used to distinguish between click and drag
119
+ */
120
+ private plusButtonMouseDownPosition: { x: number; y: number } | null = null;
121
+
122
+ /**
123
+ * Last calculated toolbar Y position
124
+ * Used to avoid unnecessary repositioning when the position hasn't changed
125
+ */
126
+ private lastToolbarY: number | null = null;
127
+
128
+ /**
129
+ * Flag to ignore the next mouseup on settings toggler after a block drop
130
+ * Prevents the settings menu from opening when the cursor is over the toggler after drop
131
+ */
132
+ private ignoreNextSettingsMouseUp = false;
133
+
134
+ /**
135
+ * @class
136
+ * @param moduleConfiguration - Module Configuration
137
+ * @param moduleConfiguration.config - Blok's config
138
+ * @param moduleConfiguration.eventsDispatcher - Blok's event dispatcher
139
+ */
140
+ constructor({ config, eventsDispatcher }: ModuleConfig) {
141
+ super({
142
+ config,
143
+ eventsDispatcher,
144
+ });
145
+ }
146
+
147
+ /**
148
+ * CSS styles
149
+ * @returns {object}
150
+ * @deprecated Use data attributes via constants instead
151
+ */
152
+ public get CSS(): { [name: string]: string } {
153
+ return {
154
+ toolbar: twJoin(
155
+ 'absolute left-0 right-0 top-0 transition-opacity duration-100 ease-linear will-change-[opacity,top]'
156
+ ),
157
+ toolbarOpened: 'block',
158
+ toolbarClosed: 'hidden',
159
+ content: twJoin(
160
+ 'relative mx-auto max-w-content'
161
+ ),
162
+ actions: twJoin(
163
+ 'absolute flex opacity-0 pr-[5px]',
164
+ 'right-full',
165
+ // Mobile styles
166
+ 'mobile:right-auto',
167
+ // RTL styles
168
+ 'group-data-[blok-rtl=true]:right-auto group-data-[blok-rtl=true]:left-[calc(-1*theme(width.toolbox-btn))]',
169
+ 'mobile:group-data-[blok-rtl=true]:ml-0 mobile:group-data-[blok-rtl=true]:mr-auto mobile:group-data-[blok-rtl=true]:pr-0 mobile:group-data-[blok-rtl=true]:pl-[10px]'
170
+ ),
171
+ actionsOpened: 'opacity-100',
172
+
173
+ plusButton: twJoin(
174
+ // Base toolbox-button styles
175
+ 'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
176
+ 'shrink-0',
177
+ // SVG sizing
178
+ '[&_svg]:h-6 [&_svg]:w-6',
179
+ // Hover (can-hover)
180
+ 'can-hover:hover:bg-bg-light',
181
+ // Mobile styles (static positioning with overlay-pane appearance)
182
+ 'mobile:bg-white mobile:border mobile:border-[#e8e8eb] mobile:shadow-overlay-pane mobile:rounded-[6px] mobile:z-[2]',
183
+ 'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile',
184
+ // RTL styles
185
+ 'group-data-[blok-rtl=true]:right-[calc(-1*theme(width.toolbox-btn))] group-data-[blok-rtl=true]:left-auto',
186
+ // Narrow mode (not-mobile)
187
+ 'not-mobile:group-data-[blok-narrow=true]:left-[5px]',
188
+ // Narrow mode RTL (not-mobile)
189
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-0 not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:right-[5px]'
190
+ ),
191
+ plusButtonShortcutKey: 'text-white',
192
+ /**
193
+ * Data attribute selector used by SortableJS for drag handle
194
+ */
195
+ settingsToggler: twJoin(
196
+ // Base toolbox-button styles
197
+ 'text-dark cursor-pointer w-toolbox-btn h-toolbox-btn rounded-[7px] inline-flex justify-center items-center select-none',
198
+ 'cursor-pointer select-none',
199
+ // SVG sizing
200
+ '[&_svg]:h-6 [&_svg]:w-6',
201
+ // Active state
202
+ 'active:cursor-grabbing',
203
+ // Hover (can-hover)
204
+ 'can-hover:hover:bg-bg-light can-hover:hover:cursor-grab',
205
+ // When toolbox is opened, use pointer cursor on hover
206
+ 'group-data-[blok-toolbox-opened=true]:can-hover:hover:cursor-pointer',
207
+ // Mobile styles (static positioning with overlay-pane appearance)
208
+ 'mobile:bg-white mobile:border mobile:border-[#e8e8eb] mobile:shadow-overlay-pane mobile:rounded-[6px] mobile:z-[2]',
209
+ 'mobile:w-toolbox-btn-mobile mobile:h-toolbox-btn-mobile',
210
+ // Not-mobile styles
211
+ 'not-mobile:w-6'
212
+ ),
213
+ settingsTogglerHidden: 'hidden',
214
+ settingsTogglerOpened: twJoin(
215
+ // When opened, override hover cursor
216
+ 'can-hover:hover:cursor-pointer'
217
+ ),
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Returns the Toolbar opening state
223
+ * @returns {boolean}
224
+ */
225
+ public get opened(): boolean {
226
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
227
+ return this.nodes.wrapper?.classList.contains(this.CSS.toolbarOpened) ?? false;
228
+ }
229
+
230
+ /**
231
+ * Check if the element is contained in the Toolbar or its components (Toolbox, BlockSettings)
232
+ * @param element - element to check
233
+ */
234
+ public contains(element: HTMLElement): boolean {
235
+ if (this.nodes.wrapper?.contains(element)) {
236
+ return true;
237
+ }
238
+
239
+ if (this.toolboxInstance?.contains(element)) {
240
+ return true;
241
+ }
242
+
243
+ if (this.Blok.BlockSettings.contains(element)) {
244
+ return true;
245
+ }
246
+
247
+ return false;
248
+ }
249
+
250
+ /**
251
+ * Public interface for accessing the Toolbox
252
+ */
253
+ public get toolbox(): {
254
+ opened: boolean | undefined; // undefined is for the case when Toolbox is not initialized yet
255
+ close: () => void;
256
+ open: () => void;
257
+ toggle: () => void;
258
+ hasFocus: () => boolean | undefined;
259
+ } {
260
+ return {
261
+ opened: this.toolboxInstance?.opened,
262
+ close: () => {
263
+ this.toolboxInstance?.close();
264
+ },
265
+ open: () => {
266
+ /**
267
+ * If Toolbox is not initialized yet, do nothing
268
+ */
269
+ if (this.toolboxInstance === null) {
270
+ log('toolbox.open() called before initialization is finished', 'warn');
271
+
272
+ return;
273
+ }
274
+
275
+ /**
276
+ * Set current block to cover the case when the Toolbar showed near hovered Block but caret is set to another Block.
277
+ */
278
+ if (this.hoveredBlock) {
279
+ this.Blok.BlockManager.currentBlock = this.hoveredBlock;
280
+ }
281
+
282
+ this.toolboxInstance.open();
283
+ },
284
+ toggle: () => {
285
+ /**
286
+ * If Toolbox is not initialized yet, do nothing
287
+ */
288
+ if (this.toolboxInstance === null) {
289
+ log('toolbox.toggle() called before initialization is finished', 'warn');
290
+
291
+ return;
292
+ }
293
+
294
+ this.toolboxInstance.toggle();
295
+ },
296
+ hasFocus: () => this.toolboxInstance?.hasFocus(),
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Block actions appearance manipulations
302
+ */
303
+ private get blockActions(): { hide: () => void; show: () => void } {
304
+ return {
305
+ hide: (): void => {
306
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
307
+ this.nodes.actions?.classList.remove(this.CSS.actionsOpened);
308
+ this.nodes.actions?.removeAttribute('data-blok-opened');
309
+ },
310
+ show: (): void => {
311
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
312
+ this.nodes.actions?.classList.add(this.CSS.actionsOpened);
313
+ this.nodes.actions?.setAttribute('data-blok-opened', 'true');
314
+ },
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Methods for working with Block Tunes toggler
320
+ */
321
+ private get blockTunesToggler(): { hide: () => void; show: () => void } {
322
+ return {
323
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
324
+ hide: (): void => this.nodes.settingsToggler?.classList.add(this.CSS.settingsTogglerHidden),
325
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
326
+ show: (): void => this.nodes.settingsToggler?.classList.remove(this.CSS.settingsTogglerHidden),
327
+ };
328
+ }
329
+
330
+
331
+ /**
332
+ * Toggles read-only mode
333
+ * @param {boolean} readOnlyEnabled - read-only mode
334
+ */
335
+ public toggleReadOnly(readOnlyEnabled: boolean): void {
336
+ if (!readOnlyEnabled) {
337
+ window.requestIdleCallback(async () => {
338
+ await this.drawUI();
339
+ this.enableModuleBindings();
340
+ }, { timeout: 2000 });
341
+ } else {
342
+ this.destroy();
343
+ this.Blok.BlockSettings.destroy();
344
+ this.disableModuleBindings();
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Move Toolbar to the passed (or current) Block
350
+ * @param block - block to move Toolbar near it
351
+ * @param target - optional target element that was hovered (for content offset calculation)
352
+ */
353
+ public moveAndOpen(block?: Block | null, target?: Element | null): void {
354
+ /**
355
+ * Some UI elements creates inside requestIdleCallback, so the can be not ready yet
356
+ */
357
+ if (this.toolboxInstance === null) {
358
+ log('Can\'t open Toolbar since Blok initialization is not finished yet', 'warn');
359
+
360
+ return;
361
+ }
362
+
363
+ /**
364
+ * Close Toolbox when we move toolbar
365
+ */
366
+ if (this.toolboxInstance.opened) {
367
+ this.toolboxInstance.close();
368
+ }
369
+
370
+ if (this.Blok.BlockSettings.opened) {
371
+ this.Blok.BlockSettings.close();
372
+ }
373
+
374
+ /**
375
+ * If no one Block selected as a Current
376
+ */
377
+ const targetBlock = block ?? this.Blok.BlockManager.currentBlock;
378
+
379
+ if (!targetBlock) {
380
+ return;
381
+ }
382
+
383
+ /** Clean up draggable on previous block if any */
384
+ if (this.hoveredBlock && this.hoveredBlock !== targetBlock) {
385
+ this.hoveredBlock.cleanupDraggable();
386
+ }
387
+
388
+ this.hoveredBlock = targetBlock;
389
+ this.hoveredTarget = target ?? null;
390
+ this.lastToolbarY = null; // Reset cached position when moving to a new block
391
+
392
+ const { wrapper, plusButton, settingsToggler } = this.nodes;
393
+
394
+ if (!wrapper || !plusButton) {
395
+ return;
396
+ }
397
+
398
+ const targetBlockHolder = targetBlock.holder;
399
+ const { isMobile } = this.Blok.UI;
400
+
401
+
402
+ const toolbarY = this.calculateToolbarY(targetBlock, plusButton, isMobile);
403
+
404
+ /**
405
+ * Move Toolbar to the Top coordinate of Block
406
+ */
407
+ const newToolbarY = Math.floor(toolbarY);
408
+
409
+ this.lastToolbarY = newToolbarY;
410
+ wrapper.style.top = `${newToolbarY}px`;
411
+ targetBlockHolder.appendChild(wrapper);
412
+
413
+ /** Set up draggable on the target block using the settings toggler as drag handle */
414
+ if (settingsToggler && !this.Blok.ReadOnly.isEnabled) {
415
+ targetBlock.setupDraggable(settingsToggler, this.Blok.DragManager);
416
+ }
417
+
418
+ /**
419
+ * Apply content offset for nested elements (e.g., nested list items)
420
+ */
421
+ this.applyContentOffset(targetBlock);
422
+
423
+ /**
424
+ * Do not show Block Tunes Toggler near single and empty block
425
+ */
426
+ const tunes = targetBlock.getTunes();
427
+ const hasAnyTunes = tunes.toolTunes.length > 0 || tunes.commonTunes.length > 0;
428
+
429
+ if (this.Blok.BlockManager.blocks.length === 1 && targetBlock.isEmpty && !hasAnyTunes) {
430
+ this.blockTunesToggler.hide();
431
+ } else {
432
+ this.blockTunesToggler.show();
433
+ }
434
+
435
+ this.open();
436
+ }
437
+
438
+ /**
439
+ * Move Toolbar to the specified block (or first selected block) and open it for multi-block selection.
440
+ * Keeps the add button visible so users can still insert blocks while multiple are selected.
441
+ * @param block - optional block to position the toolbar at (defaults to first selected block)
442
+ */
443
+ public moveAndOpenForMultipleBlocks(block?: Block): void {
444
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
445
+
446
+ if (selectedBlocks.length < 2) {
447
+ return;
448
+ }
449
+
450
+ /**
451
+ * Some UI elements creates inside requestIdleCallback, so they can be not ready yet
452
+ */
453
+ if (this.toolboxInstance === null) {
454
+ log('Can\'t open Toolbar since Blok initialization is not finished yet', 'warn');
455
+
456
+ return;
457
+ }
458
+
459
+ /**
460
+ * Close Toolbox when we move toolbar
461
+ */
462
+ if (this.toolboxInstance.opened) {
463
+ this.toolboxInstance.close();
464
+ }
465
+
466
+ if (this.Blok.BlockSettings.opened) {
467
+ this.Blok.BlockSettings.close();
468
+ }
469
+
470
+ /**
471
+ * Use the provided block or fall back to the first selected block as the anchor for the toolbar
472
+ */
473
+ const targetBlock = block ?? selectedBlocks[0];
474
+
475
+ /** Clean up draggable on previous block if any */
476
+ if (this.hoveredBlock && this.hoveredBlock !== targetBlock) {
477
+ this.hoveredBlock.cleanupDraggable();
478
+ }
479
+
480
+ this.hoveredBlock = targetBlock;
481
+ this.hoveredTarget = null; // No target for multi-block selection
482
+ this.lastToolbarY = null; // Reset cached position when moving to a new block
483
+
484
+ const { wrapper, plusButton } = this.nodes;
485
+
486
+ if (!wrapper || !plusButton) {
487
+ return;
488
+ }
489
+
490
+ const targetBlockHolder = targetBlock.holder;
491
+
492
+ const newToolbarY = Math.floor(this.calculateToolbarY(targetBlock, plusButton, false));
493
+
494
+ this.lastToolbarY = newToolbarY;
495
+ wrapper.style.top = `${newToolbarY}px`;
496
+ targetBlockHolder.appendChild(wrapper);
497
+
498
+ /** Set up draggable on the target block using the settings toggler as drag handle */
499
+ const { settingsToggler } = this.nodes;
500
+
501
+ if (settingsToggler && !this.Blok.ReadOnly.isEnabled) {
502
+ targetBlock.setupDraggable(settingsToggler, this.Blok.DragManager);
503
+ }
504
+
505
+ /**
506
+ * Reset content offset for multi-block selection
507
+ */
508
+ this.applyContentOffset(targetBlock);
509
+
510
+ /**
511
+ * Always show the settings toggler for multi-block selection
512
+ */
513
+ this.blockTunesToggler.show();
514
+
515
+ this.open();
516
+ }
517
+
518
+ /**
519
+ * Close the Toolbar
520
+ */
521
+ public close(): void {
522
+ if (this.Blok.ReadOnly.isEnabled) {
523
+ return;
524
+ }
525
+
526
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
527
+ this.nodes.wrapper?.classList.remove(this.CSS.toolbarOpened);
528
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
529
+ this.nodes.wrapper?.classList.add(this.CSS.toolbarClosed);
530
+ this.nodes.wrapper?.removeAttribute('data-blok-opened');
531
+
532
+ /** Close components */
533
+ this.blockActions.hide();
534
+ this.toolboxInstance?.close();
535
+ this.Blok.BlockSettings.close();
536
+
537
+ /**
538
+ * Restore plus button visibility in case it was hidden by other interactions
539
+ */
540
+ if (this.nodes.plusButton) {
541
+ this.nodes.plusButton.style.display = '';
542
+ }
543
+
544
+ /**
545
+ * Reset the content offset transform
546
+ */
547
+ if (this.nodes.actions) {
548
+ this.nodes.actions.style.transform = '';
549
+ }
550
+ this.hoveredTarget = null;
551
+
552
+ this.reset();
553
+ }
554
+
555
+ /**
556
+ * Prevents the settings menu from opening on the next mouseup event
557
+ * Used after block drop to avoid accidental menu opening
558
+ */
559
+ public skipNextSettingsToggle(): void {
560
+ this.ignoreNextSettingsMouseUp = true;
561
+ }
562
+
563
+ /**
564
+ * Reset the Toolbar position to prevent DOM height growth, for example after blocks deletion
565
+ */
566
+ private reset(): void {
567
+ this.lastToolbarY = null; // Reset cached position when toolbar is reset
568
+
569
+ if (this.nodes.wrapper) {
570
+ this.nodes.wrapper.style.top = 'unset';
571
+
572
+ /**
573
+ * Move Toolbar back to the Blok wrapper to save it from deletion
574
+ */
575
+ this.Blok.UI.nodes.wrapper.appendChild(this.nodes.wrapper);
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Open Toolbar with Plus Button and Actions
581
+ * @param {boolean} withBlockActions - by default, Toolbar opens with Block Actions.
582
+ * This flag allows to open Toolbar without Actions.
583
+ */
584
+ private open(withBlockActions = true): void {
585
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
586
+ this.nodes.wrapper?.classList.remove(this.CSS.toolbarClosed);
587
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
588
+ this.nodes.wrapper?.classList.add(this.CSS.toolbarOpened);
589
+ this.nodes.wrapper?.setAttribute('data-blok-opened', 'true');
590
+
591
+ if (withBlockActions) {
592
+ this.blockActions.show();
593
+ } else {
594
+ this.blockActions.hide();
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Draws Toolbar elements
600
+ */
601
+ private async make(): Promise<void> {
602
+ const wrapper = $.make('div', [
603
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
604
+ this.CSS.toolbar,
605
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
606
+ this.CSS.toolbarClosed,
607
+ 'group-data-[blok-dragging=true]:pointer-events-none',
608
+ ]);
609
+
610
+ this.nodes.wrapper = wrapper;
611
+ wrapper.setAttribute(DATA_ATTR.toolbar, '');
612
+ wrapper.setAttribute('data-blok-testid', 'toolbar');
613
+
614
+ /**
615
+ * Make Content Zone and Actions Zone
616
+ */
617
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
618
+ const content = $.make('div', this.CSS.content);
619
+ const actions = $.make('div', [
620
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- CSS getter now returns Tailwind classes
621
+ this.CSS.actions,
622
+ // Narrow mode positioning on non-mobile screens
623
+ 'not-mobile:group-data-[blok-narrow=true]:right-[calc(-1*theme(spacing.narrow-mode-right-padding)-5px)]',
624
+ // RTL narrow mode: use left positioning instead
625
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:right-auto',
626
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-[calc(-1*theme(spacing.narrow-mode-right-padding)-5px)]',
627
+ // RTL narrow mode additional left offset
628
+ 'not-mobile:group-data-[blok-narrow=true]:group-data-[blok-rtl=true]:left-[-5px]',
629
+ ]);
630
+
631
+ this.nodes.content = content;
632
+
633
+ this.nodes.actions = actions;
634
+ actions.setAttribute('data-blok-testid', 'toolbar-actions');
635
+
636
+ /**
637
+ * Actions will be included to the toolbar content so we can align in to the right of the content
638
+ */
639
+ $.append(wrapper, content);
640
+ $.append(content, actions);
641
+
642
+ /**
643
+ * Fill Content Zone:
644
+ * - Plus Button
645
+ * - Toolbox
646
+ */
647
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
648
+ const plusButton = $.make('div', this.CSS.plusButton, {
649
+ innerHTML: IconPlus,
650
+ });
651
+
652
+ plusButton.setAttribute('data-blok-testid', 'plus-button');
653
+
654
+ this.nodes.plusButton = plusButton;
655
+ $.append(actions, plusButton);
656
+
657
+ /**
658
+ * Plus button mousedown handler
659
+ * Stores the initial mouse position to distinguish between click and drag
660
+ */
661
+ this.readOnlyMutableListeners.on(plusButton, 'mousedown', (e) => {
662
+ const mouseEvent = e as MouseEvent;
663
+
664
+ /**
665
+ * Store the mouse position when mousedown occurs
666
+ * This will be used to determine if the user dragged or clicked
667
+ */
668
+ this.plusButtonMouseDownPosition = {
669
+ x: mouseEvent.clientX,
670
+ y: mouseEvent.clientY,
671
+ };
672
+ }, true);
673
+
674
+ /**
675
+ * Plus button mouseup handler
676
+ * Only opens the toolbox if the mouse didn't move significantly (i.e., it was a click, not a drag)
677
+ *
678
+ * We use mouseup instead of click because when multiple blocks are selected,
679
+ * the browser may not generate a click event due to focus/selection changes
680
+ * during the mousedown phase.
681
+ */
682
+ this.readOnlyMutableListeners.on(plusButton, 'mouseup', (e) => {
683
+ e.stopPropagation();
684
+
685
+ const mouseEvent = e as MouseEvent;
686
+
687
+ /**
688
+ * Check if this was a drag or a click by comparing mouse positions
689
+ * If the mouse moved more than the threshold, it was a drag - don't open toolbox
690
+ */
691
+ const mouseDownPos = this.plusButtonMouseDownPosition;
692
+
693
+ this.plusButtonMouseDownPosition = null;
694
+
695
+ /**
696
+ * If mouseDownPos is null, it means mousedown didn't happen on this element
697
+ * (e.g., user started drag from elsewhere), so ignore this mouseup
698
+ */
699
+ if (mouseDownPos === null) {
700
+ return;
701
+ }
702
+
703
+ const wasDragged = (
704
+ Math.abs(mouseEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
705
+ Math.abs(mouseEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
706
+ );
707
+
708
+ if (wasDragged) {
709
+ return;
710
+ }
711
+
712
+ hide(true);
713
+ this.plusButtonClicked();
714
+ }, true);
715
+
716
+ /**
717
+ * Add events to show/hide tooltip for plus button
718
+ */
719
+ const userOS = getUserOS();
720
+ const modifierClickText = userOS.win
721
+ ? this.Blok.I18n.t('toolbox.ctrlAddAbove')
722
+ : this.Blok.I18n.t('toolbox.optionAddAbove');
723
+
724
+ const tooltipContent = this.createTooltipContent([
725
+ this.Blok.I18n.t('toolbox.addBelow'),
726
+ modifierClickText,
727
+ ]);
728
+
729
+ onHover(plusButton, tooltipContent, {
730
+ hidingDelay: 400,
731
+ });
732
+
733
+ /**
734
+ * Fill Actions Zone:
735
+ * - Settings Toggler
736
+ * - Remove Block Button
737
+ * - Settings Panel
738
+ */
739
+ const settingsToggler = $.make('span', [
740
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
741
+ this.CSS.settingsToggler,
742
+ 'group-data-[blok-dragging=true]:cursor-grabbing',
743
+ ], {
744
+ innerHTML: IconMenu,
745
+ });
746
+
747
+ settingsToggler.setAttribute(DATA_ATTR.settingsToggler, '');
748
+ settingsToggler.setAttribute(DATA_ATTR.dragHandle, '');
749
+ settingsToggler.setAttribute('data-blok-testid', 'settings-toggler');
750
+
751
+ // Accessibility: make the drag handle accessible to screen readers
752
+ // Using tabindex="-1" keeps it accessible but removes from tab order
753
+ // Users can move blocks with keyboard shortcuts (Cmd/Ctrl+Shift+Arrow)
754
+ settingsToggler.setAttribute('role', 'button');
755
+ settingsToggler.setAttribute('tabindex', '-1');
756
+ settingsToggler.setAttribute(
757
+ 'aria-label',
758
+ this.Blok.I18n.t('a11y.dragHandle')
759
+ );
760
+ settingsToggler.setAttribute(
761
+ 'aria-roledescription',
762
+ this.Blok.I18n.t('a11y.dragHandleRole')
763
+ );
764
+
765
+ this.nodes.settingsToggler = settingsToggler;
766
+
767
+ $.append(actions, settingsToggler);
768
+
769
+ const blockTunesTooltip = this.createTooltipContent([
770
+ this.Blok.I18n.t('blockSettings.dragToMove'),
771
+ this.Blok.I18n.t('blockSettings.clickToOpenMenu'),
772
+ ]);
773
+
774
+ onHover(settingsToggler, blockTunesTooltip, {
775
+ hidingDelay: 400,
776
+ });
777
+
778
+ /**
779
+ * Appending Toolbar components to itself
780
+ */
781
+ $.append(actions, this.makeToolbox());
782
+
783
+ const blockSettingsElement = this.Blok.BlockSettings.getElement();
784
+
785
+ if (!blockSettingsElement) {
786
+ throw new Error('Block Settings element was not created');
787
+ }
788
+
789
+ $.append(actions, blockSettingsElement);
790
+
791
+ /**
792
+ * Append toolbar to the Blok
793
+ */
794
+ $.append(this.Blok.UI.nodes.wrapper, wrapper);
795
+ }
796
+
797
+ /**
798
+ * Creates the Toolbox instance and return it's rendered element
799
+ */
800
+ private makeToolbox(): Element {
801
+ /**
802
+ * Make the Toolbox
803
+ */
804
+ this.toolboxInstance = new Toolbox({
805
+ api: this.Blok.API.methods,
806
+ tools: this.Blok.Tools.blockTools,
807
+ i18nLabels: {
808
+ filter: this.Blok.I18n.t('popover.search'),
809
+ nothingFound: this.Blok.I18n.t('popover.nothingFound'),
810
+ },
811
+ i18n: this.Blok.I18n,
812
+ triggerElement: this.nodes.plusButton,
813
+ });
814
+
815
+ this.toolboxInstance.on(ToolboxEvent.Opened, () => {
816
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
817
+ this.Blok.UI.nodes.wrapper.classList.add(this.CSS.openedToolboxHolderModifier);
818
+ this.Blok.UI.nodes.wrapper.setAttribute(DATA_ATTR.toolboxOpened, 'true');
819
+ });
820
+
821
+ this.toolboxInstance.on(ToolboxEvent.Closed, () => {
822
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
823
+ this.Blok.UI.nodes.wrapper.classList.remove(this.CSS.openedToolboxHolderModifier);
824
+ this.Blok.UI.nodes.wrapper.removeAttribute(DATA_ATTR.toolboxOpened);
825
+ });
826
+
827
+ this.toolboxInstance.on(ToolboxEvent.BlockAdded, ({ block }) => {
828
+ const { BlockManager, Caret } = this.Blok;
829
+ const newBlock = BlockManager.getBlockById(block.id);
830
+
831
+ if (!newBlock) {
832
+ return;
833
+ }
834
+
835
+ if (newBlock.inputs.length !== 0) {
836
+ return;
837
+ }
838
+
839
+ /**
840
+ * If the new block doesn't contain inputs, insert the new paragraph below
841
+ */
842
+ if (newBlock === BlockManager.lastBlock) {
843
+ BlockManager.insertAtEnd();
844
+ Caret.setToBlock(BlockManager.lastBlock);
845
+
846
+ return;
847
+ }
848
+
849
+ const nextBlock = BlockManager.nextBlock;
850
+
851
+ if (nextBlock) {
852
+ Caret.setToBlock(nextBlock);
853
+ }
854
+ });
855
+
856
+ const element = this.toolboxInstance.getElement();
857
+
858
+ if (element === null) {
859
+ throw new Error('Toolbox element was not created');
860
+ }
861
+
862
+ return element;
863
+ }
864
+
865
+
866
+ /**
867
+ * Handler for Plus Button
868
+ */
869
+ private plusButtonClicked(): void {
870
+ /**
871
+ * We need to update Current Block because user can click on the Plus Button (thanks to appearing by hover) without any clicks on blok
872
+ * In this case currentBlock will point last block
873
+ */
874
+ if (this.hoveredBlock) {
875
+ this.Blok.BlockManager.currentBlock = this.hoveredBlock;
876
+ }
877
+
878
+ /**
879
+ * Close Block Settings if opened, similar to how settings toggler closes toolbox
880
+ */
881
+ if (this.Blok.BlockSettings.opened) {
882
+ this.Blok.BlockSettings.close();
883
+ }
884
+
885
+ /**
886
+ * Clear block selection when plus button is clicked
887
+ * This allows users to add new blocks even when multiple blocks are selected
888
+ */
889
+ if (this.Blok.BlockSelection.anyBlockSelected) {
890
+ this.Blok.BlockSelection.clearSelection();
891
+ }
892
+
893
+ /**
894
+ * Remove native text selection that may have been created during cross-block selection
895
+ * This needs to happen regardless of anyBlockSelected state, as cross-block selection
896
+ * via Shift+Arrow creates native text selection that spans multiple blocks
897
+ */
898
+ SelectionUtils.get()?.removeAllRanges();
899
+
900
+ this.toolboxInstance?.toggle();
901
+ }
902
+
903
+ /**
904
+ * Enable bindings
905
+ */
906
+ private enableModuleBindings(): void {
907
+ /**
908
+ * Settings toggler
909
+ *
910
+ * mousedown is used because on click selection is lost in Safari and FF
911
+ */
912
+ const settingsToggler = this.nodes.settingsToggler;
913
+
914
+ if (settingsToggler) {
915
+ /**
916
+ * Settings toggler mousedown handler
917
+ * Stores the initial mouse position to distinguish between click and drag
918
+ */
919
+ this.readOnlyMutableListeners.on(settingsToggler, 'mousedown', (e) => {
920
+ hide(true);
921
+
922
+ const mouseEvent = e as MouseEvent;
923
+
924
+ /**
925
+ * Store the mouse position when mousedown occurs
926
+ * This will be used to determine if the user dragged or clicked
927
+ */
928
+ this.settingsTogglerMouseDownPosition = {
929
+ x: mouseEvent.clientX,
930
+ y: mouseEvent.clientY,
931
+ };
932
+ }, true);
933
+
934
+ /**
935
+ * Settings toggler mouseup handler
936
+ * Only opens the menu if the mouse didn't move significantly (i.e., it was a click, not a drag)
937
+ *
938
+ * We use mouseup instead of click because SortableJS (used for drag-and-drop) calls
939
+ * removeAllRanges() during drag preparation, which causes the activeElement to change
940
+ * from the contenteditable DIV to BODY. When the activeElement changes between mousedown
941
+ * and mouseup, the browser doesn't generate a click event. By using mouseup, we can
942
+ * still detect clicks even when SortableJS interferes.
943
+ */
944
+ this.readOnlyMutableListeners.on(settingsToggler, 'mouseup', (e) => {
945
+ e.stopPropagation();
946
+
947
+ /**
948
+ * Ignore mouseup after a block drop to prevent settings menu from opening
949
+ */
950
+ if (this.ignoreNextSettingsMouseUp) {
951
+ this.ignoreNextSettingsMouseUp = false;
952
+
953
+ return;
954
+ }
955
+
956
+ const mouseEvent = e as MouseEvent;
957
+
958
+ /**
959
+ * Check if this was a drag or a click by comparing mouse positions
960
+ * If the mouse moved more than the threshold, it was a drag - don't open menu
961
+ */
962
+ const mouseDownPos = this.settingsTogglerMouseDownPosition;
963
+
964
+ this.settingsTogglerMouseDownPosition = null;
965
+
966
+ /**
967
+ * If mouseDownPos is null, it means mousedown didn't happen on this element
968
+ * (e.g., user started drag from elsewhere), so ignore this mouseup
969
+ */
970
+ if (mouseDownPos === null) {
971
+ return;
972
+ }
973
+
974
+ const wasDragged = (
975
+ Math.abs(mouseEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
976
+ Math.abs(mouseEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
977
+ );
978
+
979
+ if (wasDragged) {
980
+ return;
981
+ }
982
+
983
+ this.settingsTogglerClicked();
984
+
985
+ if (this.toolboxInstance?.opened) {
986
+ this.toolboxInstance.close();
987
+ }
988
+ }, true);
989
+ }
990
+
991
+ /**
992
+ * Subscribe to the 'block-hovered' event if current view is not mobile
993
+ * @see https://github.com/codex-team/editor.js/issues/1972
994
+ */
995
+ if (!isMobileScreen()) {
996
+ /**
997
+ * Subscribe to the 'block-hovered' event
998
+ */
999
+ this.eventsDispatcher.on(BlockHovered, (data) => {
1000
+ /**
1001
+ * Do not move toolbar during drag operations
1002
+ */
1003
+ if (this.Blok.DragManager.isDragging) {
1004
+ return;
1005
+ }
1006
+
1007
+ const hoveredBlock = (data as { block?: Block; target?: Element }).block;
1008
+ const hoveredTarget = (data as { block?: Block; target?: Element }).target;
1009
+
1010
+ if (!(hoveredBlock instanceof Block)) {
1011
+ return;
1012
+ }
1013
+
1014
+ /**
1015
+ * Do not move toolbar if Block Settings or Toolbox opened
1016
+ */
1017
+ if (this.Blok.BlockSettings.opened || this.toolboxInstance?.opened) {
1018
+ return;
1019
+ }
1020
+
1021
+ /**
1022
+ * Check if multiple blocks are selected
1023
+ */
1024
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
1025
+ const isMultiBlockSelection = selectedBlocks.length > 1;
1026
+ const isHoveredBlockSelected = isMultiBlockSelection && selectedBlocks.some(block => block === hoveredBlock);
1027
+
1028
+ /**
1029
+ * For multi-block selection, only move toolbar if the hovered block is one of the selected blocks
1030
+ */
1031
+ if (isMultiBlockSelection && isHoveredBlockSelected) {
1032
+ this.moveAndOpenForMultipleBlocks(hoveredBlock);
1033
+
1034
+ return;
1035
+ }
1036
+
1037
+ /**
1038
+ * For multi-block selection where hovered block is not selected, do nothing
1039
+ */
1040
+ if (isMultiBlockSelection) {
1041
+ return;
1042
+ }
1043
+
1044
+ this.moveAndOpen(hoveredBlock, hoveredTarget);
1045
+ });
1046
+ }
1047
+
1048
+ /**
1049
+ * Subscribe to the Block Settings events to toggle 'opened' state of the Settings Toggler
1050
+ */
1051
+ this.eventsDispatcher.on(BlockSettingsOpened, this.onBlockSettingsOpen);
1052
+ this.eventsDispatcher.on(BlockSettingsClosed, this.onBlockSettingsClose);
1053
+
1054
+ /**
1055
+ * Subscribe to block changes to reposition toolbar when block content changes
1056
+ */
1057
+ this.eventsDispatcher.on(BlockChanged, this.onBlockChanged);
1058
+ }
1059
+
1060
+ /**
1061
+ * Disable bindings
1062
+ */
1063
+ private disableModuleBindings(): void {
1064
+ this.readOnlyMutableListeners.clearAll();
1065
+ this.eventsDispatcher.off(BlockSettingsOpened, this.onBlockSettingsOpen);
1066
+ this.eventsDispatcher.off(BlockSettingsClosed, this.onBlockSettingsClose);
1067
+ this.eventsDispatcher.off(BlockChanged, this.onBlockChanged);
1068
+ }
1069
+
1070
+ /**
1071
+ * Handler for BlockSettingsOpened event
1072
+ */
1073
+ private onBlockSettingsOpen = (): void => {
1074
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1075
+ this.nodes.settingsToggler?.classList.add(this.CSS.settingsTogglerOpened);
1076
+ };
1077
+
1078
+ /**
1079
+ * Handler for BlockSettingsClosed event
1080
+ */
1081
+ private onBlockSettingsClose = (): void => {
1082
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1083
+ this.nodes.settingsToggler?.classList.remove(this.CSS.settingsTogglerOpened);
1084
+ };
1085
+
1086
+ /**
1087
+ * Handler for BlockChanged event - repositions toolbar when block content changes
1088
+ */
1089
+ private onBlockChanged = (payload: BlockChangedPayload): void => {
1090
+ /**
1091
+ * Only reposition if toolbar is opened and we have a hovered block
1092
+ */
1093
+ if (!this.opened || !this.hoveredBlock) {
1094
+ return;
1095
+ }
1096
+
1097
+ /**
1098
+ * Don't reposition if Block Settings or Toolbox is opened
1099
+ */
1100
+ if (this.Blok.BlockSettings.opened || this.toolboxInstance?.opened) {
1101
+ return;
1102
+ }
1103
+
1104
+ /**
1105
+ * Only reposition if the changed block is the hovered block.
1106
+ * This prevents unnecessary repositioning when other blocks change,
1107
+ * and avoids toolbar jumping when interacting with checklist items.
1108
+ */
1109
+ const changedBlockId = payload.event.detail.target.id;
1110
+
1111
+ if (changedBlockId !== this.hoveredBlock.id) {
1112
+ return;
1113
+ }
1114
+
1115
+ this.repositionToolbar();
1116
+ };
1117
+
1118
+ /**
1119
+ * Calculates the Y position for the toolbar, centered on the first line of the block
1120
+ * @param targetBlock - the block to position the toolbar relative to
1121
+ * @param plusButton - the plus button element (used to get toolbar height)
1122
+ * @param isMobile - whether the current view is mobile
1123
+ * @returns the Y position in pixels
1124
+ */
1125
+ private calculateToolbarY(targetBlock: Block, plusButton: HTMLElement, isMobile: boolean): number {
1126
+ const targetBlockHolder = targetBlock.holder;
1127
+ const holderRect = targetBlockHolder.getBoundingClientRect();
1128
+
1129
+ /**
1130
+ * Use the hovered target element (e.g., a nested list item) if available,
1131
+ * otherwise fall back to the block's pluginsContent
1132
+ */
1133
+ const listItemElement = this.hoveredTarget?.closest('[role="listitem"]');
1134
+ /**
1135
+ * For list items, find the actual text content element ([contenteditable]) and use its position
1136
+ * to properly center the toolbar on the text, not on the marker which may have different font-size
1137
+ */
1138
+ const textElement = listItemElement?.querySelector('[contenteditable]');
1139
+ const contentElement = textElement ?? listItemElement ?? targetBlock.pluginsContent;
1140
+ const contentRect = contentElement.getBoundingClientRect();
1141
+ const contentOffset = contentRect.top - holderRect.top;
1142
+
1143
+ const contentStyle = window.getComputedStyle(contentElement);
1144
+ const contentPaddingTop = parseInt(contentStyle.paddingTop, 10) || 0;
1145
+ const lineHeight = parseFloat(contentStyle.lineHeight) || 24;
1146
+ const toolbarHeight = parseInt(window.getComputedStyle(plusButton).height, 10);
1147
+
1148
+ if (isMobile) {
1149
+ return contentOffset - toolbarHeight;
1150
+ }
1151
+
1152
+ const firstLineTop = contentOffset + contentPaddingTop;
1153
+ const firstLineCenterY = firstLineTop + (lineHeight / 2);
1154
+
1155
+ return firstLineCenterY - (toolbarHeight / 2);
1156
+ }
1157
+
1158
+ /**
1159
+ * Repositions the toolbar to stay centered on the first line of the current block
1160
+ * without closing/opening toolbox or block settings
1161
+ */
1162
+ private repositionToolbar(): void {
1163
+ const { wrapper, plusButton } = this.nodes;
1164
+
1165
+ if (!wrapper || !plusButton || !this.hoveredBlock) {
1166
+ return;
1167
+ }
1168
+
1169
+ const newToolbarY = Math.floor(this.calculateToolbarY(this.hoveredBlock, plusButton, this.Blok.UI.isMobile));
1170
+
1171
+ /**
1172
+ * Only update the toolbar position if it has actually changed significantly.
1173
+ * This prevents unnecessary repositioning when block changes don't affect
1174
+ * the toolbar's position (e.g., toggling checkbox styles in a checklist).
1175
+ *
1176
+ * We use a tolerance of 2px to account for:
1177
+ * - Floating-point precision issues in getBoundingClientRect()
1178
+ * - Minor layout changes that don't warrant toolbar repositioning
1179
+ * - Browser rendering differences during DOM mutations
1180
+ */
1181
+ const POSITION_TOLERANCE = 2;
1182
+ const positionChanged = this.lastToolbarY === null ||
1183
+ Math.abs(newToolbarY - this.lastToolbarY) > POSITION_TOLERANCE;
1184
+
1185
+ if (positionChanged) {
1186
+ this.lastToolbarY = newToolbarY;
1187
+ wrapper.style.top = `${newToolbarY}px`;
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * Applies the content offset transform to the actions element based on the hovered target.
1193
+ * This positions the toolbar closer to nested content like list items.
1194
+ * @param targetBlock - the block to get the content offset from
1195
+ */
1196
+ private applyContentOffset(targetBlock: Block): void {
1197
+ const { actions } = this.nodes;
1198
+
1199
+ if (!actions) {
1200
+ return;
1201
+ }
1202
+
1203
+ if (!this.hoveredTarget) {
1204
+ actions.style.transform = '';
1205
+
1206
+ return;
1207
+ }
1208
+
1209
+ const contentOffset = targetBlock.getContentOffset(this.hoveredTarget);
1210
+ const hasValidOffset = contentOffset && contentOffset.left > 0;
1211
+
1212
+ actions.style.transform = hasValidOffset ? `translateX(${contentOffset.left}px)` : '';
1213
+ }
1214
+
1215
+ /**
1216
+ * Clicks on the Block Settings toggler
1217
+ */
1218
+ private settingsTogglerClicked(): void {
1219
+ /**
1220
+ * Cancel any pending drag tracking since we're opening the settings menu
1221
+ * This prevents the drag from starting when the user moves their mouse to the menu
1222
+ */
1223
+ this.Blok.DragManager.cancelTracking();
1224
+
1225
+ /**
1226
+ * Prefer the hovered block (desktop), fall back to the current block (mobile) so tapping the toggler still works
1227
+ */
1228
+ const targetBlock = this.hoveredBlock ?? this.Blok.BlockManager.currentBlock;
1229
+
1230
+ if (!targetBlock) {
1231
+ return;
1232
+ }
1233
+
1234
+ this.hoveredBlock = targetBlock;
1235
+ this.Blok.BlockManager.currentBlock = targetBlock;
1236
+
1237
+ if (this.Blok.BlockSettings.opened) {
1238
+ this.Blok.BlockSettings.close();
1239
+ } else {
1240
+ void this.Blok.BlockSettings.open(targetBlock, this.nodes.settingsToggler);
1241
+ }
1242
+ }
1243
+
1244
+ /**
1245
+ * Draws Toolbar UI
1246
+ *
1247
+ * Toolbar contains BlockSettings and Toolbox.
1248
+ * That's why at first we draw its components and then Toolbar itself
1249
+ *
1250
+ * Steps:
1251
+ * - Make Toolbar dependent components like BlockSettings, Toolbox and so on
1252
+ * - Make itself and append dependent nodes to itself
1253
+ *
1254
+ */
1255
+ private async drawUI(): Promise<void> {
1256
+ /**
1257
+ * Make BlockSettings Panel
1258
+ */
1259
+ this.Blok.BlockSettings.make();
1260
+
1261
+ /**
1262
+ * Make Toolbar
1263
+ */
1264
+ await this.make();
1265
+ }
1266
+
1267
+ /**
1268
+ * Creates a tooltip content element with multiple lines and consistent styling
1269
+ * @param lines - array of text strings, each will be displayed on its own line
1270
+ * @returns the tooltip container element
1271
+ */
1272
+ private createTooltipContent(lines: string[]): HTMLElement {
1273
+ const container = $.make('div');
1274
+
1275
+ container.style.display = 'flex';
1276
+ container.style.flexDirection = 'column';
1277
+ container.style.gap = '4px';
1278
+
1279
+ lines.forEach((text) => {
1280
+ const line = $.make('div');
1281
+ const spaceIndex = text.indexOf(' ');
1282
+
1283
+ if (spaceIndex > 0) {
1284
+ const firstWord = text.substring(0, spaceIndex);
1285
+ const rest = text.substring(spaceIndex);
1286
+ const styledWord = $.make('span', null, { textContent: firstWord });
1287
+
1288
+ styledWord.style.color = 'white';
1289
+ line.appendChild(styledWord);
1290
+ line.appendChild(document.createTextNode(rest));
1291
+ } else {
1292
+ line.appendChild(document.createTextNode(text));
1293
+ }
1294
+
1295
+ container.appendChild(line);
1296
+ });
1297
+
1298
+ return container;
1299
+ }
1300
+
1301
+ /**
1302
+ * Removes all created and saved HTMLElements
1303
+ * It is used in Read-Only mode
1304
+ */
1305
+ private destroy(): void {
1306
+ this.removeAllNodes();
1307
+ if (this.toolboxInstance) {
1308
+ this.toolboxInstance.destroy();
1309
+ }
1310
+ }
1311
+ }