@jackuait/blok 0.10.3 → 0.10.4

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 (284) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-3wc3aInM.mjs → blok-NcdNQ0I6.mjs} +2364 -2178
  3. package/dist/chunks/{constants-Bp622jic.mjs → constants-DtfShkXT.mjs} +318 -227
  4. package/dist/chunks/{i18next-loader-CDnSPae_.mjs → i18next-loader-D32EUWLr.mjs} +1 -1
  5. package/dist/chunks/{lightweight-i18n-DZmo8dAI.mjs → lightweight-i18n-DpkvRXEd.mjs} +19 -0
  6. package/dist/{messages-Ddq3Ce3E2.mjs → chunks/messages-AD17iDBx.mjs} +18 -0
  7. package/dist/{messages-neGD3WGq.mjs → chunks/messages-B03yUEra2.mjs} +18 -0
  8. package/dist/chunks/{messages-CIfUm1Oa.mjs → messages-B2dU00Z3.mjs} +18 -0
  9. package/dist/chunks/{messages-BKN3YVIj.mjs → messages-B8hICx3L.mjs} +18 -0
  10. package/dist/{messages-Dnd5YSWv.mjs → chunks/messages-BBe45sPH.mjs} +18 -0
  11. package/dist/{messages-C7lJg8fy2.mjs → chunks/messages-BCifzMVO2.mjs} +18 -0
  12. package/dist/chunks/{messages-D7dx_6k8.mjs → messages-BGmvvtg_.mjs} +18 -0
  13. package/dist/{messages-Q5sQeVap2.mjs → chunks/messages-BJNFCDv42.mjs} +18 -0
  14. package/dist/chunks/{messages-BlxwW7M6.mjs → messages-BNy4e7Xl.mjs} +18 -0
  15. package/dist/chunks/{messages-C15z2r5U.mjs → messages-BcHZf9o-.mjs} +18 -0
  16. package/dist/{messages-A96tMxeU.mjs → chunks/messages-BjyYZeBm2.mjs} +18 -0
  17. package/dist/{messages-BbJ7ZXY8.mjs → chunks/messages-Bop7vrhU.mjs} +18 -0
  18. package/dist/{messages-BiTMwiKH.mjs → chunks/messages-BouFtpfO.mjs} +18 -0
  19. package/dist/chunks/{messages-ElIGUi0O2.mjs → messages-Br6bE1FD2.mjs} +18 -0
  20. package/dist/chunks/{messages-BHMiK51R.mjs → messages-C-EBhOHE.mjs} +18 -0
  21. package/dist/chunks/{messages-kGmxkeFH.mjs → messages-C3X7dv3f.mjs} +18 -0
  22. package/dist/chunks/{messages-4Ck88DYZ2.mjs → messages-C7Pjof0d2.mjs} +18 -0
  23. package/dist/{messages-D0lLw9KM.mjs → chunks/messages-C7sBaZOO2.mjs} +18 -0
  24. package/dist/chunks/{messages-QMOmwcZb.mjs → messages-C85zv_7x.mjs} +18 -0
  25. package/dist/chunks/{messages-DSrdy9Nw2.mjs → messages-CCEgR9GN2.mjs} +18 -0
  26. package/dist/chunks/{messages-DUr9WAkD.mjs → messages-CDSyoUft.mjs} +18 -0
  27. package/dist/{messages-bkGniiaz.mjs → chunks/messages-CGFlOwst.mjs} +18 -0
  28. package/dist/chunks/{messages-DBMaLL8b2.mjs → messages-CGLTjtRv2.mjs} +18 -0
  29. package/dist/{messages-2ZWBTerL.mjs → chunks/messages-CGPxUESo.mjs} +18 -0
  30. package/dist/chunks/{messages-BfAcUavP.mjs → messages-CNaaqQVz.mjs} +18 -0
  31. package/dist/{messages-DBhvm8NK.mjs → chunks/messages-CPFB2_m-2.mjs} +18 -0
  32. package/dist/chunks/{messages-zt6zdYWh.mjs → messages-CTdSIOAc.mjs} +18 -0
  33. package/dist/chunks/{messages-1_6UkKLS.mjs → messages-CXVWb9js.mjs} +18 -0
  34. package/dist/{messages-CdEASHDp2.mjs → chunks/messages-Cfbmwdep2.mjs} +18 -0
  35. package/dist/chunks/{messages-DxHh0O8j2.mjs → messages-ChayV9WY2.mjs} +18 -0
  36. package/dist/chunks/{messages-BgM91Lxm2.mjs → messages-Ci7UXRVI2.mjs} +18 -0
  37. package/dist/chunks/{messages-Clku7Cf-2.mjs → messages-CpXvyGWv2.mjs} +18 -0
  38. package/dist/chunks/{messages-DjvaiALg2.mjs → messages-Cql2ozf_2.mjs} +18 -0
  39. package/dist/{messages-DODrhcop.mjs → chunks/messages-Cxy_E2IS.mjs} +18 -0
  40. package/dist/chunks/{messages-CZSlfnkO2.mjs → messages-D9syZVGi2.mjs} +18 -0
  41. package/dist/chunks/{messages-BRAoJpOu.mjs → messages-D9uWAWjW.mjs} +18 -0
  42. package/dist/chunks/{messages-BK8Cp2d0.mjs → messages-DRJxSTqs.mjs} +18 -0
  43. package/dist/{messages-C_Qn9SbQ.mjs → chunks/messages-DSbI0vJf.mjs} +18 -0
  44. package/dist/chunks/{messages-CD_MnBln.mjs → messages-DVvrZRyZ.mjs} +18 -0
  45. package/dist/{messages-BE_z-zrb.mjs → chunks/messages-DY8zPIZW.mjs} +18 -0
  46. package/dist/chunks/{messages-Bz0-KNEB.mjs → messages-D_kZN9rB.mjs} +18 -0
  47. package/dist/{messages-C1vc5584.mjs → chunks/messages-DjSuq0-y2.mjs} +18 -0
  48. package/dist/chunks/{messages-DPzHD51Y.mjs → messages-DkP3Jf4F.mjs} +18 -0
  49. package/dist/{messages-_PLyRfVw.mjs → chunks/messages-DoPdy75l.mjs} +18 -0
  50. package/dist/chunks/{messages-JSQjKQ8I.mjs → messages-DpydMd36.mjs} +18 -0
  51. package/dist/{messages-BckDk9aq2.mjs → chunks/messages-DtZ9U9g72.mjs} +18 -0
  52. package/dist/{messages-JNrYldAa2.mjs → chunks/messages-H6vLy8wJ.mjs} +18 -0
  53. package/dist/chunks/{messages-DTN1XGll.mjs → messages-HzH9_QH8.mjs} +18 -0
  54. package/dist/chunks/{messages-C0IFfhnp.mjs → messages-O6FOfUgF.mjs} +18 -0
  55. package/dist/{messages-Be_2RHZD.mjs → chunks/messages-OSP4Hj5o.mjs} +18 -0
  56. package/dist/chunks/{messages-DMoERagV2.mjs → messages-RiqdVwuN2.mjs} +18 -0
  57. package/dist/chunks/{messages-BJ-vT1SU2.mjs → messages-SP659Sal2.mjs} +18 -0
  58. package/dist/{messages-Che99vKP.mjs → chunks/messages-THR8q8bJ.mjs} +18 -0
  59. package/dist/chunks/{messages-CvANwuht2.mjs → messages-VlEyFUxF2.mjs} +18 -0
  60. package/dist/{messages-apA6BStA.mjs → chunks/messages-VtfKWZ2S.mjs} +18 -0
  61. package/dist/{messages-DpJGbx3q.mjs → chunks/messages-YbckahVx2.mjs} +18 -0
  62. package/dist/{messages-DYuD5-rO.mjs → chunks/messages-ZhHLC6dk.mjs} +18 -0
  63. package/dist/{messages-C0GSBBCo2.mjs → chunks/messages-bFEdH3lv.mjs} +18 -0
  64. package/dist/chunks/{messages-euM2m3wQ.mjs → messages-dpXwA3Sz.mjs} +18 -0
  65. package/dist/chunks/{messages-CQBo3lmL2.mjs → messages-fbL5y58u2.mjs} +18 -0
  66. package/dist/chunks/{messages-CxiURE2X.mjs → messages-oPV2oMxM.mjs} +18 -0
  67. package/dist/{messages-DM4Gjc9h.mjs → chunks/messages-oXBbHW9A.mjs} +18 -0
  68. package/dist/chunks/{messages-QilfinOn2.mjs → messages-vDgsEqQW2.mjs} +18 -0
  69. package/dist/{messages-ClGvlFcH2.mjs → chunks/messages-wYQksm10.mjs} +18 -0
  70. package/dist/{messages-CnuH-BZK2.mjs → chunks/messages-yGedmr61.mjs} +18 -0
  71. package/dist/chunks/{messages-sDdNf8O9.mjs → messages-zQOpKjl3.mjs} +18 -0
  72. package/dist/chunks/{messages-eFd4YYzt.mjs → messages-zWqsggJh.mjs} +18 -0
  73. package/dist/chunks/{tools-BC1jRfoS.mjs → tools-DMSi-3RW.mjs} +3445 -1302
  74. package/dist/full.mjs +10 -10
  75. package/dist/locales.mjs +86 -67
  76. package/dist/{messages-BK_LsgY4.mjs → messages-0lOPMv8u.mjs} +18 -0
  77. package/dist/{messages-LYJbLq_F.mjs → messages-5wuR90qS.mjs} +18 -0
  78. package/dist/{messages-98nQiC7t2.mjs → messages-6eX0fWGR2.mjs} +18 -0
  79. package/dist/{chunks/messages-DUeiPraX.mjs → messages-9L4qqCKh2.mjs} +18 -0
  80. package/dist/{chunks/messages-Q7-4ZJLB2.mjs → messages-B4zPxKl62.mjs} +18 -0
  81. package/dist/{messages-D0005ti32.mjs → messages-BCMFYqKc2.mjs} +18 -0
  82. package/dist/{chunks/messages-CC_noR8y.mjs → messages-BKXjO3NH.mjs} +18 -0
  83. package/dist/{messages-CRNogopy2.mjs → messages-BLW2GX7J2.mjs} +18 -0
  84. package/dist/{messages-D81w6AmW.mjs → messages-BLfK27kX.mjs} +18 -0
  85. package/dist/{chunks/messages-CPBN4zWc.mjs → messages-BM2kx9Td.mjs} +18 -0
  86. package/dist/{messages-E8NjqzWq2.mjs → messages-BORkMoil2.mjs} +18 -0
  87. package/dist/{messages-Dqu4aX9s.mjs → messages-BPw_x-6H.mjs} +18 -0
  88. package/dist/{messages-DSmxJWju2.mjs → messages-BRY51SEw2.mjs} +18 -0
  89. package/dist/{chunks/messages-BONyZroH.mjs → messages-BSNsrZVN.mjs} +18 -0
  90. package/dist/{chunks/messages-BAlZjPcl.mjs → messages-B_UKuqrH.mjs} +18 -0
  91. package/dist/{chunks/messages-DB_-5Xln.mjs → messages-BrYeJsSE2.mjs} +18 -0
  92. package/dist/{messages-Brd5R-da2.mjs → messages-BwttyHDI2.mjs} +18 -0
  93. package/dist/{messages-qfvXgPpu2.mjs → messages-C-8qb9sf2.mjs} +18 -0
  94. package/dist/{chunks/messages-BbEW9bQz.mjs → messages-C34dTwF72.mjs} +18 -0
  95. package/dist/{messages-BmH2cQHQ.mjs → messages-C67YUZ9-.mjs} +18 -0
  96. package/dist/{messages-DpwMKDV0.mjs → messages-C6yKu_PJ.mjs} +18 -0
  97. package/dist/{messages-Do7Xjy0n.mjs → messages-CA6J_QoC.mjs} +18 -0
  98. package/dist/{messages-DVL0KZE5.mjs → messages-CFUBJfnf.mjs} +18 -0
  99. package/dist/{chunks/messages-DVr1sqfI2.mjs → messages-CLUBh7O_.mjs} +18 -0
  100. package/dist/{chunks/messages-wl8YrvGG.mjs → messages-CLZoy5fQ.mjs} +18 -0
  101. package/dist/{messages-CisR4PNV.mjs → messages-CNGwdIEz.mjs} +18 -0
  102. package/dist/{messages-DopaMHC42.mjs → messages-CR4gHjd82.mjs} +18 -0
  103. package/dist/{messages-DK6dA0O2.mjs → messages-CVMngZNA.mjs} +18 -0
  104. package/dist/{messages-Xc0KUbYl.mjs → messages-Cd5CW5Tt.mjs} +18 -0
  105. package/dist/{chunks/messages-ChK7v1PV.mjs → messages-CrjQ2Op0.mjs} +18 -0
  106. package/dist/{chunks/messages-CRF7nNrO.mjs → messages-Cv1PSaNk.mjs} +18 -0
  107. package/dist/{messages-DOGbHYv-2.mjs → messages-CxZarWTm2.mjs} +18 -0
  108. package/dist/{messages-D3rwCtKn.mjs → messages-D0eT_eWA.mjs} +18 -0
  109. package/dist/{messages-C6ONf71u2.mjs → messages-D6RYu9JW2.mjs} +18 -0
  110. package/dist/{messages-DQORja0D.mjs → messages-D8U5D391.mjs} +18 -0
  111. package/dist/{chunks/messages-EDMC5ukV.mjs → messages-D8dO6OMN.mjs} +18 -0
  112. package/dist/{messages-DfFZ6Yj5.mjs → messages-DA4T9WBe.mjs} +18 -0
  113. package/dist/{chunks/messages-D22e9h7V2.mjs → messages-DB4UKN8D.mjs} +18 -0
  114. package/dist/{chunks/messages-DEBy3nuJ2.mjs → messages-DCdP2ujL.mjs} +18 -0
  115. package/dist/{messages-D05jqBIa2.mjs → messages-DPFuzIdF2.mjs} +18 -0
  116. package/dist/{chunks/messages-DrfRYiM32.mjs → messages-DQ1icG7L.mjs} +18 -0
  117. package/dist/{chunks/messages-a07QVz8U.mjs → messages-DT7dwzEe.mjs} +18 -0
  118. package/dist/{chunks/messages-CszmHAvQ.mjs → messages-DUYxMxrQ2.mjs} +18 -0
  119. package/dist/{chunks/messages-DtoId_bw2.mjs → messages-D_V0kHD7.mjs} +18 -0
  120. package/dist/{messages-BesJaI6A.mjs → messages-DfqM_XvD.mjs} +18 -0
  121. package/dist/{messages-CT-Kdas6.mjs → messages-Di3-WVzq.mjs} +18 -0
  122. package/dist/{messages-BcVB3osF.mjs → messages-Dl0bfeA-.mjs} +18 -0
  123. package/dist/{chunks/messages-C1S9ztpF.mjs → messages-Do3mHd9U.mjs} +18 -0
  124. package/dist/{chunks/messages-BeGZqQwz.mjs → messages-DqDlcEPn.mjs} +18 -0
  125. package/dist/{chunks/messages-CTCe595D2.mjs → messages-DwiykEgr2.mjs} +18 -0
  126. package/dist/{chunks/messages-8Ld7P_9j2.mjs → messages-Dx5n6MLQ2.mjs} +18 -0
  127. package/dist/{messages-LMaR2_bE.mjs → messages-DxEiqa-B.mjs} +18 -0
  128. package/dist/{chunks/messages-CxxyR4vY.mjs → messages-Dxr1BBvo.mjs} +18 -0
  129. package/dist/{messages-D6VIFnSW.mjs → messages-DzknMM7W.mjs} +18 -0
  130. package/dist/{chunks/messages-oMc7qugU2.mjs → messages-ELvF3qMl2.mjs} +18 -0
  131. package/dist/{messages-53w0fPZS2.mjs → messages-JVJdC0Er2.mjs} +18 -0
  132. package/dist/{chunks/messages-BMD37y3q2.mjs → messages-KVerxvZC.mjs} +18 -0
  133. package/dist/{chunks/messages-Du2BffA7.mjs → messages-OOiDDmVw.mjs} +18 -0
  134. package/dist/{messages-uwK7ktqk.mjs → messages-PyOr_YgV.mjs} +18 -0
  135. package/dist/{messages-qbKjjvgd2.mjs → messages-VrQw3tQ62.mjs} +18 -0
  136. package/dist/{messages-CTTmWn4Y2.mjs → messages-WsUHzXMu2.mjs} +18 -0
  137. package/dist/{messages-CZbcxlZt2.mjs → messages-ZHgPRUj02.mjs} +18 -0
  138. package/dist/{messages-DKHbt-7l2.mjs → messages-aoO_TtoE2.mjs} +18 -0
  139. package/dist/{chunks/messages-CW35K1pq.mjs → messages-bh8BiOee2.mjs} +18 -0
  140. package/dist/{messages-BrOWqNCu2.mjs → messages-gZEhkRrR2.mjs} +18 -0
  141. package/dist/{chunks/messages-BRoa9tGl.mjs → messages-hya8YLMj.mjs} +18 -0
  142. package/dist/{messages-CdduYw-q.mjs → messages-tb1FD_ge.mjs} +18 -0
  143. package/dist/react.mjs +2 -2
  144. package/dist/tools.mjs +3 -3
  145. package/dist/vendor.LICENSE.txt +135 -0
  146. package/package.json +2 -1
  147. package/src/blok.ts +48 -0
  148. package/src/components/block/index.ts +21 -2
  149. package/src/components/block-tunes/block-tune-copy-link.ts +82 -0
  150. package/src/components/i18n/locales/am/messages.json +18 -0
  151. package/src/components/i18n/locales/ar/messages.json +18 -0
  152. package/src/components/i18n/locales/az/messages.json +18 -0
  153. package/src/components/i18n/locales/bg/messages.json +18 -0
  154. package/src/components/i18n/locales/bn/messages.json +18 -0
  155. package/src/components/i18n/locales/bs/messages.json +18 -0
  156. package/src/components/i18n/locales/cs/messages.json +18 -0
  157. package/src/components/i18n/locales/da/messages.json +18 -0
  158. package/src/components/i18n/locales/de/messages.json +18 -0
  159. package/src/components/i18n/locales/dv/messages.json +18 -0
  160. package/src/components/i18n/locales/el/messages.json +18 -0
  161. package/src/components/i18n/locales/en/messages.json +19 -0
  162. package/src/components/i18n/locales/es/messages.json +18 -0
  163. package/src/components/i18n/locales/et/messages.json +18 -0
  164. package/src/components/i18n/locales/fa/messages.json +18 -0
  165. package/src/components/i18n/locales/fi/messages.json +18 -0
  166. package/src/components/i18n/locales/fil/messages.json +18 -0
  167. package/src/components/i18n/locales/fr/messages.json +18 -0
  168. package/src/components/i18n/locales/gu/messages.json +18 -0
  169. package/src/components/i18n/locales/he/messages.json +18 -0
  170. package/src/components/i18n/locales/hi/messages.json +18 -0
  171. package/src/components/i18n/locales/hr/messages.json +18 -0
  172. package/src/components/i18n/locales/hu/messages.json +18 -0
  173. package/src/components/i18n/locales/hy/messages.json +18 -0
  174. package/src/components/i18n/locales/id/messages.json +18 -0
  175. package/src/components/i18n/locales/it/messages.json +18 -0
  176. package/src/components/i18n/locales/ja/messages.json +18 -0
  177. package/src/components/i18n/locales/ka/messages.json +18 -0
  178. package/src/components/i18n/locales/km/messages.json +18 -0
  179. package/src/components/i18n/locales/kn/messages.json +18 -0
  180. package/src/components/i18n/locales/ko/messages.json +18 -0
  181. package/src/components/i18n/locales/ku/messages.json +18 -0
  182. package/src/components/i18n/locales/lo/messages.json +18 -0
  183. package/src/components/i18n/locales/lt/messages.json +18 -0
  184. package/src/components/i18n/locales/lv/messages.json +18 -0
  185. package/src/components/i18n/locales/mk/messages.json +18 -0
  186. package/src/components/i18n/locales/ml/messages.json +18 -0
  187. package/src/components/i18n/locales/mn/messages.json +18 -0
  188. package/src/components/i18n/locales/mr/messages.json +18 -0
  189. package/src/components/i18n/locales/ms/messages.json +18 -0
  190. package/src/components/i18n/locales/my/messages.json +18 -0
  191. package/src/components/i18n/locales/ne/messages.json +18 -0
  192. package/src/components/i18n/locales/nl/messages.json +18 -0
  193. package/src/components/i18n/locales/no/messages.json +18 -0
  194. package/src/components/i18n/locales/pa/messages.json +18 -0
  195. package/src/components/i18n/locales/pl/messages.json +18 -0
  196. package/src/components/i18n/locales/ps/messages.json +18 -0
  197. package/src/components/i18n/locales/pt/messages.json +18 -0
  198. package/src/components/i18n/locales/ro/messages.json +18 -0
  199. package/src/components/i18n/locales/ru/messages.json +18 -0
  200. package/src/components/i18n/locales/sd/messages.json +18 -0
  201. package/src/components/i18n/locales/si/messages.json +18 -0
  202. package/src/components/i18n/locales/sk/messages.json +18 -0
  203. package/src/components/i18n/locales/sl/messages.json +18 -0
  204. package/src/components/i18n/locales/sq/messages.json +18 -0
  205. package/src/components/i18n/locales/sr/messages.json +18 -0
  206. package/src/components/i18n/locales/sv/messages.json +18 -0
  207. package/src/components/i18n/locales/sw/messages.json +18 -0
  208. package/src/components/i18n/locales/ta/messages.json +18 -0
  209. package/src/components/i18n/locales/te/messages.json +18 -0
  210. package/src/components/i18n/locales/th/messages.json +18 -0
  211. package/src/components/i18n/locales/tr/messages.json +18 -0
  212. package/src/components/i18n/locales/ug/messages.json +18 -0
  213. package/src/components/i18n/locales/uk/messages.json +18 -0
  214. package/src/components/i18n/locales/ur/messages.json +18 -0
  215. package/src/components/i18n/locales/vi/messages.json +18 -0
  216. package/src/components/i18n/locales/yi/messages.json +18 -0
  217. package/src/components/i18n/locales/zh/messages.json +18 -0
  218. package/src/components/icons/index.ts +65 -0
  219. package/src/components/inline-tools/inline-tool-bold.ts +10 -0
  220. package/src/components/inline-tools/inline-tool-code.ts +54 -1
  221. package/src/components/inline-tools/inline-tool-italic.ts +54 -1
  222. package/src/components/inline-tools/inline-tool-strikethrough.ts +54 -1
  223. package/src/components/inline-tools/inline-tool-underline.ts +54 -1
  224. package/src/components/inline-tools/services/bold-normalization-pass.ts +29 -3
  225. package/src/components/inline-tools/utils/formatting-range-utils.ts +83 -0
  226. package/src/components/modules/api/tools.ts +19 -0
  227. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +5 -5
  228. package/src/components/modules/blockManager/blockManager.ts +22 -0
  229. package/src/components/modules/blockManager/event-binder.ts +12 -1
  230. package/src/components/modules/blockManager/factory.ts +4 -0
  231. package/src/components/modules/blockManager/types.ts +4 -0
  232. package/src/components/modules/blockManager/yjs-sync.ts +16 -2
  233. package/src/components/modules/paste/google-docs-preprocessor.ts +49 -3
  234. package/src/components/modules/paste/handlers/table-cells-handler.ts +12 -2
  235. package/src/components/modules/paste/index.ts +8 -4
  236. package/src/components/modules/paste/types.ts +2 -0
  237. package/src/components/modules/renderer.ts +22 -2
  238. package/src/components/modules/saver.ts +19 -1
  239. package/src/components/modules/themeManager.ts +3 -1
  240. package/src/components/modules/toolbar/blockSettings.ts +51 -1
  241. package/src/components/modules/toolbar/inline/index.ts +0 -3
  242. package/src/components/modules/tools.ts +5 -0
  243. package/src/components/modules/uiControllers/controllers/keyboard.ts +29 -0
  244. package/src/components/modules/uiControllers/controllers/selection.ts +14 -2
  245. package/src/components/modules/yjs/document-store.ts +22 -0
  246. package/src/components/modules/yjs/index.ts +10 -0
  247. package/src/components/modules/yjs/serializer.ts +20 -0
  248. package/src/components/ui/toolbox.ts +0 -1
  249. package/src/components/utils/id-generator.ts +11 -0
  250. package/src/components/utils/key-icon.ts +187 -0
  251. package/src/components/utils/popover/components/hint/hint.ts +3 -1
  252. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +18 -5
  253. package/src/components/utils/popover/popover-abstract.ts +45 -0
  254. package/src/components/utils/popover/popover-desktop.ts +1 -0
  255. package/src/components/utils/popover/popover-position.ts +4 -2
  256. package/src/components/utils/popover/popover.const.ts +2 -0
  257. package/src/components/utils.ts +1 -0
  258. package/src/styles/main.css +1269 -0
  259. package/src/tools/code/index.ts +4 -0
  260. package/src/tools/database/database-backend-sync.ts +132 -0
  261. package/src/tools/database/database-board-view.ts +410 -0
  262. package/src/tools/database/database-card-drag.ts +306 -0
  263. package/src/tools/database/database-card-drawer.ts +546 -0
  264. package/src/tools/database/database-column-controls.ts +141 -0
  265. package/src/tools/database/database-column-drag.ts +262 -0
  266. package/src/tools/database/database-keyboard.ts +35 -0
  267. package/src/tools/database/database-list-row-drag.ts +245 -0
  268. package/src/tools/database/database-list-view.ts +333 -0
  269. package/src/tools/database/database-model.ts +214 -0
  270. package/src/tools/database/database-property-type-popover.ts +108 -0
  271. package/src/tools/database/database-tab-bar.ts +558 -0
  272. package/src/tools/database/database-view-popover.ts +129 -0
  273. package/src/tools/database/database-view-renderer.ts +25 -0
  274. package/src/tools/database/index.ts +1223 -0
  275. package/src/tools/database/types.ts +152 -0
  276. package/src/tools/database-row/index.ts +74 -0
  277. package/src/tools/index.ts +4 -0
  278. package/types/api/tools.d.ts +18 -0
  279. package/types/configs/blok-config.d.ts +27 -0
  280. package/types/data-formats/output-data.d.ts +13 -0
  281. package/types/index.d.ts +17 -0
  282. package/types/tools/database.d.ts +152 -0
  283. package/types/tools-entry.d.ts +7 -4
  284. package/types/utils/popover/popover.d.ts +6 -0
@@ -0,0 +1,546 @@
1
+ import type { I18n, OutputData } from '../../../types';
2
+ import type { ToolsConfig } from '../../../types/api/tools';
3
+ import type { DatabaseRow, PropertyDefinition, PropertyType, PropertyValue } from './types';
4
+ import { IconChevronRight } from '../../components/icons';
5
+ import { DatabasePropertyTypePopover } from './database-property-type-popover';
6
+
7
+ interface BlokInstance {
8
+ save(): Promise<OutputData>;
9
+ destroy(): void;
10
+ isReady: Promise<void>;
11
+ }
12
+
13
+ export interface CardDrawerOptions {
14
+ wrapper: HTMLElement;
15
+ readOnly: boolean;
16
+ i18n?: I18n;
17
+ toolsConfig?: ToolsConfig;
18
+ titlePropertyId: string;
19
+ descriptionPropertyId?: string;
20
+ schema: PropertyDefinition[];
21
+ onTitleChange: (rowId: string, title: string) => void;
22
+ onDescriptionChange: (rowId: string, description: OutputData) => void;
23
+ onClose: () => void;
24
+ onAddProperty?: (type: PropertyType) => void;
25
+ }
26
+
27
+ /**
28
+ * Side drawer that opens when a kanban card is clicked.
29
+ * Sits beside the board as a flex sibling, taking layout space.
30
+ * Contains a title input, status property, and a nested Blok editor for the card description.
31
+ */
32
+ export class DatabaseCardDrawer {
33
+ private readonly wrapper: HTMLElement;
34
+ private readonly readOnly: boolean;
35
+ private readonly i18n: I18n | undefined;
36
+ private readonly toolsConfig: ToolsConfig | undefined;
37
+ private readonly titlePropertyId: string;
38
+ private readonly descriptionPropertyId: string | undefined;
39
+ private schema: PropertyDefinition[];
40
+ private readonly onTitleChange: (rowId: string, title: string) => void;
41
+ private readonly onDescriptionChange: (rowId: string, description: OutputData) => void;
42
+ private readonly onClose: () => void;
43
+ private readonly onAddProperty: ((type: PropertyType) => void) | undefined;
44
+
45
+ private drawer: HTMLDivElement | null = null;
46
+ private currentRowId: string | null = null;
47
+ private currentRow: DatabaseRow | null = null;
48
+ private blokInstance: BlokInstance | null = null;
49
+ private escapeHandler: ((e: KeyboardEvent) => void) | null = null;
50
+ private outsideClickHandler: ((e: MouseEvent) => void) | null = null;
51
+ private propertyTypePopover: DatabasePropertyTypePopover | null = null;
52
+
53
+ constructor(options: CardDrawerOptions) {
54
+ this.wrapper = options.wrapper;
55
+ this.readOnly = options.readOnly;
56
+ this.i18n = options.i18n;
57
+ this.toolsConfig = options.toolsConfig;
58
+ this.titlePropertyId = options.titlePropertyId;
59
+ this.descriptionPropertyId = options.descriptionPropertyId;
60
+ this.schema = options.schema;
61
+ this.onTitleChange = options.onTitleChange;
62
+ this.onDescriptionChange = options.onDescriptionChange;
63
+ this.onClose = options.onClose;
64
+ this.onAddProperty = options.onAddProperty;
65
+ }
66
+
67
+ get isOpen(): boolean {
68
+ return this.drawer !== null;
69
+ }
70
+
71
+ open(row: DatabaseRow): void {
72
+ if (this.drawer) {
73
+ if (row.id === this.currentRowId) {
74
+ return;
75
+ }
76
+
77
+ this.loadCard(row);
78
+
79
+ return;
80
+ }
81
+
82
+ // Remove any drawer still animating out from a previous close
83
+ const exiting = this.wrapper.querySelector('[data-blok-database-drawer]');
84
+
85
+ exiting?.remove();
86
+
87
+ this.currentRowId = row.id;
88
+ this.currentRow = row;
89
+ this.updateActiveCard(row.id);
90
+
91
+ const title = (row.properties[this.titlePropertyId] as string) ?? '';
92
+
93
+ const drawer = document.createElement('div');
94
+
95
+ drawer.setAttribute('data-blok-database-drawer', '');
96
+ drawer.setAttribute('role', 'complementary');
97
+ drawer.setAttribute('aria-label', 'Card details');
98
+
99
+ // --- Top toolbar ---
100
+ const toolbar = document.createElement('div');
101
+
102
+ toolbar.setAttribute('data-blok-database-drawer-toolbar', '');
103
+
104
+ const closeBtn = document.createElement('button');
105
+
106
+ closeBtn.setAttribute('data-blok-database-drawer-close', '');
107
+ closeBtn.setAttribute('aria-label', 'Close');
108
+ closeBtn.innerHTML = IconChevronRight + IconChevronRight;
109
+ closeBtn.addEventListener('click', () => {
110
+ this.close();
111
+ });
112
+ toolbar.appendChild(closeBtn);
113
+
114
+ drawer.appendChild(toolbar);
115
+
116
+ // --- Scrollable content ---
117
+ const content = document.createElement('div');
118
+
119
+ content.setAttribute('data-blok-database-drawer-content', '');
120
+
121
+ // --- Title input ---
122
+ const titleInput = document.createElement('textarea');
123
+
124
+ titleInput.setAttribute('data-blok-database-drawer-title', '');
125
+ titleInput.setAttribute('aria-label', 'Card title');
126
+ titleInput.placeholder = this.i18n?.t('tools.database.cardTitlePlaceholder') ?? 'Empty page';
127
+ titleInput.value = title;
128
+ titleInput.rows = 1;
129
+ titleInput.readOnly = this.readOnly;
130
+ titleInput.addEventListener('input', () => {
131
+ if (this.currentRowId !== null) {
132
+ this.onTitleChange(this.currentRowId, titleInput.value);
133
+ }
134
+ this.autoResizeTitle(titleInput);
135
+ });
136
+ titleInput.addEventListener('keydown', (e) => {
137
+ if (e.key === 'Enter') {
138
+ e.preventDefault();
139
+ }
140
+ });
141
+ content.appendChild(titleInput);
142
+
143
+ // --- Properties section ---
144
+ const renderableSchema = this.getRenderableSchema();
145
+
146
+ content.appendChild(this.buildPropsSection(renderableSchema, row));
147
+
148
+ // --- Divider ---
149
+ const divider = document.createElement('hr');
150
+
151
+ content.appendChild(divider);
152
+
153
+ // --- Editor holder ---
154
+ const editorHolder = document.createElement('div');
155
+
156
+ editorHolder.setAttribute('data-blok-database-drawer-editor', '');
157
+ content.appendChild(editorHolder);
158
+
159
+ drawer.appendChild(content);
160
+ this.wrapper.appendChild(drawer);
161
+ this.drawer = drawer;
162
+
163
+ requestAnimationFrame(() => {
164
+ drawer.style.width = '45%';
165
+ drawer.addEventListener('transitionend', () => {
166
+ this.autoResizeTitle(titleInput);
167
+
168
+ if (!title) {
169
+ titleInput.focus();
170
+ }
171
+ }, { once: true });
172
+ });
173
+
174
+ this.initNestedEditor(editorHolder, row);
175
+
176
+ this.escapeHandler = (e: KeyboardEvent): void => {
177
+ if (e.key !== 'Escape') {
178
+ return;
179
+ }
180
+
181
+ const target = e.target as Node | null;
182
+
183
+ if (target && editorHolder.contains(target)) {
184
+ return;
185
+ }
186
+
187
+ this.close();
188
+ };
189
+ document.addEventListener('keydown', this.escapeHandler);
190
+
191
+ this.outsideClickHandler = (e: MouseEvent): void => {
192
+ const target = e.target as Node | null;
193
+
194
+ if (target && drawer.contains(target)) {
195
+ return;
196
+ }
197
+
198
+ /**
199
+ * Toolbox and other popovers are portaled to document.body,
200
+ * so they sit outside the drawer DOM tree. Without this check,
201
+ * clicking a popover item would be treated as an "outside click"
202
+ * and close the drawer.
203
+ */
204
+ if (target instanceof Element && target.closest('[data-blok-popover-opened]') !== null) {
205
+ return;
206
+ }
207
+
208
+ /**
209
+ * The tab bar lives outside the drawer DOM tree, so clicking a
210
+ * tab to switch views would be treated as an outside click and
211
+ * close the drawer before switchView() runs.
212
+ */
213
+ if (target instanceof Element && target.closest('[data-blok-database-tab-bar]') !== null) {
214
+ return;
215
+ }
216
+
217
+ this.close();
218
+ };
219
+ document.addEventListener('mousedown', this.outsideClickHandler);
220
+
221
+ titleInput.focus();
222
+ }
223
+
224
+ /**
225
+ * Swaps content in the already-open drawer to show a different card
226
+ * without closing/reopening the drawer panel.
227
+ */
228
+ private loadCard(row: DatabaseRow): void {
229
+ if (this.drawer === null) {
230
+ return;
231
+ }
232
+
233
+ this.cleanupEditor();
234
+
235
+ this.currentRowId = row.id;
236
+ this.currentRow = row;
237
+ this.updateActiveCard(row.id);
238
+
239
+ const title = (row.properties[this.titlePropertyId] as string) ?? '';
240
+
241
+ // Update title
242
+ const titleInput = this.drawer.querySelector<HTMLTextAreaElement>('[data-blok-database-drawer-title]');
243
+
244
+ if (titleInput !== null) {
245
+ titleInput.value = title;
246
+ this.autoResizeTitle(titleInput);
247
+
248
+ if (!title) {
249
+ titleInput.focus();
250
+ }
251
+ }
252
+
253
+ // Replace properties section
254
+ this.drawer.querySelector('[data-blok-database-drawer-props]')?.remove();
255
+
256
+ const renderableSchema = this.getRenderableSchema();
257
+ const content = this.drawer.querySelector('[data-blok-database-drawer-content]');
258
+ const divider = content?.querySelector('hr') ?? null;
259
+
260
+ if (content !== null) {
261
+ content.insertBefore(this.buildPropsSection(renderableSchema, row), divider);
262
+ }
263
+
264
+ // Reinitialize editor
265
+ const editorHolder = this.drawer.querySelector<HTMLElement>('[data-blok-database-drawer-editor]');
266
+
267
+ if (editorHolder !== null) {
268
+ editorHolder.innerHTML = '';
269
+ this.initNestedEditor(editorHolder, row);
270
+ }
271
+ }
272
+
273
+ close(): void {
274
+ const wasOpen = this.drawer !== null;
275
+
276
+ this.updateActiveCard(null);
277
+ this.cleanupListeners();
278
+ this.cleanupEditor();
279
+
280
+ if (this.drawer) {
281
+ const drawer = this.drawer;
282
+
283
+ this.drawer = null;
284
+ drawer.style.width = '0px';
285
+ drawer.addEventListener('transitionend', () => {
286
+ drawer.remove();
287
+ }, { once: true });
288
+ }
289
+
290
+ this.currentRowId = null;
291
+ this.currentRow = null;
292
+
293
+ if (wasOpen) {
294
+ this.onClose();
295
+ }
296
+ }
297
+
298
+ destroy(): void {
299
+ this.cleanupListeners();
300
+ this.cleanupEditor();
301
+ this.propertyTypePopover?.destroy();
302
+ this.propertyTypePopover = null;
303
+
304
+ if (this.drawer) {
305
+ this.drawer.remove();
306
+ this.drawer = null;
307
+ }
308
+
309
+ // Remove any drawer still animating out
310
+ const exiting = this.wrapper.querySelector('[data-blok-database-drawer]');
311
+
312
+ exiting?.remove();
313
+
314
+ this.currentRowId = null;
315
+ this.currentRow = null;
316
+ }
317
+
318
+ /**
319
+ * Updates the schema and, if the drawer is currently open, rebuilds the
320
+ * properties section in-place using the current row's data.
321
+ */
322
+ refreshSchema(schema: PropertyDefinition[]): void {
323
+ this.schema = schema;
324
+
325
+ if (this.drawer === null || this.currentRow === null) {
326
+ return;
327
+ }
328
+
329
+ this.drawer.querySelector('[data-blok-database-drawer-props]')?.remove();
330
+
331
+ const renderableSchema = this.getRenderableSchema();
332
+ const content = this.drawer.querySelector('[data-blok-database-drawer-content]');
333
+ const divider = content?.querySelector('hr') ?? null;
334
+
335
+ if (content !== null) {
336
+ content.insertBefore(this.buildPropsSection(renderableSchema, this.currentRow), divider);
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Builds a `[data-blok-database-drawer-props]` section element with one row per
342
+ * renderable schema property.
343
+ */
344
+ private buildPropsSection(renderableSchema: PropertyDefinition[], row: DatabaseRow): HTMLDivElement {
345
+ const propsSection = document.createElement('div');
346
+
347
+ propsSection.setAttribute('data-blok-database-drawer-props', '');
348
+
349
+ for (const def of renderableSchema) {
350
+ propsSection.appendChild(this.createPropertyRow(def, row.properties[def.id] ?? null));
351
+ }
352
+
353
+ if (!this.readOnly) {
354
+ const addBtn = document.createElement('button');
355
+
356
+ addBtn.setAttribute('data-blok-database-drawer-add-prop', '');
357
+ addBtn.textContent = '+ Add a property';
358
+ addBtn.addEventListener('click', () => {
359
+ if (this.propertyTypePopover === null) {
360
+ this.propertyTypePopover = new DatabasePropertyTypePopover({
361
+ onSelect: (type) => {
362
+ this.onAddProperty?.(type);
363
+ this.propertyTypePopover?.close();
364
+ },
365
+ });
366
+ }
367
+
368
+ this.propertyTypePopover.open(addBtn);
369
+ });
370
+ propsSection.appendChild(addBtn);
371
+ }
372
+
373
+ return propsSection;
374
+ }
375
+
376
+ /**
377
+ * Returns schema properties that should be shown in the properties section,
378
+ * sorted by position. Excludes 'title' and 'richText' (rendered separately).
379
+ */
380
+ private getRenderableSchema(): PropertyDefinition[] {
381
+ return [...this.schema]
382
+ .filter((def) => def.type !== 'title' && def.type !== 'richText')
383
+ .sort((a, b) => {
384
+ if (a.position < b.position) return -1;
385
+ if (a.position > b.position) return 1;
386
+
387
+ return 0;
388
+ });
389
+ }
390
+
391
+ /**
392
+ * Creates a pill badge element for a select option.
393
+ */
394
+ private createSelectPill(option: { label: string; color?: string }): HTMLSpanElement {
395
+ const pill = document.createElement('span');
396
+
397
+ pill.setAttribute('data-blok-database-drawer-prop-pill', '');
398
+
399
+ if (option.color !== undefined) {
400
+ pill.style.backgroundColor = `var(--blok-color-${option.color}-bg)`;
401
+ pill.style.color = `var(--blok-color-${option.color}-text)`;
402
+
403
+ const dot = document.createElement('span');
404
+
405
+ dot.setAttribute('data-blok-database-drawer-prop-dot', '');
406
+ dot.style.backgroundColor = `var(--blok-color-${option.color}-text)`;
407
+ pill.appendChild(dot);
408
+ }
409
+
410
+ const pillText = document.createElement('span');
411
+
412
+ pillText.textContent = option.label;
413
+ pill.appendChild(pillText);
414
+
415
+ return pill;
416
+ }
417
+
418
+ /**
419
+ * Creates a property row that dispatches on the property type to render
420
+ * the appropriate value representation.
421
+ */
422
+ private createPropertyRow(def: PropertyDefinition, value: PropertyValue): HTMLDivElement {
423
+ const row = document.createElement('div');
424
+
425
+ row.setAttribute('data-blok-database-drawer-prop-row', '');
426
+
427
+ const label = document.createElement('span');
428
+
429
+ label.setAttribute('data-blok-database-drawer-prop-label', '');
430
+ label.textContent = def.name;
431
+ row.appendChild(label);
432
+
433
+ const valueEl = document.createElement('span');
434
+
435
+ valueEl.setAttribute('data-blok-database-drawer-prop-value', '');
436
+
437
+ if (def.type === 'select') {
438
+ const config = def.config;
439
+ const optionId = typeof value === 'string' ? value : null;
440
+ const option = config?.options.find((o) => o.id === optionId);
441
+
442
+ if (option !== undefined) {
443
+ valueEl.appendChild(this.createSelectPill(option));
444
+ }
445
+ } else if (def.type === 'multiSelect') {
446
+ const config = def.config;
447
+ const selectedIds = Array.isArray(value) ? value : [];
448
+
449
+ selectedIds
450
+ .map((id) => config?.options.find((o) => o.id === id))
451
+ .filter((opt): opt is NonNullable<typeof opt> => opt !== undefined)
452
+ .forEach((opt) => valueEl.appendChild(this.createSelectPill(opt)));
453
+ } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
454
+ valueEl.textContent = String(value);
455
+ }
456
+
457
+ row.appendChild(valueEl);
458
+
459
+ return row;
460
+ }
461
+
462
+ private updateActiveCard(rowId: string | null): void {
463
+ const prev = this.wrapper.querySelector('[data-blok-database-card-active]');
464
+
465
+ prev?.removeAttribute('data-blok-database-card-active');
466
+
467
+ if (rowId !== null) {
468
+ const cardEl = this.wrapper.querySelector(`[data-blok-database-card][data-row-id="${rowId}"]`);
469
+
470
+ cardEl?.setAttribute('data-blok-database-card-active', '');
471
+ }
472
+ }
473
+
474
+ private cleanupListeners(): void {
475
+ if (this.escapeHandler) {
476
+ document.removeEventListener('keydown', this.escapeHandler);
477
+ this.escapeHandler = null;
478
+ }
479
+
480
+ if (this.outsideClickHandler) {
481
+ document.removeEventListener('mousedown', this.outsideClickHandler);
482
+ this.outsideClickHandler = null;
483
+ }
484
+ }
485
+
486
+ private cleanupEditor(): void {
487
+ if (this.blokInstance) {
488
+ try {
489
+ const instance = this.blokInstance;
490
+ const rowId = this.currentRowId;
491
+
492
+ instance.save().then((data) => {
493
+ if (rowId !== null) {
494
+ this.onDescriptionChange(rowId, data);
495
+ }
496
+ instance.destroy();
497
+ }).catch(() => {
498
+ instance.destroy();
499
+ });
500
+ } catch {
501
+ // Blok may already be destroyed
502
+ }
503
+ this.blokInstance = null;
504
+ }
505
+ }
506
+
507
+ private autoResizeTitle(textarea: HTMLTextAreaElement): void {
508
+ const { style } = textarea;
509
+
510
+ style.height = 'auto';
511
+
512
+ if (textarea.scrollHeight > 0) {
513
+ style.height = `${textarea.scrollHeight}px`;
514
+ }
515
+ }
516
+
517
+ private initNestedEditor(editorHolder: HTMLElement, row: DatabaseRow): void {
518
+ import('../../blok').then(({ Blok }) => {
519
+ const rowId = row.id;
520
+ const description = this.descriptionPropertyId !== undefined
521
+ ? row.properties[this.descriptionPropertyId]
522
+ : undefined;
523
+ const blok = new Blok({
524
+ ...this.toolsConfig,
525
+ holder: editorHolder,
526
+ data: description as OutputData | undefined,
527
+ readOnly: this.readOnly,
528
+ onChange: async () => {
529
+ try {
530
+ const data = await this.blokInstance?.save();
531
+
532
+ if (data !== undefined) {
533
+ this.onDescriptionChange(rowId, data);
534
+ }
535
+ } catch {
536
+ // save may fail if editor is being destroyed
537
+ }
538
+ },
539
+ });
540
+
541
+ this.blokInstance = blok as unknown as BlokInstance;
542
+ }).catch(() => {
543
+ // Blok import may fail in unit tests (jsdom), drawer still works for title
544
+ });
545
+ }
546
+ }
@@ -0,0 +1,141 @@
1
+ import type { I18n } from '../../../types';
2
+
3
+ export interface ColumnControlsOptions {
4
+ i18n: I18n;
5
+ onRename: (optionId: string, label: string) => void;
6
+ onDelete: (optionId: string) => void;
7
+ /** Called on every keystroke — for instant local update. Optional. */
8
+ onRenameInput?: (optionId: string, label: string) => void;
9
+ /** Called on blur/Enter — for debounced backend persist. Optional. */
10
+ onRenameCommit?: (optionId: string, label: string) => void;
11
+ }
12
+
13
+ export class DatabaseColumnControls {
14
+ private options: ColumnControlsOptions;
15
+ private readonly i18n: I18n;
16
+
17
+ constructor(options: ColumnControlsOptions) {
18
+ this.options = options;
19
+ this.i18n = options.i18n;
20
+ }
21
+
22
+ makeEditable(headerEl: HTMLElement, optionId: string): void {
23
+ const titleEl = headerEl.querySelector('[data-blok-database-column-title]');
24
+
25
+ if (titleEl) {
26
+ const input = document.createElement('input');
27
+
28
+ input.type = 'text';
29
+ input.value = titleEl.textContent ?? '';
30
+ input.setAttribute('data-blok-database-column-title-input', '');
31
+ input.setAttribute('aria-label', this.i18n.t('tools.database.renameColumn'));
32
+
33
+ // Fallback for browsers without field-sizing: content (e.g. Firefox).
34
+ // Sets the size attribute to the character count so the input doesn't
35
+ // default to 20-char width. Modern browsers use the CSS property instead.
36
+ const syncSize = (): void => {
37
+ input.size = Math.max(input.value.length, 1);
38
+ };
39
+
40
+ syncSize();
41
+ input.addEventListener('input', syncSize);
42
+ input.addEventListener('change', () => {
43
+ this.options.onRename(optionId, input.value);
44
+ });
45
+ titleEl.replaceWith(input);
46
+ }
47
+
48
+ this.appendDeleteButton(headerEl, optionId);
49
+ }
50
+
51
+ appendDeleteButton(headerEl: HTMLElement, optionId: string): void {
52
+ const deleteBtn = document.createElement('button');
53
+
54
+ deleteBtn.setAttribute('data-blok-database-delete-column', '');
55
+ deleteBtn.setAttribute('aria-label', this.i18n.t('tools.database.deleteColumn'));
56
+ deleteBtn.setAttribute('data-option-id', optionId);
57
+ deleteBtn.addEventListener('click', () => {
58
+ this.options.onDelete(optionId);
59
+ });
60
+ headerEl.appendChild(deleteBtn);
61
+ }
62
+
63
+ makePillTitleEditable(headerEl: HTMLElement, optionId: string): void {
64
+ const titleEl = headerEl.querySelector<HTMLElement>('[data-blok-database-column-title]');
65
+ if (titleEl === null) return;
66
+
67
+ titleEl.style.cursor = 'text';
68
+
69
+ titleEl.addEventListener('click', (e: MouseEvent) => {
70
+ e.stopPropagation(); // prevent column drag from starting
71
+
72
+ const originalLabel = titleEl.textContent ?? '';
73
+
74
+ const input = document.createElement('input');
75
+ input.type = 'text';
76
+ input.value = originalLabel;
77
+ input.setAttribute('data-blok-database-column-title-input', '');
78
+ input.setAttribute('aria-label', this.i18n.t('tools.database.renameColumn'));
79
+
80
+ // Size sync for Firefox (no field-sizing: content)
81
+ const syncSize = (): void => { input.size = Math.max(input.value.length, 1); };
82
+ syncSize();
83
+ input.addEventListener('input', syncSize);
84
+
85
+ input.addEventListener('input', () => {
86
+ this.options.onRenameInput?.(optionId, input.value);
87
+ });
88
+
89
+ const guard = { done: false };
90
+
91
+ const restoreDiv = (label: string): HTMLElement => {
92
+ const div = document.createElement('div');
93
+ div.setAttribute('data-blok-database-column-title', '');
94
+ div.style.cursor = 'text';
95
+ div.textContent = label;
96
+ return div;
97
+ };
98
+
99
+ const commit = (): void => {
100
+ if (guard.done) return;
101
+ guard.done = true;
102
+ const rawValue = input.value;
103
+ const newLabel = rawValue.trim() || originalLabel;
104
+ const restoredDiv = restoreDiv(newLabel);
105
+ input.replaceWith(restoredDiv);
106
+ // re-attach click listener on restored div
107
+ this.makePillTitleEditable(headerEl, optionId);
108
+ // Fire commit only when the final resolved label actually changed
109
+ if (newLabel !== originalLabel) {
110
+ this.options.onRenameCommit?.(optionId, newLabel);
111
+ this.options.onRename(optionId, newLabel);
112
+ }
113
+ };
114
+
115
+ const cancel = (): void => {
116
+ if (guard.done) return;
117
+ guard.done = true;
118
+ const restoredDiv = restoreDiv(originalLabel);
119
+ input.replaceWith(restoredDiv);
120
+ this.makePillTitleEditable(headerEl, optionId);
121
+ };
122
+
123
+ input.addEventListener('blur', commit);
124
+ input.addEventListener('keydown', (ke: KeyboardEvent) => {
125
+ ke.stopPropagation();
126
+ if (ke.key === 'Enter') {
127
+ commit();
128
+ } else if (ke.key === 'Escape') {
129
+ input.removeEventListener('blur', commit);
130
+ cancel();
131
+ }
132
+ });
133
+
134
+ titleEl.replaceWith(input);
135
+ input.focus();
136
+ input.select();
137
+ });
138
+ }
139
+
140
+ destroy(): void { /* no global listeners */ }
141
+ }