@jackuait/blok 0.4.1-beta.0 → 0.4.1-beta.11

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