@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,773 @@
1
+ import { array, isNumber, isString } from './utils';
2
+
3
+ /**
4
+ * DOM manipulations helper
5
+ * @todo get rid of class and make separate utility functions
6
+ */
7
+ export class Dom {
8
+ /**
9
+ * Check if passed tag has no closed tag
10
+ * @param {HTMLElement} tag - element to check
11
+ * @returns {boolean}
12
+ */
13
+ public static isSingleTag(tag: HTMLElement): boolean {
14
+ return Boolean(tag.tagName) && [
15
+ 'AREA',
16
+ 'BASE',
17
+ 'BR',
18
+ 'COL',
19
+ 'COMMAND',
20
+ 'EMBED',
21
+ 'HR',
22
+ 'IMG',
23
+ 'INPUT',
24
+ 'KEYGEN',
25
+ 'LINK',
26
+ 'META',
27
+ 'PARAM',
28
+ 'SOURCE',
29
+ 'TRACK',
30
+ 'WBR',
31
+ ].includes(tag.tagName);
32
+ }
33
+
34
+ /**
35
+ * Check if element is BR or WBR
36
+ * @param {HTMLElement} element - element to check
37
+ * @returns {boolean}
38
+ */
39
+ public static isLineBreakTag(element: HTMLElement): element is HTMLBRElement {
40
+ return !!element && ['BR', 'WBR'].includes(element.tagName);
41
+ }
42
+
43
+ /**
44
+ * Checks if a class name is valid for use with classList.add()
45
+ * classList.add() throws if class contains whitespace, is empty, or contains invalid characters
46
+ * @param className - class name to validate
47
+ * @returns {boolean} - true if valid for classList.add()
48
+ */
49
+ private static isValidClassName(className: string): boolean {
50
+ if (className === '' || /\s/.test(className)) {
51
+ return false;
52
+ }
53
+
54
+ /**
55
+ * Try to validate by creating a temporary element and using classList.add
56
+ * This is more reliable than regex because it follows the actual browser implementation
57
+ */
58
+ try {
59
+ const testEl = document.createElement('div');
60
+
61
+ testEl.classList.add(className);
62
+
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Safely adds class names to an element, filtering out invalid ones
71
+ * @param element - element to add classes to
72
+ * @param classNames - array of class names to add
73
+ */
74
+ private static safelyAddClasses(element: HTMLElement, classNames: string[]): void {
75
+ const validClasses: string[] = [];
76
+ const invalidClasses: string[] = [];
77
+
78
+ for (const className of classNames) {
79
+ if (Dom.isValidClassName(className)) {
80
+ validClasses.push(className);
81
+ } else {
82
+ invalidClasses.push(className);
83
+ }
84
+ }
85
+
86
+ if (validClasses.length > 0) {
87
+ element.classList.add(...validClasses);
88
+ }
89
+
90
+ /**
91
+ * For invalid class names (e.g. Tailwind arbitrary values with brackets/parentheses),
92
+ * we need to set them via className attribute directly
93
+ */
94
+ if (invalidClasses.length > 0) {
95
+ const existingClasses = element.className;
96
+ const allClasses = existingClasses
97
+ ? `${existingClasses} ${invalidClasses.join(' ')}`
98
+ : invalidClasses.join(' ');
99
+
100
+ element.setAttribute('class', allClasses);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Helper for making Elements with class name and attributes
106
+ * @param {string} tagName - new Element tag name
107
+ * @param {string[]|string} [classNames] - list or name of CSS class name(s)
108
+ * @param {object} [attributes] - any attributes
109
+ * @returns {HTMLElement}
110
+ */
111
+ public static make(tagName: string, classNames: string | (string | undefined)[] | null = null, attributes: Record<string, string | number | boolean | null | undefined> = {}): HTMLElement {
112
+ const el = document.createElement(tagName);
113
+
114
+ if (Array.isArray(classNames)) {
115
+ const validClassnames = classNames
116
+ .filter((className): className is string => className !== undefined && className !== '')
117
+ .flatMap((className) => className.split(' '))
118
+ .filter((className) => className !== '');
119
+
120
+ Dom.safelyAddClasses(el, validClassnames);
121
+ }
122
+
123
+ if (typeof classNames === 'string' && classNames !== '') {
124
+ const splitClassNames = classNames.split(' ').filter((className) => className !== '');
125
+
126
+ Dom.safelyAddClasses(el, splitClassNames);
127
+ }
128
+
129
+ for (const attrName in attributes) {
130
+ if (!Object.prototype.hasOwnProperty.call(attributes, attrName)) {
131
+ continue;
132
+ }
133
+
134
+ const value = attributes[attrName];
135
+
136
+ if (value === undefined || value === null) {
137
+ continue;
138
+ }
139
+
140
+ if (attrName in el) {
141
+ (el as unknown as Record<string, unknown>)[attrName] = value;
142
+
143
+ continue;
144
+ }
145
+
146
+ el.setAttribute(attrName, String(value));
147
+ }
148
+
149
+ return el;
150
+ }
151
+
152
+ /**
153
+ * Creates Text Node with the passed content
154
+ * @param {string} content - text content
155
+ * @returns {Text}
156
+ */
157
+ public static text(content: string): Text {
158
+ return document.createTextNode(content);
159
+ }
160
+
161
+ /**
162
+ * Append one or several elements to the parent
163
+ * @param {Element|DocumentFragment} parent - where to append
164
+ * @param {Element|Element[]|DocumentFragment|Text|Text[]} elements - element or elements list
165
+ */
166
+ public static append(
167
+ parent: Element | DocumentFragment,
168
+ elements: Element | Element[] | DocumentFragment | Text | Text[]
169
+ ): void {
170
+ if (Array.isArray(elements)) {
171
+ elements.forEach((el) => parent.appendChild(el));
172
+ } else {
173
+ parent.appendChild(elements);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Append element or a couple to the beginning of the parent elements
179
+ * @param {Element} parent - where to append
180
+ * @param {Element|Element[]} elements - element or elements list
181
+ */
182
+ public static prepend(parent: Element, elements: Element | Element[]): void {
183
+ if (Array.isArray(elements)) {
184
+ const reversedElements = [ ...elements ].reverse();
185
+
186
+ reversedElements.forEach((el) => parent.prepend(el));
187
+ } else {
188
+ parent.prepend(elements);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Selector Decorator
194
+ *
195
+ * Returns first match
196
+ * @param {Element} el - element we searching inside. Default - DOM Document
197
+ * @param {string} selector - searching string
198
+ * @returns {Element}
199
+ */
200
+ public static find(el: Element | Document = document, selector: string): Element | null {
201
+ return el.querySelector(selector);
202
+ }
203
+
204
+ /**
205
+ * Get Element by Id
206
+ * @param {string} id - id to find
207
+ * @returns {HTMLElement | null}
208
+ */
209
+ public static get(id: string): HTMLElement | null {
210
+ return document.getElementById(id);
211
+ }
212
+
213
+ /**
214
+ * Selector Decorator.
215
+ *
216
+ * Returns all matches
217
+ * @param {Element|Document} el - element we searching inside. Default - DOM Document
218
+ * @param {string} selector - searching string
219
+ * @returns {NodeList}
220
+ */
221
+ public static findAll(el: Element | Document = document, selector: string): NodeList {
222
+ return el.querySelectorAll(selector);
223
+ }
224
+
225
+ /**
226
+ * Returns CSS selector for all text inputs
227
+ */
228
+ public static get allInputsSelector(): string {
229
+ const allowedInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url'];
230
+
231
+ return '[contenteditable=true], textarea, input:not([type]), ' +
232
+ allowedInputTypes.map((type) => `input[type="${type}"]`).join(', ');
233
+ }
234
+
235
+ /**
236
+ * Find all contenteditable, textarea and editable input elements passed holder contains
237
+ * @param holder - element where to find inputs
238
+ */
239
+ public static findAllInputs(holder: Element): HTMLElement[] {
240
+ return array(holder.querySelectorAll(Dom.allInputsSelector))
241
+ /**
242
+ * If contenteditable element contains block elements, treat them as inputs.
243
+ */
244
+ .reduce((result, input) => {
245
+ if (Dom.isNativeInput(input) || Dom.containsOnlyInlineElements(input)) {
246
+ return [...result, input];
247
+ }
248
+
249
+ return [...result, ...Dom.getDeepestBlockElements(input)];
250
+ }, []);
251
+ }
252
+
253
+ /**
254
+ * Search for deepest node which is Leaf.
255
+ * Leaf is the vertex that doesn't have any child nodes
256
+ * @description Method recursively goes throw the all Node until it finds the Leaf
257
+ * @param {Node} node - root Node. From this vertex we start Deep-first search
258
+ * {@link https://en.wikipedia.org/wiki/Depth-first_search}
259
+ * @param {boolean} [atLast] - find last text node
260
+ * @returns - it can be text Node or Element Node, so that caret will able to work with it
261
+ * Can return null if node is Document or DocumentFragment, or node is not attached to the DOM
262
+ */
263
+ public static getDeepestNode(node: Node | null, atLast = false): Node | null {
264
+ /**
265
+ * Current function have two directions:
266
+ * - starts from first child and every time gets first or nextSibling in special cases
267
+ * - starts from last child and gets last or previousSibling
268
+ * @type {string}
269
+ */
270
+ const child: 'lastChild' | 'firstChild' = atLast ? 'lastChild' : 'firstChild';
271
+ const sibling: 'previousSibling' | 'nextSibling' = atLast ? 'previousSibling' : 'nextSibling';
272
+
273
+ if (node === null || node.nodeType !== Node.ELEMENT_NODE) {
274
+ return node;
275
+ }
276
+
277
+ const nodeChildProperty = node[child];
278
+
279
+ if (nodeChildProperty === null) {
280
+ return node;
281
+ }
282
+
283
+ const nodeChild = nodeChildProperty as Node;
284
+ const shouldSkipChild = Dom.isSingleTag(nodeChild as HTMLElement) &&
285
+ !Dom.isNativeInput(nodeChild) &&
286
+ !Dom.isLineBreakTag(nodeChild as HTMLElement);
287
+
288
+ if (!shouldSkipChild) {
289
+ return this.getDeepestNode(nodeChild, atLast);
290
+ }
291
+
292
+ const siblingNode = nodeChild[sibling];
293
+
294
+ if (siblingNode) {
295
+ return this.getDeepestNode(siblingNode, atLast);
296
+ }
297
+
298
+ const parentSiblingNode = nodeChild.parentNode?.[sibling];
299
+
300
+ if (parentSiblingNode) {
301
+ return this.getDeepestNode(parentSiblingNode, atLast);
302
+ }
303
+
304
+ return nodeChild.parentNode;
305
+ }
306
+
307
+ /**
308
+ * Check if object is DOM node
309
+ * @param {*} node - object to check
310
+ * @returns {boolean}
311
+ */
312
+
313
+ public static isElement(node: any): node is Element {
314
+ if (isNumber(node)) {
315
+ return false;
316
+ }
317
+
318
+ return node != null && node.nodeType != null && node.nodeType === Node.ELEMENT_NODE;
319
+ }
320
+
321
+ /**
322
+ * Check if object is DocumentFragment node
323
+ * @param {object} node - object to check
324
+ * @returns {boolean}
325
+ */
326
+
327
+ public static isFragment(node: any): node is DocumentFragment {
328
+ if (isNumber(node)) {
329
+ return false;
330
+ }
331
+
332
+ return node != null && node.nodeType != null && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
333
+ }
334
+
335
+ /**
336
+ * Check if passed element is contenteditable
337
+ * @param {HTMLElement} element - html element to check
338
+ * @returns {boolean}
339
+ */
340
+ public static isContentEditable(element: HTMLElement): boolean {
341
+ return element.contentEditable === 'true';
342
+ }
343
+
344
+ /**
345
+ * Checks target if it is native input
346
+ * @param {*} target - HTML element or string
347
+ * @returns {boolean}
348
+ */
349
+
350
+ public static isNativeInput(target: any): target is HTMLInputElement | HTMLTextAreaElement {
351
+ const nativeInputs = [
352
+ 'INPUT',
353
+ 'TEXTAREA',
354
+ ];
355
+
356
+ return target != null && typeof target.tagName === 'string' ? nativeInputs.includes(target.tagName) : false;
357
+ }
358
+
359
+ /**
360
+ * Checks if we can set caret
361
+ * @param {HTMLElement} target - target to check
362
+ * @returns {boolean}
363
+ */
364
+ public static canSetCaret(target: HTMLElement): boolean {
365
+ if (Dom.isNativeInput(target)) {
366
+ const disallowedTypes = new Set([
367
+ 'file',
368
+ 'checkbox',
369
+ 'radio',
370
+ 'hidden',
371
+ 'submit',
372
+ 'button',
373
+ 'image',
374
+ 'reset',
375
+ ]);
376
+
377
+ return !disallowedTypes.has(target.type);
378
+ }
379
+
380
+ return Dom.isContentEditable(target);
381
+ }
382
+
383
+ /**
384
+ * Checks node if it is empty
385
+ * @description Method checks simple Node without any childs for emptiness
386
+ * If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method
387
+ * @param {Node} node - node to check
388
+ * @param {string} [ignoreChars] - char or substring to treat as empty
389
+ * @returns {boolean} true if it is empty
390
+ */
391
+ public static isNodeEmpty(node: Node, ignoreChars?: string): boolean {
392
+ if (this.isSingleTag(node as HTMLElement) && !this.isLineBreakTag(node as HTMLElement)) {
393
+ return false;
394
+ }
395
+
396
+ const baseText = this.isElement(node) && this.isNativeInput(node)
397
+ ? (node as HTMLInputElement).value
398
+ : node.textContent?.replace('\u200B', '');
399
+ const normalizedText = ignoreChars
400
+ ? baseText?.replace(new RegExp(ignoreChars, 'g'), '')
401
+ : baseText;
402
+
403
+ return (normalizedText?.length ?? 0) === 0;
404
+ }
405
+
406
+ /**
407
+ * checks node if it is doesn't have any child nodes
408
+ * @param {Node} node - node to check
409
+ * @returns {boolean}
410
+ */
411
+ public static isLeaf(node: Node): boolean {
412
+ if (!node) {
413
+ return false;
414
+ }
415
+
416
+ return node.childNodes.length === 0;
417
+ }
418
+
419
+ /**
420
+ * breadth-first search (BFS)
421
+ * {@link https://en.wikipedia.org/wiki/Breadth-first_search}
422
+ * @description Pushes to stack all DOM leafs and checks for emptiness
423
+ * @param {Node} node - node to check
424
+ * @param {string} [ignoreChars] - char or substring to treat as empty
425
+ * @returns {boolean}
426
+ */
427
+ public static isEmpty(node: Node, ignoreChars?: string): boolean {
428
+ const treeWalker = [ node ];
429
+
430
+ while (treeWalker.length > 0) {
431
+ const currentNode = treeWalker.shift();
432
+
433
+ if (!currentNode) {
434
+ continue;
435
+ }
436
+
437
+ if (this.isLeaf(currentNode) && !this.isNodeEmpty(currentNode, ignoreChars)) {
438
+ return false;
439
+ }
440
+
441
+ if (currentNode.childNodes) {
442
+ treeWalker.push(...Array.from(currentNode.childNodes));
443
+ }
444
+ }
445
+
446
+ return true;
447
+ }
448
+
449
+ /**
450
+ * Check if string contains html elements
451
+ * @param {string} str - string to check
452
+ * @returns {boolean}
453
+ */
454
+ public static isHTMLString(str: string): boolean {
455
+ const wrapper = Dom.make('div');
456
+
457
+ wrapper.innerHTML = str;
458
+
459
+ return wrapper.childElementCount > 0;
460
+ }
461
+
462
+ /**
463
+ * Return length of node`s text content
464
+ * @param {Node} node - node with content
465
+ * @returns {number}
466
+ */
467
+ public static getContentLength(node: Node): number {
468
+ if (Dom.isNativeInput(node)) {
469
+ return (node as HTMLInputElement).value.length;
470
+ }
471
+
472
+ if (node.nodeType === Node.TEXT_NODE) {
473
+ return (node as Text).length;
474
+ }
475
+
476
+ return node.textContent?.length ?? 0;
477
+ }
478
+
479
+ /**
480
+ * Return array of names of block html elements
481
+ * @returns {string[]}
482
+ */
483
+ public static get blockElements(): string[] {
484
+ return [
485
+ 'address',
486
+ 'article',
487
+ 'aside',
488
+ 'blockquote',
489
+ 'canvas',
490
+ 'div',
491
+ 'dl',
492
+ 'dt',
493
+ 'fieldset',
494
+ 'figcaption',
495
+ 'figure',
496
+ 'footer',
497
+ 'form',
498
+ 'h1',
499
+ 'h2',
500
+ 'h3',
501
+ 'h4',
502
+ 'h5',
503
+ 'h6',
504
+ 'header',
505
+ 'hgroup',
506
+ 'hr',
507
+ 'li',
508
+ 'main',
509
+ 'nav',
510
+ 'noscript',
511
+ 'ol',
512
+ 'output',
513
+ 'p',
514
+ 'pre',
515
+ 'ruby',
516
+ 'section',
517
+ 'table',
518
+ 'tbody',
519
+ 'thead',
520
+ 'tr',
521
+ 'tfoot',
522
+ 'ul',
523
+ 'video',
524
+ ];
525
+ }
526
+
527
+ /**
528
+ * Check if passed content includes only inline elements
529
+ * @param {string|HTMLElement} data - element or html string
530
+ * @returns {boolean}
531
+ */
532
+ public static containsOnlyInlineElements(data: string | HTMLElement): boolean {
533
+ const wrapper = isString(data)
534
+ ? (() => {
535
+ const container = document.createElement('div');
536
+
537
+ container.innerHTML = data;
538
+
539
+ return container;
540
+ })()
541
+ : data;
542
+
543
+ const check = (element: Element): boolean => {
544
+ return !Dom.blockElements.includes(element.tagName.toLowerCase()) &&
545
+ Array.from(element.children).every(check);
546
+ };
547
+
548
+ return Array.from(wrapper.children).every(check);
549
+ }
550
+
551
+ /**
552
+ * Find and return all block elements in the passed parent (including subtree)
553
+ * @param {HTMLElement} parent - root element
554
+ * @returns {HTMLElement[]}
555
+ */
556
+ public static getDeepestBlockElements(parent: HTMLElement): HTMLElement[] {
557
+ if (Dom.containsOnlyInlineElements(parent)) {
558
+ return [ parent ];
559
+ }
560
+
561
+ return Array.from(parent.children).reduce((result, element) => {
562
+ return [...result, ...Dom.getDeepestBlockElements(element as HTMLElement)];
563
+ }, [] as HTMLElement[]);
564
+ }
565
+
566
+ /**
567
+ * Helper for get holder from {string} or return HTMLElement
568
+ * @param {string | HTMLElement} element - holder's id or holder's HTML Element
569
+ * @returns {HTMLElement}
570
+ */
571
+ public static getHolder(element: string | HTMLElement): HTMLElement {
572
+ if (!isString(element)) {
573
+ return element;
574
+ }
575
+
576
+ const holder = document.getElementById(element);
577
+
578
+ if (holder !== null) {
579
+ return holder;
580
+ }
581
+
582
+ throw new Error(`Element with id "${element}" not found`);
583
+ }
584
+
585
+ /**
586
+ * Returns true if element is anchor (is A tag)
587
+ * @param {Element} element - element to check
588
+ * @returns {boolean}
589
+ */
590
+ public static isAnchor(element: Element): element is HTMLAnchorElement {
591
+ return element.tagName.toLowerCase() === 'a';
592
+ }
593
+
594
+ /**
595
+ * Return element's offset related to the document
596
+ * @todo handle case when blok initialized in scrollable popup
597
+ * @param el - element to compute offset
598
+ */
599
+ public static offset(el: Element): { top: number; left: number; right: number; bottom: number } {
600
+ const rect = el.getBoundingClientRect();
601
+ const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
602
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
603
+
604
+ const top = rect.top + scrollTop;
605
+ const left = rect.left + scrollLeft;
606
+
607
+ return {
608
+ top,
609
+ left,
610
+ bottom: top + rect.height,
611
+ right: left + rect.width,
612
+ };
613
+ }
614
+
615
+ /**
616
+ * Find text node and offset by total content offset
617
+ * @param {Node} root - root node to start search from
618
+ * @param {number} totalOffset - offset relative to the root node content
619
+ * @returns {{node: Node | null, offset: number}} - node and offset inside node
620
+ */
621
+ public static getNodeByOffset(root: Node, totalOffset: number): { node: Node | null; offset: number } {
622
+ const walker = document.createTreeWalker(
623
+ root,
624
+ NodeFilter.SHOW_TEXT,
625
+ null
626
+ );
627
+
628
+ const findNode = (
629
+ nextNode: Node | null,
630
+ accumulatedOffset: number,
631
+ previousNode: Node | null,
632
+ previousNodeLength: number
633
+ ): { node: Node | null; offset: number } => {
634
+ if (!nextNode && previousNode) {
635
+ const baseOffset = accumulatedOffset - previousNodeLength;
636
+ const safeTotalOffset = Math.max(totalOffset - baseOffset, 0);
637
+ const offsetInsidePrevious = Math.min(safeTotalOffset, previousNodeLength);
638
+
639
+ return {
640
+ node: previousNode,
641
+ offset: offsetInsidePrevious,
642
+ };
643
+ }
644
+
645
+ if (!nextNode) {
646
+ return {
647
+ node: null,
648
+ offset: 0,
649
+ };
650
+ }
651
+
652
+ const textContent = nextNode.textContent ?? '';
653
+ const nodeLength = textContent.length;
654
+ const hasReachedOffset = accumulatedOffset + nodeLength >= totalOffset;
655
+
656
+ if (hasReachedOffset) {
657
+ return {
658
+ node: nextNode,
659
+ offset: Math.min(totalOffset - accumulatedOffset, nodeLength),
660
+ };
661
+ }
662
+
663
+ return findNode(
664
+ walker.nextNode(),
665
+ accumulatedOffset + nodeLength,
666
+ nextNode,
667
+ nodeLength
668
+ );
669
+ };
670
+
671
+ const initialNode = walker.nextNode();
672
+ const { node, offset } = findNode(initialNode, 0, null, 0);
673
+
674
+ if (!node) {
675
+ return {
676
+ node: null,
677
+ offset: 0,
678
+ };
679
+ }
680
+
681
+ const textContent = node.textContent;
682
+
683
+ if (!textContent || textContent.length === 0) {
684
+ return {
685
+ node: null,
686
+ offset: 0,
687
+ };
688
+ }
689
+
690
+ return {
691
+ node,
692
+ offset,
693
+ };
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Determine whether a passed text content is a collapsed whitespace.
699
+ *
700
+ * In HTML, whitespaces at the start and end of elements and outside elements are ignored.
701
+ * There are two types of whitespaces in HTML:
702
+ * - Visible (&nbsp;)
703
+ * - Invisible (regular trailing spaces, tabs, etc)
704
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
705
+ * @see https://www.w3.org/TR/css-text-3/#white-space-processing
706
+ * @param textContent — any string, for ex a textContent of a node
707
+ * @returns True if passed text content is whitespace which is collapsed (invisible) in browser
708
+ */
709
+ export const isCollapsedWhitespaces = (textContent: string): boolean => {
710
+ /**
711
+ * Throughout, whitespace is defined as one of the characters
712
+ * "\t" TAB \u0009
713
+ * "\n" LF \u000A
714
+ * "\r" CR \u000D
715
+ * " " SPC \u0020
716
+ *
717
+ * Also \u200B (Zero Width Space) is considered as collapsed whitespace
718
+ */
719
+ return !/[^\t\n\r \u200B]/.test(textContent);
720
+ };
721
+
722
+ /**
723
+ * Calculates the Y coordinate of the text baseline from the top of the element's margin box,
724
+ *
725
+ * The calculation formula is as follows:
726
+ *
727
+ * 1. Calculate the baseline offset:
728
+ * - Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
729
+ *
730
+ * 2. Calculate the additional space due to `lineHeight`:
731
+ * - If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
732
+ *
733
+ * 3. Calculate the total baseline Y coordinate:
734
+ * - Sum of `marginTop`, `borderTopWidth`, `paddingTop`, the extra space due to `lineHeight`, and the baseline offset.
735
+ * @param element - The element to calculate the baseline for.
736
+ * @returns {number} - The Y coordinate of the text baseline from the top of the element's margin box.
737
+ */
738
+ export const calculateBaseline = (element: Element): number => {
739
+ const style = window.getComputedStyle(element);
740
+ const fontSize = parseFloat(style.fontSize);
741
+
742
+ const lineHeight = parseFloat(style.lineHeight) || fontSize * 1.2; // default line-height if not set
743
+ const paddingTop = parseFloat(style.paddingTop);
744
+ const borderTopWidth = parseFloat(style.borderTopWidth);
745
+ const marginTop = parseFloat(style.marginTop);
746
+
747
+ /**
748
+ * Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
749
+ */
750
+
751
+ const baselineOffset = fontSize * 0.8;
752
+
753
+ /**
754
+ * If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
755
+ */
756
+ const extraLineHeight = (lineHeight - fontSize) / 2;
757
+
758
+ /**
759
+ * Calculate the total baseline Y coordinate from the top of the margin box
760
+ */
761
+ const baselineY = marginTop + borderTopWidth + paddingTop + extraLineHeight + baselineOffset;
762
+
763
+ return baselineY;
764
+ };
765
+
766
+ /**
767
+ * Toggles the [data-blok-empty] attribute on element depending on its emptiness
768
+ * Used to mark empty inputs with a special attribute for placeholders feature
769
+ * @param element - The element to toggle the [data-blok-empty] attribute on
770
+ */
771
+ export const toggleEmptyMark = (element: HTMLElement): void => {
772
+ element.setAttribute('data-blok-empty', Dom.isEmpty(element) ? 'true' : 'false');
773
+ };