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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/README.md +136 -17
  2. package/codemod/README.md +16 -0
  3. package/codemod/migrate-editorjs-to-blok.js +868 -92
  4. package/codemod/test.js +682 -77
  5. package/dist/blok.mjs +5 -2
  6. package/dist/chunks/blok-B5qs7C5l.mjs +12838 -0
  7. package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
  8. package/dist/chunks/i18next-loader-CTrK3HzG.mjs +43 -0
  9. package/dist/{index-Cl_5rkKS.mjs → chunks/index-DDpzQn-0.mjs} +2 -2
  10. package/dist/chunks/inline-tool-convert-RBcopmCh.mjs +1988 -0
  11. package/dist/chunks/messages-2434tVOK.mjs +47 -0
  12. package/dist/chunks/messages-3DcCwXMF.mjs +47 -0
  13. package/dist/chunks/messages-4kMwVAKY.mjs +47 -0
  14. package/dist/chunks/messages-57uL5htT.mjs +47 -0
  15. package/dist/chunks/messages-76-iJV9Q.mjs +47 -0
  16. package/dist/chunks/messages-8p86Eyf2.mjs +47 -0
  17. package/dist/chunks/messages-BBX0p0Pi.mjs +47 -0
  18. package/dist/chunks/messages-BCm2eudQ.mjs +47 -0
  19. package/dist/chunks/messages-BFiUomgG.mjs +47 -0
  20. package/dist/chunks/messages-BIPNHHAV.mjs +47 -0
  21. package/dist/chunks/messages-BUlwu9mo.mjs +47 -0
  22. package/dist/chunks/messages-BX-DPa-z.mjs +47 -0
  23. package/dist/chunks/messages-BextV3Qh.mjs +47 -0
  24. package/dist/chunks/messages-BiPSFlUG.mjs +47 -0
  25. package/dist/chunks/messages-BiXe9G-O.mjs +47 -0
  26. package/dist/chunks/messages-Bl5z_Igo.mjs +47 -0
  27. package/dist/chunks/messages-BnsE97ku.mjs +47 -0
  28. package/dist/chunks/messages-BoO8gsVD.mjs +47 -0
  29. package/dist/chunks/messages-BqWaOGMn.mjs +47 -0
  30. package/dist/chunks/messages-BqkL2_Ro.mjs +47 -0
  31. package/dist/chunks/messages-BvCkXKX-.mjs +47 -0
  32. package/dist/chunks/messages-C6tbPLoj.mjs +47 -0
  33. package/dist/chunks/messages-CA6T3-gQ.mjs +47 -0
  34. package/dist/chunks/messages-CFFPFdWP.mjs +47 -0
  35. package/dist/chunks/messages-CFrKE-TN.mjs +47 -0
  36. package/dist/chunks/messages-CHz8VlG-.mjs +47 -0
  37. package/dist/chunks/messages-CLixzySl.mjs +47 -0
  38. package/dist/chunks/messages-CV7OM_qk.mjs +47 -0
  39. package/dist/chunks/messages-CXHt3eCC.mjs +47 -0
  40. package/dist/chunks/messages-CbmsBrB0.mjs +47 -0
  41. package/dist/chunks/messages-Ceo1KtFx.mjs +47 -0
  42. package/dist/chunks/messages-Cm0LJLtB.mjs +47 -0
  43. package/dist/chunks/messages-CmymP_Ar.mjs +47 -0
  44. package/dist/chunks/messages-D0ohMB5H.mjs +47 -0
  45. package/dist/chunks/messages-D3GrDwXh.mjs +47 -0
  46. package/dist/chunks/messages-D3vTzIpL.mjs +47 -0
  47. package/dist/chunks/messages-D5WeksbV.mjs +47 -0
  48. package/dist/chunks/messages-DGaab4EP.mjs +47 -0
  49. package/dist/chunks/messages-DKha57ZU.mjs +47 -0
  50. package/dist/chunks/messages-DOaujgMW.mjs +47 -0
  51. package/dist/chunks/messages-DVbPLd_0.mjs +47 -0
  52. package/dist/chunks/messages-D_FCyfW6.mjs +47 -0
  53. package/dist/chunks/messages-Dd5iZN3c.mjs +47 -0
  54. package/dist/chunks/messages-DehM7135.mjs +47 -0
  55. package/dist/chunks/messages-Dg1OHftD.mjs +47 -0
  56. package/dist/chunks/messages-Di6Flq-b.mjs +47 -0
  57. package/dist/chunks/messages-Dqhhex6e.mjs +47 -0
  58. package/dist/chunks/messages-DueVe0F1.mjs +47 -0
  59. package/dist/chunks/messages-Dx3eFwI0.mjs +47 -0
  60. package/dist/chunks/messages-FOtiUoKl.mjs +47 -0
  61. package/dist/chunks/messages-FTOZNhRD.mjs +47 -0
  62. package/dist/chunks/messages-IQxGfQIV.mjs +47 -0
  63. package/dist/chunks/messages-JF2fzCkK.mjs +47 -0
  64. package/dist/chunks/messages-MOGl7I5v.mjs +47 -0
  65. package/dist/chunks/messages-QgYhPL-3.mjs +47 -0
  66. package/dist/chunks/messages-WYWIbQwo.mjs +47 -0
  67. package/dist/chunks/messages-a6A_LgDv.mjs +47 -0
  68. package/dist/chunks/messages-bSf31LJi.mjs +47 -0
  69. package/dist/chunks/messages-diGozhTn.mjs +47 -0
  70. package/dist/chunks/messages-er-kd-VO.mjs +47 -0
  71. package/dist/chunks/messages-ez3w5NBn.mjs +47 -0
  72. package/dist/chunks/messages-f3uXjegd.mjs +47 -0
  73. package/dist/chunks/messages-ohwI1UGv.mjs +47 -0
  74. package/dist/chunks/messages-p9BZJaFV.mjs +47 -0
  75. package/dist/chunks/messages-qIQ4L4rw.mjs +47 -0
  76. package/dist/chunks/messages-qWkXPggi.mjs +47 -0
  77. package/dist/chunks/messages-w5foGze_.mjs +47 -0
  78. package/dist/full.mjs +50 -0
  79. package/dist/locales.mjs +227 -0
  80. package/dist/messages-2434tVOK.mjs +47 -0
  81. package/dist/messages-3DcCwXMF.mjs +47 -0
  82. package/dist/messages-4kMwVAKY.mjs +47 -0
  83. package/dist/messages-57uL5htT.mjs +47 -0
  84. package/dist/messages-76-iJV9Q.mjs +47 -0
  85. package/dist/messages-8p86Eyf2.mjs +47 -0
  86. package/dist/messages-BBX0p0Pi.mjs +47 -0
  87. package/dist/messages-BCm2eudQ.mjs +47 -0
  88. package/dist/messages-BFiUomgG.mjs +47 -0
  89. package/dist/messages-BIPNHHAV.mjs +47 -0
  90. package/dist/messages-BUlwu9mo.mjs +47 -0
  91. package/dist/messages-BX-DPa-z.mjs +47 -0
  92. package/dist/messages-BextV3Qh.mjs +47 -0
  93. package/dist/messages-BiPSFlUG.mjs +47 -0
  94. package/dist/messages-BiXe9G-O.mjs +47 -0
  95. package/dist/messages-Bl5z_Igo.mjs +47 -0
  96. package/dist/messages-BnsE97ku.mjs +47 -0
  97. package/dist/messages-BoO8gsVD.mjs +47 -0
  98. package/dist/messages-BqWaOGMn.mjs +47 -0
  99. package/dist/messages-BqkL2_Ro.mjs +47 -0
  100. package/dist/messages-BvCkXKX-.mjs +47 -0
  101. package/dist/messages-C6tbPLoj.mjs +47 -0
  102. package/dist/messages-CA6T3-gQ.mjs +47 -0
  103. package/dist/messages-CFFPFdWP.mjs +47 -0
  104. package/dist/messages-CFrKE-TN.mjs +47 -0
  105. package/dist/messages-CHz8VlG-.mjs +47 -0
  106. package/dist/messages-CLixzySl.mjs +47 -0
  107. package/dist/messages-CV7OM_qk.mjs +47 -0
  108. package/dist/messages-CXHt3eCC.mjs +47 -0
  109. package/dist/messages-CbmsBrB0.mjs +47 -0
  110. package/dist/messages-Ceo1KtFx.mjs +47 -0
  111. package/dist/messages-Cm0LJLtB.mjs +47 -0
  112. package/dist/messages-CmymP_Ar.mjs +47 -0
  113. package/dist/messages-D0ohMB5H.mjs +47 -0
  114. package/dist/messages-D3GrDwXh.mjs +47 -0
  115. package/dist/messages-D3vTzIpL.mjs +47 -0
  116. package/dist/messages-D5WeksbV.mjs +47 -0
  117. package/dist/messages-DGaab4EP.mjs +47 -0
  118. package/dist/messages-DKha57ZU.mjs +47 -0
  119. package/dist/messages-DOaujgMW.mjs +47 -0
  120. package/dist/messages-DVbPLd_0.mjs +47 -0
  121. package/dist/messages-D_FCyfW6.mjs +47 -0
  122. package/dist/messages-Dd5iZN3c.mjs +47 -0
  123. package/dist/messages-DehM7135.mjs +47 -0
  124. package/dist/messages-Dg1OHftD.mjs +47 -0
  125. package/dist/messages-Di6Flq-b.mjs +47 -0
  126. package/dist/messages-Dqhhex6e.mjs +47 -0
  127. package/dist/messages-DueVe0F1.mjs +47 -0
  128. package/dist/messages-Dx3eFwI0.mjs +47 -0
  129. package/dist/messages-FOtiUoKl.mjs +47 -0
  130. package/dist/messages-FTOZNhRD.mjs +47 -0
  131. package/dist/messages-IQxGfQIV.mjs +47 -0
  132. package/dist/messages-JF2fzCkK.mjs +47 -0
  133. package/dist/messages-MOGl7I5v.mjs +47 -0
  134. package/dist/messages-QgYhPL-3.mjs +47 -0
  135. package/dist/messages-WYWIbQwo.mjs +47 -0
  136. package/dist/messages-a6A_LgDv.mjs +47 -0
  137. package/dist/messages-bSf31LJi.mjs +47 -0
  138. package/dist/messages-diGozhTn.mjs +47 -0
  139. package/dist/messages-er-kd-VO.mjs +47 -0
  140. package/dist/messages-ez3w5NBn.mjs +47 -0
  141. package/dist/messages-f3uXjegd.mjs +47 -0
  142. package/dist/messages-ohwI1UGv.mjs +47 -0
  143. package/dist/messages-p9BZJaFV.mjs +47 -0
  144. package/dist/messages-qIQ4L4rw.mjs +47 -0
  145. package/dist/messages-qWkXPggi.mjs +47 -0
  146. package/dist/messages-w5foGze_.mjs +47 -0
  147. package/dist/tools.mjs +3073 -0
  148. package/dist/vendor.LICENSE.txt +59 -156
  149. package/package.json +48 -16
  150. package/src/blok.ts +267 -0
  151. package/src/components/__module.ts +139 -0
  152. package/src/components/block/api.ts +155 -0
  153. package/src/components/block/index.ts +1427 -0
  154. package/src/components/block-tunes/block-tune-delete.ts +51 -0
  155. package/src/components/blocks.ts +338 -0
  156. package/src/components/constants/data-attributes.ts +342 -0
  157. package/src/components/constants.ts +76 -0
  158. package/src/components/core.ts +392 -0
  159. package/src/components/dom.ts +773 -0
  160. package/src/components/domIterator.ts +189 -0
  161. package/src/components/errors/critical.ts +5 -0
  162. package/src/components/events/BlockChanged.ts +16 -0
  163. package/src/components/events/BlockHovered.ts +21 -0
  164. package/src/components/events/BlockSettingsClosed.ts +12 -0
  165. package/src/components/events/BlockSettingsOpened.ts +12 -0
  166. package/src/components/events/BlokMobileLayoutToggled.ts +15 -0
  167. package/src/components/events/FakeCursorAboutToBeToggled.ts +17 -0
  168. package/src/components/events/FakeCursorHaveBeenSet.ts +17 -0
  169. package/src/components/events/HistoryStateChanged.ts +19 -0
  170. package/src/components/events/RedactorDomChanged.ts +14 -0
  171. package/src/components/events/index.ts +46 -0
  172. package/src/components/flipper.ts +481 -0
  173. package/src/components/i18n/i18next-loader.ts +84 -0
  174. package/src/components/i18n/lightweight-i18n.ts +86 -0
  175. package/src/components/i18n/locales/TRANSLATION_GUIDELINES.md +113 -0
  176. package/src/components/i18n/locales/am/messages.json +44 -0
  177. package/src/components/i18n/locales/ar/messages.json +44 -0
  178. package/src/components/i18n/locales/az/messages.json +44 -0
  179. package/src/components/i18n/locales/bg/messages.json +44 -0
  180. package/src/components/i18n/locales/bn/messages.json +44 -0
  181. package/src/components/i18n/locales/bs/messages.json +44 -0
  182. package/src/components/i18n/locales/cs/messages.json +44 -0
  183. package/src/components/i18n/locales/da/messages.json +44 -0
  184. package/src/components/i18n/locales/de/messages.json +44 -0
  185. package/src/components/i18n/locales/dv/messages.json +44 -0
  186. package/src/components/i18n/locales/el/messages.json +44 -0
  187. package/src/components/i18n/locales/en/messages.json +44 -0
  188. package/src/components/i18n/locales/es/messages.json +44 -0
  189. package/src/components/i18n/locales/et/messages.json +44 -0
  190. package/src/components/i18n/locales/fa/messages.json +44 -0
  191. package/src/components/i18n/locales/fi/messages.json +44 -0
  192. package/src/components/i18n/locales/fil/messages.json +44 -0
  193. package/src/components/i18n/locales/fr/messages.json +44 -0
  194. package/src/components/i18n/locales/gu/messages.json +44 -0
  195. package/src/components/i18n/locales/he/messages.json +44 -0
  196. package/src/components/i18n/locales/hi/messages.json +44 -0
  197. package/src/components/i18n/locales/hr/messages.json +44 -0
  198. package/src/components/i18n/locales/hu/messages.json +44 -0
  199. package/src/components/i18n/locales/hy/messages.json +44 -0
  200. package/src/components/i18n/locales/id/messages.json +44 -0
  201. package/src/components/i18n/locales/index.ts +225 -0
  202. package/src/components/i18n/locales/it/messages.json +44 -0
  203. package/src/components/i18n/locales/ja/messages.json +44 -0
  204. package/src/components/i18n/locales/ka/messages.json +44 -0
  205. package/src/components/i18n/locales/km/messages.json +44 -0
  206. package/src/components/i18n/locales/kn/messages.json +44 -0
  207. package/src/components/i18n/locales/ko/messages.json +44 -0
  208. package/src/components/i18n/locales/ku/messages.json +44 -0
  209. package/src/components/i18n/locales/lo/messages.json +44 -0
  210. package/src/components/i18n/locales/lt/messages.json +44 -0
  211. package/src/components/i18n/locales/lv/messages.json +44 -0
  212. package/src/components/i18n/locales/mk/messages.json +44 -0
  213. package/src/components/i18n/locales/ml/messages.json +44 -0
  214. package/src/components/i18n/locales/mn/messages.json +44 -0
  215. package/src/components/i18n/locales/mr/messages.json +44 -0
  216. package/src/components/i18n/locales/ms/messages.json +44 -0
  217. package/src/components/i18n/locales/my/messages.json +44 -0
  218. package/src/components/i18n/locales/ne/messages.json +44 -0
  219. package/src/components/i18n/locales/nl/messages.json +44 -0
  220. package/src/components/i18n/locales/no/messages.json +44 -0
  221. package/src/components/i18n/locales/pa/messages.json +44 -0
  222. package/src/components/i18n/locales/pl/messages.json +44 -0
  223. package/src/components/i18n/locales/ps/messages.json +44 -0
  224. package/src/components/i18n/locales/pt/messages.json +44 -0
  225. package/src/components/i18n/locales/ro/messages.json +44 -0
  226. package/src/components/i18n/locales/ru/messages.json +44 -0
  227. package/src/components/i18n/locales/sd/messages.json +44 -0
  228. package/src/components/i18n/locales/si/messages.json +44 -0
  229. package/src/components/i18n/locales/sk/messages.json +44 -0
  230. package/src/components/i18n/locales/sl/messages.json +44 -0
  231. package/src/components/i18n/locales/sq/messages.json +44 -0
  232. package/src/components/i18n/locales/sr/messages.json +44 -0
  233. package/src/components/i18n/locales/sv/messages.json +44 -0
  234. package/src/components/i18n/locales/sw/messages.json +44 -0
  235. package/src/components/i18n/locales/ta/messages.json +44 -0
  236. package/src/components/i18n/locales/te/messages.json +44 -0
  237. package/src/components/i18n/locales/th/messages.json +44 -0
  238. package/src/components/i18n/locales/tr/messages.json +44 -0
  239. package/src/components/i18n/locales/ug/messages.json +44 -0
  240. package/src/components/i18n/locales/uk/messages.json +44 -0
  241. package/src/components/i18n/locales/ur/messages.json +44 -0
  242. package/src/components/i18n/locales/vi/messages.json +44 -0
  243. package/src/components/i18n/locales/yi/messages.json +44 -0
  244. package/src/components/i18n/locales/zh/messages.json +44 -0
  245. package/src/components/icons/index.ts +242 -0
  246. package/src/components/inline-tools/inline-tool-bold.ts +2213 -0
  247. package/src/components/inline-tools/inline-tool-convert.ts +141 -0
  248. package/src/components/inline-tools/inline-tool-italic.ts +500 -0
  249. package/src/components/inline-tools/inline-tool-link.ts +539 -0
  250. package/src/components/modules/api/blocks.ts +363 -0
  251. package/src/components/modules/api/caret.ts +125 -0
  252. package/src/components/modules/api/events.ts +51 -0
  253. package/src/components/modules/api/history.ts +73 -0
  254. package/src/components/modules/api/i18n.ts +33 -0
  255. package/src/components/modules/api/index.ts +39 -0
  256. package/src/components/modules/api/inlineToolbar.ts +33 -0
  257. package/src/components/modules/api/listeners.ts +56 -0
  258. package/src/components/modules/api/notifier.ts +46 -0
  259. package/src/components/modules/api/readonly.ts +39 -0
  260. package/src/components/modules/api/sanitizer.ts +30 -0
  261. package/src/components/modules/api/saver.ts +52 -0
  262. package/src/components/modules/api/selection.ts +48 -0
  263. package/src/components/modules/api/styles.ts +72 -0
  264. package/src/components/modules/api/toolbar.ts +79 -0
  265. package/src/components/modules/api/tools.ts +16 -0
  266. package/src/components/modules/api/tooltip.ts +67 -0
  267. package/src/components/modules/api/ui.ts +36 -0
  268. package/src/components/modules/blockEvents.ts +1375 -0
  269. package/src/components/modules/blockManager.ts +1348 -0
  270. package/src/components/modules/blockSelection.ts +708 -0
  271. package/src/components/modules/caret.ts +853 -0
  272. package/src/components/modules/crossBlockSelection.ts +329 -0
  273. package/src/components/modules/dragManager.ts +1141 -0
  274. package/src/components/modules/history.ts +1098 -0
  275. package/src/components/modules/i18n.ts +325 -0
  276. package/src/components/modules/index.ts +139 -0
  277. package/src/components/modules/modificationsObserver.ts +147 -0
  278. package/src/components/modules/paste.ts +1092 -0
  279. package/src/components/modules/readonly.ts +136 -0
  280. package/src/components/modules/rectangleSelection.ts +668 -0
  281. package/src/components/modules/renderer.ts +155 -0
  282. package/src/components/modules/saver.ts +283 -0
  283. package/src/components/modules/toolbar/blockSettings.ts +776 -0
  284. package/src/components/modules/toolbar/index.ts +1311 -0
  285. package/src/components/modules/toolbar/inline.ts +956 -0
  286. package/src/components/modules/tools.ts +589 -0
  287. package/src/components/modules/ui.ts +1179 -0
  288. package/src/components/polyfills.ts +113 -0
  289. package/src/components/selection.ts +1189 -0
  290. package/src/components/tools/base.ts +274 -0
  291. package/src/components/tools/block.ts +291 -0
  292. package/src/components/tools/collection.ts +67 -0
  293. package/src/components/tools/factory.ts +85 -0
  294. package/src/components/tools/inline.ts +71 -0
  295. package/src/components/tools/tune.ts +33 -0
  296. package/src/components/ui/toolbox.ts +497 -0
  297. package/src/components/utils/announcer.ts +205 -0
  298. package/src/components/utils/api.ts +20 -0
  299. package/src/components/utils/bem.ts +26 -0
  300. package/src/components/utils/blocks.ts +284 -0
  301. package/src/components/utils/caret.ts +1067 -0
  302. package/src/components/utils/data-model-transform.ts +382 -0
  303. package/src/components/utils/events.ts +117 -0
  304. package/src/components/utils/keyboard.ts +60 -0
  305. package/src/components/utils/listeners.ts +296 -0
  306. package/src/components/utils/mutations.ts +39 -0
  307. package/src/components/utils/notifier/draw.ts +190 -0
  308. package/src/components/utils/notifier/index.ts +66 -0
  309. package/src/components/utils/notifier/types.ts +1 -0
  310. package/src/components/utils/notifier.ts +77 -0
  311. package/src/components/utils/placeholder.ts +140 -0
  312. package/src/components/utils/popover/components/hint/hint.const.ts +10 -0
  313. package/src/components/utils/popover/components/hint/hint.ts +46 -0
  314. package/src/components/utils/popover/components/hint/index.ts +6 -0
  315. package/src/components/utils/popover/components/popover-header/index.ts +2 -0
  316. package/src/components/utils/popover/components/popover-header/popover-header.const.ts +8 -0
  317. package/src/components/utils/popover/components/popover-header/popover-header.ts +80 -0
  318. package/src/components/utils/popover/components/popover-header/popover-header.types.ts +14 -0
  319. package/src/components/utils/popover/components/popover-item/index.ts +13 -0
  320. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +50 -0
  321. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +666 -0
  322. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.const.ts +14 -0
  323. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.ts +136 -0
  324. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +20 -0
  325. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.ts +117 -0
  326. package/src/components/utils/popover/components/popover-item/popover-item.ts +187 -0
  327. package/src/components/utils/popover/components/search-input/index.ts +2 -0
  328. package/src/components/utils/popover/components/search-input/search-input.const.ts +8 -0
  329. package/src/components/utils/popover/components/search-input/search-input.ts +181 -0
  330. package/src/components/utils/popover/components/search-input/search-input.types.ts +30 -0
  331. package/src/components/utils/popover/index.ts +13 -0
  332. package/src/components/utils/popover/popover-abstract.ts +448 -0
  333. package/src/components/utils/popover/popover-desktop.ts +643 -0
  334. package/src/components/utils/popover/popover-inline.ts +338 -0
  335. package/src/components/utils/popover/popover-mobile.ts +201 -0
  336. package/src/components/utils/popover/popover.const.ts +81 -0
  337. package/src/components/utils/popover/utils/popover-states-history.ts +72 -0
  338. package/src/components/utils/promise-queue.ts +43 -0
  339. package/src/components/utils/sanitizer.ts +537 -0
  340. package/src/components/utils/scroll-locker.ts +87 -0
  341. package/src/components/utils/shortcut.ts +231 -0
  342. package/src/components/utils/shortcuts.ts +113 -0
  343. package/src/components/utils/tools.ts +105 -0
  344. package/src/components/utils/tooltip.ts +642 -0
  345. package/src/components/utils/tw.ts +241 -0
  346. package/src/components/utils.ts +1081 -0
  347. package/src/env.d.ts +13 -0
  348. package/src/full.ts +69 -0
  349. package/src/locales.ts +51 -0
  350. package/src/stories/Block.stories.ts +498 -0
  351. package/src/stories/EditorModes.stories.ts +505 -0
  352. package/src/stories/Header.stories.ts +137 -0
  353. package/src/stories/InlineToolbar.stories.ts +498 -0
  354. package/src/stories/List.stories.ts +259 -0
  355. package/src/stories/Notifier.stories.ts +340 -0
  356. package/src/stories/Paragraph.stories.ts +112 -0
  357. package/src/stories/Placeholder.stories.ts +319 -0
  358. package/src/stories/Popover.stories.ts +844 -0
  359. package/src/stories/Selection.stories.ts +250 -0
  360. package/src/stories/StubBlock.stories.ts +156 -0
  361. package/src/stories/Toolbar.stories.ts +223 -0
  362. package/src/stories/Toolbox.stories.ts +166 -0
  363. package/src/stories/Tooltip.stories.ts +198 -0
  364. package/src/stories/helpers.ts +463 -0
  365. package/src/styles/main.css +123 -0
  366. package/src/tools/header/index.ts +570 -0
  367. package/src/tools/index.ts +38 -0
  368. package/src/tools/list/index.ts +1803 -0
  369. package/src/tools/paragraph/index.ts +411 -0
  370. package/src/tools/stub/index.ts +107 -0
  371. package/src/types-internal/blok-modules.d.ts +87 -0
  372. package/src/types-internal/html-janitor.d.ts +28 -0
  373. package/src/types-internal/module-config.d.ts +11 -0
  374. package/src/variants/all-locales.ts +155 -0
  375. package/src/variants/blok-maximum.ts +20 -0
  376. package/src/variants/blok-minimum.ts +243 -0
  377. package/types/api/blocks.d.ts +1 -1
  378. package/types/api/i18n.d.ts +5 -3
  379. package/types/api/selection.d.ts +6 -0
  380. package/types/api/styles.d.ts +0 -5
  381. package/types/configs/blok-config.d.ts +21 -0
  382. package/types/configs/i18n-config.d.ts +52 -2
  383. package/types/configs/i18n-dictionary.d.ts +16 -90
  384. package/types/data-attributes.d.ts +169 -0
  385. package/types/data-formats/output-data.d.ts +15 -0
  386. package/types/full.d.ts +80 -0
  387. package/types/index.d.ts +9 -12
  388. package/types/locales.d.ts +59 -0
  389. package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
  390. package/types/tools/block-tool.d.ts +9 -0
  391. package/types/tools/header.d.ts +18 -0
  392. package/types/tools/index.d.ts +1 -0
  393. package/types/tools/list.d.ts +91 -0
  394. package/types/tools/paragraph.d.ts +71 -0
  395. package/types/tools/tool-settings.d.ts +16 -2
  396. package/types/tools/tool.d.ts +6 -0
  397. package/types/tools-entry.d.ts +49 -0
  398. package/types/utils/popover/popover-item.d.ts +0 -5
  399. package/dist/blok-DvN73wsH.mjs +0 -19922
  400. package/dist/blok.umd.js +0 -166
@@ -0,0 +1,1141 @@
1
+ /**
2
+ * @class DragManager
3
+ * @classdesc Manages pointer-based (non-native) drag and drop for blocks
4
+ * Uses mousedown/mousemove/mouseup instead of HTML5 drag API
5
+ * This allows full control over the cursor during drag operations
6
+ * @module DragManager
7
+ */
8
+ import { Module } from '../__module';
9
+ import type { Block } from '../block';
10
+ import { Dom as $ } from '../dom';
11
+ import { hide } from '../utils/tooltip';
12
+ import { DATA_ATTR, createSelector } from '../constants';
13
+ import { twMerge } from '../utils/tw';
14
+ import { announce } from '../utils/announcer';
15
+
16
+ /**
17
+ * Styles for the drag preview element
18
+ */
19
+ const PREVIEW_STYLES = {
20
+ base: 'fixed pointer-events-none z-[10000] opacity-80 transition-none',
21
+ content: 'relative mx-auto max-w-content',
22
+ };
23
+
24
+ /**
25
+ * Configuration for drag behavior
26
+ */
27
+ const DRAG_CONFIG = {
28
+ /** Offset from cursor to preview element */
29
+ previewOffsetX: 10,
30
+ previewOffsetY: 0,
31
+ /** Minimum distance to start drag (prevents accidental drags) */
32
+ dragThreshold: 5,
33
+ /** Auto-scroll configuration */
34
+ autoScrollZone: 50,
35
+ autoScrollSpeed: 10,
36
+ /** Throttle delay for drop position announcements (ms) */
37
+ announcementThrottleMs: 300,
38
+ };
39
+
40
+ /**
41
+ * State of the current drag operation
42
+ */
43
+ interface DragState {
44
+ /** Primary block being dragged (the one with the drag handle) */
45
+ sourceBlock: Block;
46
+ /** All blocks being dragged (single block or multiple selected blocks) */
47
+ sourceBlocks: Block[];
48
+ /** Whether this is a multi-block drag operation */
49
+ isMultiBlockDrag: boolean;
50
+ /** Current drop target block */
51
+ targetBlock: Block | null;
52
+ /** Edge of target block ('top' or 'bottom') */
53
+ targetEdge: 'top' | 'bottom' | null;
54
+ /** Last announced drop position (to avoid duplicate announcements) */
55
+ lastAnnouncedDropIndex: number | null;
56
+ /** Pending drop position to announce (for throttling) */
57
+ pendingAnnouncementIndex: number | null;
58
+ /** Timeout ID for throttled announcement */
59
+ announcementTimeoutId: ReturnType<typeof setTimeout> | null;
60
+ /** Drag preview element */
61
+ previewElement: HTMLElement;
62
+ /** Starting mouse position */
63
+ startX: number;
64
+ startY: number;
65
+ /** Whether drag has actually started (passed threshold) */
66
+ isDragging: boolean;
67
+ /** Auto-scroll interval */
68
+ autoScrollInterval: number | null;
69
+ /** Scrollable container for auto-scroll (null means use window) */
70
+ scrollContainer: HTMLElement | null;
71
+ }
72
+
73
+ export class DragManager extends Module {
74
+ /**
75
+ * Current drag state
76
+ */
77
+ private dragState: DragState | null = null;
78
+
79
+ /**
80
+ * Returns true if a drag operation is currently in progress
81
+ */
82
+ public get isDragging(): boolean {
83
+ return this.dragState?.isDragging ?? false;
84
+ }
85
+
86
+ /**
87
+ * Cancels any pending drag tracking without performing a drop
88
+ * Call this when something else (like opening a menu) should take precedence
89
+ */
90
+ public cancelTracking(): void {
91
+ if (this.dragState && !this.dragState.isDragging) {
92
+ this.cleanup();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Bound event handlers for cleanup
98
+ */
99
+ private boundHandlers: {
100
+ onMouseMove: (e: MouseEvent) => void;
101
+ onMouseUp: (e: MouseEvent) => void;
102
+ onKeyDown: (e: KeyboardEvent) => void;
103
+ onKeyUp: (e: KeyboardEvent) => void;
104
+ } | null = null;
105
+
106
+ /**
107
+ * Module preparation
108
+ */
109
+ public async prepare(): Promise<void> {
110
+ // No preparation needed
111
+ }
112
+
113
+ /**
114
+ * Starts listening for drag on the given drag handle
115
+ * Called by Block when toolbar moves to it
116
+ * @param dragHandle - Element to use as drag handle
117
+ * @param block - Block that will be dragged
118
+ * @returns Cleanup function
119
+ */
120
+ public setupDragHandle(dragHandle: HTMLElement, block: Block): () => void {
121
+ const onMouseDown = (e: MouseEvent): void => {
122
+ // Only handle left mouse button
123
+ if (e.button !== 0) {
124
+ return;
125
+ }
126
+
127
+ this.startDragTracking(e, block);
128
+ };
129
+
130
+ dragHandle.addEventListener('mousedown', onMouseDown);
131
+
132
+ return (): void => {
133
+ dragHandle.removeEventListener('mousedown', onMouseDown);
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Starts tracking mouse for potential drag
139
+ * @param e - Mouse event
140
+ * @param block - Block to drag
141
+ */
142
+ private startDragTracking(e: MouseEvent, block: Block): void {
143
+ const contentElement = block.holder.querySelector('[data-blok-element-content]') as HTMLElement | null;
144
+
145
+ if (!contentElement) {
146
+ return;
147
+ }
148
+
149
+ // Determine if this is a multi-block drag
150
+ const isBlockSelected = block.selected;
151
+ const initialBlocks = isBlockSelected
152
+ ? this.Blok.BlockSelection.selectedBlocks
153
+ : [block];
154
+
155
+ // For single-block list item drags, include all descendants
156
+ const descendants = !isBlockSelected
157
+ ? this.getListItemDescendants(block)
158
+ : [];
159
+ const blocksToMove = descendants.length > 0
160
+ ? [block, ...descendants]
161
+ : initialBlocks;
162
+
163
+ const isMultiBlock = blocksToMove.length > 1;
164
+
165
+ // Create appropriate preview (single or multi-block)
166
+ const preview = isMultiBlock
167
+ ? this.createMultiBlockPreview(blocksToMove)
168
+ : this.createPreview(contentElement, block.stretched);
169
+
170
+ preview.style.display = 'none';
171
+ document.body.appendChild(preview);
172
+
173
+ this.dragState = {
174
+ sourceBlock: block,
175
+ sourceBlocks: blocksToMove,
176
+ isMultiBlockDrag: isMultiBlock,
177
+ targetBlock: null,
178
+ targetEdge: null,
179
+ lastAnnouncedDropIndex: null,
180
+ pendingAnnouncementIndex: null,
181
+ announcementTimeoutId: null,
182
+ previewElement: preview,
183
+ startX: e.clientX,
184
+ startY: e.clientY,
185
+ isDragging: false,
186
+ autoScrollInterval: null,
187
+ scrollContainer: this.findScrollableAncestor(this.Blok.UI.nodes.wrapper),
188
+ };
189
+
190
+ // Bind handlers
191
+ this.boundHandlers = {
192
+ onMouseMove: this.onMouseMove.bind(this),
193
+ onMouseUp: this.onMouseUp.bind(this),
194
+ onKeyDown: this.onKeyDown.bind(this),
195
+ onKeyUp: this.onKeyUp.bind(this),
196
+ };
197
+
198
+ document.addEventListener('mousemove', this.boundHandlers.onMouseMove);
199
+ document.addEventListener('mouseup', this.boundHandlers.onMouseUp);
200
+ document.addEventListener('keydown', this.boundHandlers.onKeyDown);
201
+ document.addEventListener('keyup', this.boundHandlers.onKeyUp);
202
+ }
203
+
204
+ /**
205
+ * Creates the drag preview element
206
+ * @param contentElement - Element to clone for preview
207
+ * @param isStretched - Whether block is stretched
208
+ * @returns Preview element
209
+ */
210
+ private createPreview(contentElement: HTMLElement, isStretched: boolean): HTMLElement {
211
+ const preview = $.make('div', PREVIEW_STYLES.base);
212
+ const clone = contentElement.cloneNode(true) as HTMLElement;
213
+
214
+ // Reset styles on clone
215
+ clone.className = twMerge(PREVIEW_STYLES.content, isStretched ? 'max-w-none' : '');
216
+
217
+ // Reset margin on the tool's rendered element (first child) to prevent offset
218
+ const toolElement = clone.firstElementChild as HTMLElement | null;
219
+
220
+ if (toolElement) {
221
+ toolElement.className = twMerge(toolElement.className, '!m-0');
222
+ }
223
+
224
+ preview.appendChild(clone);
225
+
226
+ return preview;
227
+ }
228
+
229
+ /**
230
+ * Creates a stacked preview element for multiple blocks
231
+ * @param blocks - Array of blocks to preview
232
+ * @returns Preview element with stacked blocks and count badge
233
+ */
234
+ private createMultiBlockPreview(blocks: Block[]): HTMLElement {
235
+ const preview = $.make('div', PREVIEW_STYLES.base);
236
+
237
+ // Get block holder dimensions to capture actual spacing
238
+ const blockInfo = blocks.map((block) => {
239
+ const holderRect = block.holder.getBoundingClientRect();
240
+ const contentElement = block.holder.querySelector('[data-blok-element-content]') as HTMLElement | null;
241
+
242
+ if (!contentElement) {
243
+ return { width: 0, height: 0, element: null, holderHeight: 0 };
244
+ }
245
+
246
+ const contentRect = contentElement.getBoundingClientRect();
247
+
248
+ return {
249
+ width: contentRect.width,
250
+ height: contentRect.height,
251
+ element: contentElement,
252
+ holderHeight: holderRect.height, // Includes margins/padding
253
+ };
254
+ });
255
+
256
+ // Calculate cumulative top positions using actual block holder heights
257
+ const positions = blockInfo.reduce<number[]>((acc, _, index) => {
258
+ if (index === 0) {
259
+ acc.push(0);
260
+ } else {
261
+ const previousTop = acc[index - 1];
262
+ const previousHolderHeight = blockInfo[index - 1].holderHeight;
263
+
264
+ acc.push(previousTop + previousHolderHeight);
265
+ }
266
+
267
+ return acc;
268
+ }, []);
269
+
270
+ // Calculate total dimensions
271
+ const maxWidth = Math.max(...blockInfo.map(info => info.width), 0);
272
+ const lastIndex = blockInfo.length - 1;
273
+ const totalHeight = lastIndex >= 0
274
+ ? positions[lastIndex] + blockInfo[lastIndex].height
275
+ : 0;
276
+
277
+ // Create stacked blocks
278
+ blocks.forEach((block, index) => {
279
+ const info = blockInfo[index];
280
+
281
+ if (!info.element) {
282
+ return;
283
+ }
284
+
285
+ const clone = info.element.cloneNode(true) as HTMLElement;
286
+
287
+ clone.className = twMerge(PREVIEW_STYLES.content, block.stretched ? 'max-w-none' : '');
288
+
289
+ // Reset margin on the tool's rendered element (first child) to prevent offset
290
+ const toolElement = clone.firstElementChild as HTMLElement | null;
291
+
292
+ if (toolElement) {
293
+ toolElement.className = twMerge(toolElement.className, '!m-0');
294
+ }
295
+
296
+ // Position with cumulative offset
297
+ clone.style.position = 'absolute';
298
+ clone.style.top = `${positions[index]}px`;
299
+ clone.style.left = '0';
300
+ clone.style.zIndex = `${blocks.length - index}`;
301
+
302
+ preview.appendChild(clone);
303
+ });
304
+
305
+ // Set explicit dimensions on the preview container
306
+ // This is necessary because absolutely positioned children don't contribute to parent size
307
+ preview.style.width = `${maxWidth}px`;
308
+ preview.style.height = `${totalHeight}px`;
309
+
310
+ return preview;
311
+ }
312
+
313
+ /**
314
+ * Handles mouse move during drag
315
+ * @param e - Mouse event
316
+ */
317
+ private onMouseMove(e: MouseEvent): void {
318
+ if (!this.dragState) {
319
+ return;
320
+ }
321
+
322
+ const { startX, startY, isDragging, previewElement } = this.dragState;
323
+
324
+ // Check if we've passed the drag threshold and start actual drag
325
+ const dx = e.clientX - startX;
326
+ const dy = e.clientY - startY;
327
+ const distance = Math.sqrt(dx * dx + dy * dy);
328
+ const shouldStartDrag = !isDragging && distance >= DRAG_CONFIG.dragThreshold;
329
+
330
+ if (shouldStartDrag) {
331
+ this.startDrag();
332
+ }
333
+
334
+ // Update preview position
335
+ previewElement.style.left = `${e.clientX + DRAG_CONFIG.previewOffsetX}px`;
336
+ previewElement.style.top = `${e.clientY + DRAG_CONFIG.previewOffsetY}px`;
337
+
338
+ // Find drop target
339
+ this.updateDropTarget(e.clientX, e.clientY);
340
+
341
+ // Handle auto-scroll
342
+ this.handleAutoScroll(e.clientY);
343
+ }
344
+
345
+ /**
346
+ * Starts the actual drag operation
347
+ */
348
+ private startDrag(): void {
349
+ if (!this.dragState) {
350
+ return;
351
+ }
352
+
353
+ this.dragState.isDragging = true;
354
+ this.dragState.previewElement.style.display = 'block';
355
+
356
+ // Set global dragging state
357
+ const wrapper = this.Blok.UI.nodes.wrapper;
358
+
359
+ wrapper.setAttribute(DATA_ATTR.dragging, 'true');
360
+
361
+ // Add multi-block dragging attribute if applicable
362
+ if (this.dragState.isMultiBlockDrag) {
363
+ wrapper.setAttribute(DATA_ATTR.draggingMulti, 'true');
364
+ }
365
+
366
+ // Clear selection for single-block drags only
367
+ // For multi-block drags, keep selection visible for visual feedback
368
+ if (!this.dragState.isMultiBlockDrag) {
369
+ this.Blok.BlockSelection.clearSelection();
370
+ }
371
+
372
+ hide(true);
373
+ this.Blok.Toolbar.close();
374
+
375
+ // Announce drag started to screen readers
376
+ this.announceDragStarted();
377
+ }
378
+
379
+ /**
380
+ * Announces that a drag operation has started
381
+ * Note: This is only called from startDrag() which guarantees dragState exists
382
+ */
383
+ private announceDragStarted(): void {
384
+ const blockCount = this.dragState!.sourceBlocks.length;
385
+
386
+ if (blockCount > 1) {
387
+ const message = this.Blok.I18n.t('a11y.dragStartedMultiple', { count: blockCount });
388
+
389
+ announce(message, { politeness: 'assertive' });
390
+ } else {
391
+ announce(
392
+ this.Blok.I18n.t('a11y.dragStarted'),
393
+ { politeness: 'assertive' }
394
+ );
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Updates the drop target based on cursor position
400
+ * @param clientX - Cursor X position
401
+ * @param clientY - Cursor Y position
402
+ */
403
+ private updateDropTarget(clientX: number, clientY: number): void {
404
+ if (!this.dragState) {
405
+ return;
406
+ }
407
+
408
+ // Clear previous indicator
409
+ if (this.dragState.targetBlock) {
410
+ this.dragState.targetBlock.holder.removeAttribute('data-drop-indicator');
411
+ this.dragState.targetBlock.holder.style.removeProperty('--drop-indicator-depth');
412
+ }
413
+
414
+ // Find element under cursor (temporarily hide preview)
415
+ this.dragState.previewElement.style.display = 'none';
416
+ const elementUnderCursor = document.elementFromPoint(clientX, clientY);
417
+
418
+ this.dragState.previewElement.style.display = 'block';
419
+
420
+ if (!elementUnderCursor) {
421
+ this.dragState.targetBlock = null;
422
+ this.dragState.targetEdge = null;
423
+
424
+ return;
425
+ }
426
+
427
+ // Find block holder
428
+ const blockHolder = elementUnderCursor.closest(createSelector(DATA_ATTR.element)) as HTMLElement | null;
429
+
430
+ if (!blockHolder) {
431
+ this.dragState.targetBlock = null;
432
+ this.dragState.targetEdge = null;
433
+
434
+ return;
435
+ }
436
+
437
+ // Find the block instance
438
+ const targetBlock = this.Blok.BlockManager.blocks.find(b => b.holder === blockHolder);
439
+
440
+ if (!targetBlock || targetBlock === this.dragState.sourceBlock) {
441
+ this.dragState.targetBlock = null;
442
+ this.dragState.targetEdge = null;
443
+
444
+ return;
445
+ }
446
+
447
+ // Prevent dropping into the middle of a multi-block selection
448
+ if (this.dragState.isMultiBlockDrag && this.dragState.sourceBlocks.includes(targetBlock)) {
449
+ this.dragState.targetBlock = null;
450
+ this.dragState.targetEdge = null;
451
+
452
+ return;
453
+ }
454
+
455
+ // Determine edge (top or bottom half of block)
456
+ const rect = blockHolder.getBoundingClientRect();
457
+ const isTopHalf = clientY < rect.top + rect.height / 2;
458
+ const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
459
+
460
+ // Normalize: convert "top of block N" to "bottom of block N-1" (except for the first block)
461
+ // This ensures we only ever show one indicator per drop position
462
+ const previousBlock = targetIndex > 0
463
+ ? this.Blok.BlockManager.getBlockByIndex(targetIndex - 1)
464
+ : null;
465
+ const canUsePreviousBlock = previousBlock && !this.dragState.sourceBlocks.includes(previousBlock);
466
+
467
+ if (isTopHalf && targetIndex > 0 && canUsePreviousBlock) {
468
+ this.dragState.targetBlock = previousBlock;
469
+ this.dragState.targetEdge = 'bottom';
470
+ previousBlock.holder.setAttribute('data-drop-indicator', 'bottom');
471
+
472
+ const targetDepth = this.calculateTargetDepth(previousBlock, 'bottom');
473
+
474
+ previousBlock.holder.style.setProperty('--drop-indicator-depth', String(targetDepth));
475
+
476
+ return;
477
+ }
478
+
479
+ if (isTopHalf && targetIndex > 0) {
480
+ // Previous block is part of selection, can't drop here
481
+ this.dragState.targetBlock = null;
482
+ this.dragState.targetEdge = null;
483
+
484
+ return;
485
+ }
486
+
487
+ // First block top half, or any block bottom half
488
+ const edge: 'top' | 'bottom' = isTopHalf ? 'top' : 'bottom';
489
+
490
+ this.dragState.targetBlock = targetBlock;
491
+ this.dragState.targetEdge = edge;
492
+ blockHolder.setAttribute('data-drop-indicator', edge);
493
+
494
+ const targetDepth = this.calculateTargetDepth(targetBlock, edge);
495
+
496
+ blockHolder.style.setProperty('--drop-indicator-depth', String(targetDepth));
497
+
498
+ // Announce drop position change to screen readers
499
+ this.announceDropPosition();
500
+ }
501
+
502
+ /**
503
+ * Announces the current drop position to screen readers
504
+ * Throttled to avoid overwhelming screen readers with rapid announcements
505
+ * Only announces if the position has changed since the last announcement
506
+ */
507
+ private announceDropPosition(): void {
508
+ if (!this.dragState?.targetBlock || !this.dragState.targetEdge) {
509
+ return;
510
+ }
511
+
512
+ const targetIndex = this.Blok.BlockManager.getBlockIndex(this.dragState.targetBlock);
513
+ const dropIndex = this.dragState.targetEdge === 'top' ? targetIndex : targetIndex + 1;
514
+
515
+ // Don't announce if position hasn't changed
516
+ if (this.dragState.lastAnnouncedDropIndex === dropIndex) {
517
+ return;
518
+ }
519
+
520
+ // Store the pending announcement
521
+ this.dragState.pendingAnnouncementIndex = dropIndex;
522
+
523
+ // If there's already a pending timeout, let it handle the announcement
524
+ if (this.dragState.announcementTimeoutId !== null) {
525
+ return;
526
+ }
527
+
528
+ // Schedule the announcement with throttling
529
+ this.dragState.announcementTimeoutId = setTimeout(() => {
530
+ if (!this.dragState || this.dragState.pendingAnnouncementIndex === null) {
531
+ return;
532
+ }
533
+
534
+ const pendingIndex = this.dragState.pendingAnnouncementIndex;
535
+
536
+ // Clear the timeout state
537
+ this.dragState.announcementTimeoutId = null;
538
+ this.dragState.pendingAnnouncementIndex = null;
539
+
540
+ // Don't announce if it's the same as what we already announced
541
+ if (this.dragState.lastAnnouncedDropIndex === pendingIndex) {
542
+ return;
543
+ }
544
+
545
+ this.dragState.lastAnnouncedDropIndex = pendingIndex;
546
+
547
+ const total = this.Blok.BlockManager.blocks.length;
548
+ const message = this.Blok.I18n.t('a11y.dropPosition', {
549
+ position: pendingIndex + 1,
550
+ total,
551
+ });
552
+
553
+ announce(message, { politeness: 'polite' });
554
+ }, DRAG_CONFIG.announcementThrottleMs);
555
+ }
556
+
557
+ /**
558
+ * Finds the scrollable ancestor of an element
559
+ * @param element - Starting element
560
+ * @returns The scrollable element or null if window should be used
561
+ */
562
+ private findScrollableAncestor(element: HTMLElement | null): HTMLElement | null {
563
+ if (!element || element === document.body) {
564
+ return null;
565
+ }
566
+
567
+ const parent = element.parentElement;
568
+
569
+ if (!parent || parent === document.body) {
570
+ return null;
571
+ }
572
+
573
+ const style = window.getComputedStyle(parent);
574
+ const overflowY = style.overflowY;
575
+ const isScrollable = overflowY === 'auto' || overflowY === 'scroll';
576
+ const canScroll = parent.scrollHeight > parent.clientHeight;
577
+
578
+ if (isScrollable && canScroll) {
579
+ return parent;
580
+ }
581
+
582
+ return this.findScrollableAncestor(parent);
583
+ }
584
+
585
+ /**
586
+ * Handles auto-scrolling when cursor is near viewport edges
587
+ * @param clientY - Cursor Y position
588
+ */
589
+ private handleAutoScroll(clientY: number): void {
590
+ if (!this.dragState) {
591
+ return;
592
+ }
593
+
594
+ // Clear existing auto-scroll
595
+ if (this.dragState.autoScrollInterval !== null) {
596
+ cancelAnimationFrame(this.dragState.autoScrollInterval);
597
+ this.dragState.autoScrollInterval = null;
598
+ }
599
+
600
+ // Determine scroll zones based on viewport
601
+ const viewportHeight = window.innerHeight;
602
+ const scrollUp = clientY < DRAG_CONFIG.autoScrollZone;
603
+ const scrollDown = clientY > viewportHeight - DRAG_CONFIG.autoScrollZone;
604
+
605
+ if (!scrollUp && !scrollDown) {
606
+ return;
607
+ }
608
+
609
+ const { scrollContainer } = this.dragState;
610
+
611
+ const scroll = (): void => {
612
+ if (!this.dragState || !this.dragState.isDragging) {
613
+ return;
614
+ }
615
+
616
+ const direction = scrollUp ? -1 : 1;
617
+ const scrollAmount = direction * DRAG_CONFIG.autoScrollSpeed;
618
+
619
+ if (scrollContainer) {
620
+ scrollContainer.scrollTop += scrollAmount;
621
+ } else {
622
+ window.scrollBy(0, scrollAmount);
623
+ }
624
+
625
+ this.dragState.autoScrollInterval = requestAnimationFrame(scroll);
626
+ };
627
+
628
+ this.dragState.autoScrollInterval = requestAnimationFrame(scroll);
629
+ }
630
+
631
+ /**
632
+ * Handles mouse up to complete or cancel drag
633
+ * @param e - Mouse event
634
+ */
635
+ private onMouseUp(e: MouseEvent): void {
636
+ if (!this.dragState) {
637
+ return;
638
+ }
639
+
640
+ const { isDragging, sourceBlock, sourceBlocks, targetBlock, targetEdge } = this.dragState;
641
+ const canDrop = isDragging && targetBlock !== null && targetEdge !== null;
642
+
643
+ if (!canDrop) {
644
+ this.cleanup();
645
+
646
+ return;
647
+ }
648
+
649
+ // Check Alt key state at drop time to determine operation
650
+ if (e.altKey) {
651
+ void this.handleDuplicate(sourceBlocks, targetBlock, targetEdge);
652
+ } else {
653
+ this.handleDrop(sourceBlock, targetBlock, targetEdge);
654
+ }
655
+
656
+ this.cleanup();
657
+ }
658
+
659
+ /**
660
+ * Handles escape key to cancel drag and Alt key to toggle duplication mode
661
+ * @param e - Keyboard event
662
+ */
663
+ private onKeyDown(e: KeyboardEvent): void {
664
+ if (e.key === 'Escape') {
665
+ this.cleanup(true);
666
+
667
+ return;
668
+ }
669
+
670
+ // Toggle duplication mode on Alt/Option key press
671
+ if (e.key === 'Alt' && this.dragState?.isDragging) {
672
+ this.setDuplicationMode(true);
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Handles Alt key release to toggle off duplication mode
678
+ * @param e - Keyboard event
679
+ */
680
+ private onKeyUp(e: KeyboardEvent): void {
681
+ if (e.key === 'Alt' && this.dragState?.isDragging) {
682
+ this.setDuplicationMode(false);
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Sets duplication mode visual feedback on or off.
688
+ * The actual duplication decision is made by checking e.altKey at drop time,
689
+ * this method only controls the visual indicator for user feedback.
690
+ * @param isDuplicating - Whether duplication mode should be visually indicated
691
+ */
692
+ private setDuplicationMode(isDuplicating: boolean): void {
693
+ if (!this.dragState) {
694
+ return;
695
+ }
696
+
697
+ const wrapper = this.Blok.UI.nodes.wrapper;
698
+
699
+ if (isDuplicating) {
700
+ wrapper.setAttribute(DATA_ATTR.duplicating, 'true');
701
+ } else {
702
+ wrapper.removeAttribute(DATA_ATTR.duplicating);
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Handles the actual block drop
708
+ * @param sourceBlock - Block being dragged
709
+ * @param targetBlock - Block dropped onto
710
+ * @param edge - Edge of target ('top' or 'bottom')
711
+ */
712
+ private handleDrop(sourceBlock: Block, targetBlock: Block, edge: 'top' | 'bottom'): void {
713
+ const { isMultiBlockDrag, sourceBlocks } = this.dragState!;
714
+
715
+ if (isMultiBlockDrag) {
716
+ this.handleMultiBlockDrop(sourceBlocks, targetBlock, edge);
717
+ } else {
718
+ this.handleSingleBlockDrop(sourceBlock, targetBlock, edge);
719
+ }
720
+
721
+ // Announce successful drop to screen readers
722
+ this.announceDropComplete(sourceBlock, sourceBlocks, isMultiBlockDrag);
723
+
724
+ // Re-open toolbar on the dropped block
725
+ this.Blok.Toolbar.skipNextSettingsToggle();
726
+ this.Blok.Toolbar.moveAndOpen(sourceBlock);
727
+ }
728
+
729
+ /**
730
+ * Handles block duplication instead of move
731
+ * @param sourceBlocks - Blocks to duplicate
732
+ * @param targetBlock - Block to insert duplicates near
733
+ * @param edge - Edge of target ('top' or 'bottom')
734
+ */
735
+ private async handleDuplicate(
736
+ sourceBlocks: Block[],
737
+ targetBlock: Block,
738
+ edge: 'top' | 'bottom'
739
+ ): Promise<void> {
740
+ const manager = this.Blok.BlockManager;
741
+
742
+ // Sort blocks by current index to preserve order
743
+ const sortedBlocks = [...sourceBlocks].sort((a, b) =>
744
+ manager.getBlockIndex(a) - manager.getBlockIndex(b)
745
+ );
746
+
747
+ // Calculate target insertion point
748
+ const targetIndex = manager.getBlockIndex(targetBlock);
749
+ const baseInsertIndex = edge === 'top' ? targetIndex : targetIndex + 1;
750
+
751
+ // Save all blocks concurrently and filter out failures
752
+ const saveResults = await Promise.all(
753
+ sortedBlocks.map(async (block) => {
754
+ const saved = await block.save();
755
+
756
+ if (!saved) {
757
+ return null;
758
+ }
759
+
760
+ return {
761
+ saved,
762
+ toolName: block.name,
763
+ };
764
+ })
765
+ );
766
+
767
+ const validResults = saveResults.filter(
768
+ (result): result is NonNullable<typeof result> => result !== null
769
+ );
770
+
771
+ if (validResults.length === 0) {
772
+ return;
773
+ }
774
+
775
+ // Insert duplicated blocks
776
+ const duplicatedBlocks = validResults.map(({ saved, toolName }, index) =>
777
+ manager.insert({
778
+ tool: toolName,
779
+ data: saved.data,
780
+ tunes: saved.tunes,
781
+ index: baseInsertIndex + index,
782
+ needToFocus: false,
783
+ })
784
+ );
785
+
786
+ // Announce duplication to screen readers
787
+ this.announceDuplicateComplete(duplicatedBlocks);
788
+
789
+ // Select all duplicated blocks
790
+ this.Blok.BlockSelection.clearSelection();
791
+ duplicatedBlocks.forEach(block => {
792
+ this.Blok.BlockSelection.selectBlock(block);
793
+ });
794
+
795
+ // Re-open toolbar on the first duplicated block
796
+ if (duplicatedBlocks.length > 0) {
797
+ this.Blok.Toolbar.skipNextSettingsToggle();
798
+ this.Blok.Toolbar.moveAndOpen(duplicatedBlocks[0]);
799
+ }
800
+ }
801
+
802
+ /**
803
+ * Announces that a duplication operation has completed
804
+ * @param duplicatedBlocks - The blocks that were duplicated
805
+ */
806
+ private announceDuplicateComplete(duplicatedBlocks: Block[]): void {
807
+ const firstBlock = duplicatedBlocks[0];
808
+
809
+ if (!firstBlock) {
810
+ return;
811
+ }
812
+
813
+ const newIndex = this.Blok.BlockManager.getBlockIndex(firstBlock);
814
+ const count = duplicatedBlocks.length;
815
+
816
+ if (count > 1) {
817
+ const message = this.Blok.I18n.t('a11y.blocksDuplicated', {
818
+ count,
819
+ position: newIndex + 1,
820
+ });
821
+
822
+ announce(message, { politeness: 'assertive' });
823
+ } else {
824
+ const total = this.Blok.BlockManager.blocks.length;
825
+ const message = this.Blok.I18n.t('a11y.blockDuplicated', {
826
+ position: newIndex + 1,
827
+ total,
828
+ });
829
+
830
+ announce(message, { politeness: 'assertive' });
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Announces that a drop operation has completed
836
+ * @param sourceBlock - The primary block that was dropped
837
+ * @param sourceBlocks - All blocks that were dropped
838
+ * @param isMultiBlockDrag - Whether this was a multi-block drag
839
+ */
840
+ private announceDropComplete(sourceBlock: Block, sourceBlocks: Block[], isMultiBlockDrag: boolean): void {
841
+ const newIndex = this.Blok.BlockManager.getBlockIndex(sourceBlock);
842
+ const total = this.Blok.BlockManager.blocks.length;
843
+
844
+ if (isMultiBlockDrag) {
845
+ const message = this.Blok.I18n.t('a11y.blocksMoved', {
846
+ count: sourceBlocks.length,
847
+ position: newIndex + 1,
848
+ });
849
+
850
+ announce(message, { politeness: 'assertive' });
851
+ } else {
852
+ const message = this.Blok.I18n.t('a11y.blockMoved', {
853
+ position: newIndex + 1,
854
+ total,
855
+ });
856
+
857
+ announce(message, { politeness: 'assertive' });
858
+ }
859
+ }
860
+
861
+ /**
862
+ * Handles dropping a single block
863
+ * @param sourceBlock - Block being dragged
864
+ * @param targetBlock - Block dropped onto
865
+ * @param edge - Edge of target ('top' or 'bottom')
866
+ */
867
+ private handleSingleBlockDrop(sourceBlock: Block, targetBlock: Block, edge: 'top' | 'bottom'): void {
868
+ const fromIndex = this.Blok.BlockManager.getBlockIndex(sourceBlock);
869
+ const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
870
+
871
+ // Calculate the new index based on drop position
872
+ const baseIndex = edge === 'top' ? targetIndex : targetIndex + 1;
873
+
874
+ // Adjust index if moving from before the target
875
+ const toIndex = fromIndex < baseIndex ? baseIndex - 1 : baseIndex;
876
+
877
+ // Only move if position actually changed
878
+ if (fromIndex === toIndex) {
879
+ return;
880
+ }
881
+
882
+ this.Blok.BlockManager.move(toIndex, fromIndex, false);
883
+
884
+ // Select the moved block to provide visual feedback
885
+ const movedBlock = this.Blok.BlockManager.getBlockByIndex(toIndex);
886
+
887
+ if (!movedBlock) {
888
+ return;
889
+ }
890
+
891
+ this.Blok.BlockSelection.selectBlock(movedBlock);
892
+ }
893
+
894
+ /**
895
+ * Handles dropping multiple selected blocks
896
+ * @param sourceBlocks - Array of blocks being dragged
897
+ * @param targetBlock - Block dropped onto
898
+ * @param edge - Edge of target ('top' or 'bottom')
899
+ */
900
+ private handleMultiBlockDrop(
901
+ sourceBlocks: Block[],
902
+ targetBlock: Block,
903
+ edge: 'top' | 'bottom'
904
+ ): void {
905
+ const manager = this.Blok.BlockManager;
906
+
907
+ // Sort blocks by current index
908
+ const sortedBlocks = [...sourceBlocks].sort((a, b) =>
909
+ manager.getBlockIndex(a) - manager.getBlockIndex(b)
910
+ );
911
+
912
+ // Calculate target insertion point
913
+ const targetIndex = manager.getBlockIndex(targetBlock);
914
+ const insertIndex = edge === 'top' ? targetIndex : targetIndex + 1;
915
+
916
+ // Determine if we're moving blocks up or down
917
+ const firstBlockIndex = manager.getBlockIndex(sortedBlocks[0]);
918
+ const movingDown = insertIndex > firstBlockIndex;
919
+
920
+ // Execute the move based on direction
921
+ if (movingDown) {
922
+ this.moveBlocksDown(sortedBlocks, insertIndex);
923
+ } else {
924
+ this.moveBlocksUp(sortedBlocks, insertIndex);
925
+ }
926
+
927
+ // Clear selection first, then re-select all moved blocks
928
+ this.Blok.BlockSelection.clearSelection();
929
+ sortedBlocks.forEach(block => {
930
+ this.Blok.BlockSelection.selectBlock(block);
931
+ });
932
+ }
933
+
934
+ /**
935
+ * Moves blocks down (to a higher index)
936
+ * @param sortedBlocks - Blocks sorted by current index
937
+ * @param insertIndex - Target insertion index
938
+ */
939
+ private moveBlocksDown(sortedBlocks: Block[], insertIndex: number): void {
940
+ const manager = this.Blok.BlockManager;
941
+
942
+ // When moving down, start with insertIndex - 1 and decrement for each block
943
+ // This ensures blocks maintain their relative order
944
+ const reversedBlocks = [...sortedBlocks].reverse();
945
+
946
+ reversedBlocks.forEach((block, index) => {
947
+ const currentIndex = manager.getBlockIndex(block);
948
+ const targetPosition = insertIndex - 1 - index;
949
+
950
+ manager.move(targetPosition, currentIndex, false);
951
+ });
952
+ }
953
+
954
+ /**
955
+ * Moves blocks up (to a lower index)
956
+ * @param sortedBlocks - Blocks sorted by current index
957
+ * @param baseInsertIndex - Base target insertion index
958
+ */
959
+ private moveBlocksUp(sortedBlocks: Block[], baseInsertIndex: number): void {
960
+ const manager = this.Blok.BlockManager;
961
+
962
+ // Track how many blocks we've inserted to adjust the target index
963
+ sortedBlocks.forEach((block, index) => {
964
+ const currentIndex = manager.getBlockIndex(block);
965
+ const targetIndex = baseInsertIndex + index;
966
+
967
+ if (currentIndex === targetIndex) {
968
+ return;
969
+ }
970
+
971
+ manager.move(targetIndex, currentIndex, false);
972
+ });
973
+ }
974
+
975
+ /**
976
+ * Cleans up drag state and event listeners
977
+ * @param wasCancelled - Whether the drag was cancelled (not dropped)
978
+ */
979
+ private cleanup(wasCancelled = false): void {
980
+ if (!this.dragState) {
981
+ return;
982
+ }
983
+
984
+ // Announce cancellation to screen readers if drag was in progress and cancelled
985
+ if (wasCancelled && this.dragState.isDragging) {
986
+ announce(
987
+ this.Blok.I18n.t('a11y.dropCancelled'),
988
+ { politeness: 'polite' }
989
+ );
990
+ }
991
+
992
+ // Clear auto-scroll
993
+ if (this.dragState.autoScrollInterval !== null) {
994
+ cancelAnimationFrame(this.dragState.autoScrollInterval);
995
+ }
996
+
997
+ // Clear pending announcement timeout
998
+ if (this.dragState.announcementTimeoutId !== null) {
999
+ clearTimeout(this.dragState.announcementTimeoutId);
1000
+ }
1001
+
1002
+ // Clear drop indicator
1003
+ if (this.dragState.targetBlock) {
1004
+ this.dragState.targetBlock.holder.removeAttribute('data-drop-indicator');
1005
+ this.dragState.targetBlock.holder.style.removeProperty('--drop-indicator-depth');
1006
+ }
1007
+
1008
+ // Remove preview
1009
+ if (this.dragState.previewElement.parentNode) {
1010
+ this.dragState.previewElement.remove();
1011
+ }
1012
+
1013
+ // Remove global dragging state
1014
+ const wrapper = this.Blok.UI.nodes.wrapper;
1015
+
1016
+ wrapper.removeAttribute(DATA_ATTR.dragging);
1017
+ wrapper.removeAttribute(DATA_ATTR.draggingMulti);
1018
+ wrapper.removeAttribute(DATA_ATTR.duplicating);
1019
+
1020
+ // Remove event listeners
1021
+ if (this.boundHandlers) {
1022
+ document.removeEventListener('mousemove', this.boundHandlers.onMouseMove);
1023
+ document.removeEventListener('mouseup', this.boundHandlers.onMouseUp);
1024
+ document.removeEventListener('keydown', this.boundHandlers.onKeyDown);
1025
+ document.removeEventListener('keyup', this.boundHandlers.onKeyUp);
1026
+ this.boundHandlers = null;
1027
+ }
1028
+
1029
+ this.dragState = null;
1030
+ }
1031
+
1032
+ /**
1033
+ * Gets the depth of a list item block from its DOM.
1034
+ * Returns null if the block is not a list item.
1035
+ * @param block - Block to check
1036
+ * @returns Depth number or null if not a list item
1037
+ */
1038
+ private getListItemDepth(block: Block): number | null {
1039
+ const listWrapper = block.holder.querySelector('[data-list-depth]');
1040
+
1041
+ if (!listWrapper) {
1042
+ return null;
1043
+ }
1044
+
1045
+ const depthAttr = listWrapper.getAttribute('data-list-depth');
1046
+
1047
+ return depthAttr ? parseInt(depthAttr, 10) : 0;
1048
+ }
1049
+
1050
+ /**
1051
+ * Calculates the target depth for a block dropped at the given position.
1052
+ * This determines what nesting level the block will have after being dropped.
1053
+ * @param targetBlock - Block being dropped onto
1054
+ * @param targetEdge - Edge of target ('top' or 'bottom')
1055
+ * @returns The target depth (0 for root level, 1+ for nested)
1056
+ */
1057
+ private calculateTargetDepth(targetBlock: Block, targetEdge: 'top' | 'bottom'): number {
1058
+ const targetIndex = this.Blok.BlockManager.getBlockIndex(targetBlock);
1059
+ const dropIndex = targetEdge === 'top' ? targetIndex : targetIndex + 1;
1060
+
1061
+ // First position always has depth 0
1062
+ if (dropIndex === 0) {
1063
+ return 0;
1064
+ }
1065
+
1066
+ // Get the block that will be immediately before the drop position
1067
+ const previousBlock = this.Blok.BlockManager.getBlockByIndex(dropIndex - 1);
1068
+
1069
+ if (!previousBlock) {
1070
+ return 0;
1071
+ }
1072
+
1073
+ const previousDepth = this.getListItemDepth(previousBlock) ?? 0;
1074
+
1075
+ // Get the block that will be immediately after the drop position
1076
+ const nextBlock = this.Blok.BlockManager.getBlockByIndex(dropIndex);
1077
+ const nextDepth = nextBlock ? (this.getListItemDepth(nextBlock) ?? 0) : 0;
1078
+
1079
+ // If next item is nested, match its depth (become sibling)
1080
+ if (nextDepth > 0 && nextDepth <= previousDepth + 1) {
1081
+ return nextDepth;
1082
+ }
1083
+
1084
+ // If previous item is nested, match its depth
1085
+ if (previousDepth > 0) {
1086
+ return previousDepth;
1087
+ }
1088
+
1089
+ return 0;
1090
+ }
1091
+
1092
+ /**
1093
+ * Gets all descendant list items of a block (direct children and their descendants).
1094
+ * Only includes items that are strictly deeper than the dragged item.
1095
+ * Stops when encountering a sibling (same depth) or parent (shallower depth).
1096
+ * @param block - Parent block to find descendants for
1097
+ * @returns Array of descendant blocks (empty if block is not a list item or has no descendants)
1098
+ */
1099
+ private getListItemDescendants(block: Block): Block[] {
1100
+ const parentDepth = this.getListItemDepth(block);
1101
+
1102
+ if (parentDepth === null) {
1103
+ return [];
1104
+ }
1105
+
1106
+ const blockIndex = this.Blok.BlockManager.getBlockIndex(block);
1107
+ const totalBlocks = this.Blok.BlockManager.blocks.length;
1108
+
1109
+ const collectDescendants = (index: number, acc: Block[]): Block[] => {
1110
+ if (index >= totalBlocks) {
1111
+ return acc;
1112
+ }
1113
+
1114
+ const nextBlock = this.Blok.BlockManager.getBlockByIndex(index);
1115
+
1116
+ if (!nextBlock) {
1117
+ return acc;
1118
+ }
1119
+
1120
+ const nextDepth = this.getListItemDepth(nextBlock);
1121
+
1122
+ // Stop if not a list item or depth <= parent depth (sibling or shallower level)
1123
+ // A sibling is an item at the same depth - it's not a child of the dragged item
1124
+ if (nextDepth === null || nextDepth <= parentDepth) {
1125
+ return acc;
1126
+ }
1127
+
1128
+ // Only include items strictly deeper than the parent (children, grandchildren, etc.)
1129
+ return collectDescendants(index + 1, [...acc, nextBlock]);
1130
+ };
1131
+
1132
+ return collectDescendants(blockIndex + 1, []);
1133
+ }
1134
+
1135
+ /**
1136
+ * Module destruction
1137
+ */
1138
+ public destroy(): void {
1139
+ this.cleanup();
1140
+ }
1141
+ }