@jackuait/blok 0.10.0-beta.3 → 0.10.0-beta.5

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 (251) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-CrfCy6RQ.mjs → blok-DH-WEcA8.mjs} +2213 -2200
  3. package/dist/chunks/{constants-BzBmIAnT.mjs → constants-Ccno9NZS.mjs} +86 -64
  4. package/dist/chunks/{i18next-Ch0gVA3V.mjs → i18next-G6FKbZqA.mjs} +1 -1
  5. package/dist/chunks/{i18next-loader-BOlOKRt8.mjs → i18next-loader-CwNimni3.mjs} +2 -2
  6. package/dist/chunks/{lightweight-i18n-D1n0OClP.mjs → lightweight-i18n-DWCdzAw0.mjs} +17 -1
  7. package/dist/{messages-D7Wofcg3.mjs → chunks/messages-1Raf1IK82.mjs} +16 -0
  8. package/dist/chunks/{messages-Du6j7HqD2.mjs → messages-6mikOS4D2.mjs} +16 -0
  9. package/dist/{messages-CyQQ8g9w2.mjs → chunks/messages-B0ffBqzr.mjs} +16 -0
  10. package/dist/{messages-CfaiAQHW2.mjs → chunks/messages-B2pW6jO_.mjs} +16 -0
  11. package/dist/{messages-4kMr3vfK2.mjs → chunks/messages-B3s2vra72.mjs} +16 -0
  12. package/dist/{messages-CBWUNVHy.mjs → chunks/messages-B7MIRzCa2.mjs} +16 -0
  13. package/dist/chunks/{messages-0Nh_GHU02.mjs → messages-BPog17132.mjs} +16 -0
  14. package/dist/chunks/{messages-CbXL5f99.mjs → messages-BS1nOvZ-.mjs} +16 -0
  15. package/dist/{messages-BCIuVjwb.mjs → chunks/messages-BW_7lfqG2.mjs} +16 -0
  16. package/dist/chunks/{messages-naWwXCx3.mjs → messages-BaPZuLjN.mjs} +16 -0
  17. package/dist/chunks/{messages-DcrkPXXn.mjs → messages-BbdNugdi.mjs} +16 -0
  18. package/dist/{messages-CeyO7HXV.mjs → chunks/messages-BdnSVKOw.mjs} +16 -0
  19. package/dist/chunks/{messages-BORQKKT9.mjs → messages-BgVEGd4c.mjs} +16 -0
  20. package/dist/chunks/{messages-C1pm6RWX.mjs → messages-BgsPQXfP.mjs} +16 -0
  21. package/dist/chunks/{messages-CYZgPXFK.mjs → messages-BkCjgGxc.mjs} +16 -0
  22. package/dist/{messages-DB1-0FXB.mjs → chunks/messages-BnznaKEP2.mjs} +16 -0
  23. package/dist/{messages-CIpXgMRr2.mjs → chunks/messages-Bpda_3PM2.mjs} +16 -0
  24. package/dist/{messages-D2GHT83V2.mjs → chunks/messages-BrFl5773.mjs} +16 -0
  25. package/dist/chunks/{messages-BLD1DC722.mjs → messages-C-b6tPad2.mjs} +16 -0
  26. package/dist/chunks/{messages-LiSIeruD2.mjs → messages-C11byid72.mjs} +16 -0
  27. package/dist/chunks/{messages-IKrYzwFq.mjs → messages-C1OqT_nL.mjs} +16 -0
  28. package/dist/chunks/{messages-fitmpwb3.mjs → messages-Cdx4QMR1.mjs} +16 -0
  29. package/dist/{messages-CLB0caVL.mjs → chunks/messages-CgRvtOEY.mjs} +16 -0
  30. package/dist/{messages-BxcqzUx0.mjs → chunks/messages-ClRHDxzh.mjs} +16 -0
  31. package/dist/{messages-DwroI9YW.mjs → chunks/messages-CljStrYi.mjs} +16 -0
  32. package/dist/chunks/{messages-CR48wvl32.mjs → messages-CmXADeab2.mjs} +16 -0
  33. package/dist/{messages-BkZDaKu6.mjs → chunks/messages-CmrMwBv3.mjs} +16 -0
  34. package/dist/chunks/{messages-B7Vlzmgw.mjs → messages-CpzO7KRA.mjs} +16 -0
  35. package/dist/{messages-DwTwecgF2.mjs → chunks/messages-Cqc-6rfh2.mjs} +16 -0
  36. package/dist/chunks/{messages-BN_zp4oj.mjs → messages-CrMfiGu5.mjs} +16 -0
  37. package/dist/{messages-BRlbE8SE.mjs → chunks/messages-Cs9XBt4T.mjs} +16 -0
  38. package/dist/chunks/{messages-DDM4t-j8.mjs → messages-Csvm4mtA.mjs} +16 -0
  39. package/dist/chunks/{messages-CuHxfDvL2.mjs → messages-Ct7AMBS82.mjs} +16 -0
  40. package/dist/{messages-BjEY7_jw.mjs → chunks/messages-CvfKofOP.mjs} +16 -0
  41. package/dist/{messages-Du0fWeyE.mjs → chunks/messages-CyNsByCY.mjs} +16 -0
  42. package/dist/{messages-6Cq_jyNk2.mjs → chunks/messages-Czny5pPT2.mjs} +16 -0
  43. package/dist/{messages-CGqRnKaM.mjs → chunks/messages-D5IgUbBD2.mjs} +16 -0
  44. package/dist/{messages-uP6wmMOs.mjs → chunks/messages-DA7Zk-Cy.mjs} +16 -0
  45. package/dist/chunks/{messages-DJ9yyqUO2.mjs → messages-DAVsuDWh2.mjs} +16 -0
  46. package/dist/chunks/{messages-i9ThpxZk2.mjs → messages-DBpXyvRe2.mjs} +16 -0
  47. package/dist/chunks/{messages-CogKhvJL.mjs → messages-DC7TX-YT.mjs} +16 -0
  48. package/dist/{messages-C-KPP7bC.mjs → chunks/messages-DD7BI6BK.mjs} +16 -0
  49. package/dist/{messages-ZMa-zmIc.mjs → chunks/messages-DHCVA7XQ.mjs} +16 -0
  50. package/dist/chunks/{messages-BPQA3B862.mjs → messages-DJA6fb_P2.mjs} +16 -0
  51. package/dist/chunks/{messages-D9SfB6MI.mjs → messages-DJkIeapn.mjs} +16 -0
  52. package/dist/{messages-CZkwcbV12.mjs → chunks/messages-DPykxECP2.mjs} +16 -0
  53. package/dist/chunks/{messages-BQvBhQem.mjs → messages-DQ5AyNCU.mjs} +16 -0
  54. package/dist/chunks/{messages-rxlf-Ule.mjs → messages-DR09nkcZ.mjs} +16 -0
  55. package/dist/chunks/{messages-B1uFbxNg.mjs → messages-DSjXen8E.mjs} +16 -0
  56. package/dist/chunks/{messages-OFFQT8Fg.mjs → messages-DT7fRpCy.mjs} +16 -0
  57. package/dist/{messages-DnNNd3RW2.mjs → chunks/messages-DUDgFEEe2.mjs} +16 -0
  58. package/dist/chunks/{messages-7PIvzufT.mjs → messages-Df87zXXG.mjs} +16 -0
  59. package/dist/chunks/{messages-CvA7Fbqf.mjs → messages-Dfpi8pDY.mjs} +16 -0
  60. package/dist/{messages-CXlAjnEQ.mjs → chunks/messages-Dm4YVlrm.mjs} +16 -0
  61. package/dist/chunks/{messages-CuJLHCj5.mjs → messages-Dplnp19q.mjs} +16 -0
  62. package/dist/{messages-DEfeDBuV2.mjs → chunks/messages-FHrCEJmY2.mjs} +16 -0
  63. package/dist/chunks/{messages-nZP1GShd.mjs → messages-ID1PHnMv.mjs} +16 -0
  64. package/dist/{messages-BiK5fMYF2.mjs → chunks/messages-IDEUsFhQ2.mjs} +16 -0
  65. package/dist/{messages-C672uqt-.mjs → chunks/messages-JQKFJo7C.mjs} +16 -0
  66. package/dist/{messages-DVHWIOfr2.mjs → chunks/messages-LxumrNue2.mjs} +16 -0
  67. package/dist/chunks/{messages-By-gACFM2.mjs → messages-R2W_rGOo2.mjs} +16 -0
  68. package/dist/chunks/{messages-FEjIF48t.mjs → messages-ZJ0b1C3a.mjs} +16 -0
  69. package/dist/{messages-nosa-xnx2.mjs → chunks/messages-d0Ky6QjR.mjs} +16 -0
  70. package/dist/{messages-CHaVGY89.mjs → chunks/messages-p4byLfvR.mjs} +16 -0
  71. package/dist/{messages-D-bnq2qy.mjs → chunks/messages-u2yxkNTE2.mjs} +16 -0
  72. package/dist/{messages-axsznSTn2.mjs → chunks/messages-wLSVQbsA2.mjs} +16 -0
  73. package/dist/chunks/{messages-DiaQNuPV.mjs → messages-xfjdrZmx.mjs} +16 -0
  74. package/dist/chunks/{objectSpread2-CyPxu8-u.mjs → objectSpread2-BY4mgzrQ.mjs} +1 -1
  75. package/dist/chunks/{tools-B6tibTzs.mjs → tools-C2_IVsQY.mjs} +2868 -1082
  76. package/dist/full.mjs +11 -11
  77. package/dist/locales.mjs +83 -67
  78. package/dist/{messages-BfbYJ8Wk2.mjs → messages--S8_taOd2.mjs} +16 -0
  79. package/dist/{chunks/messages-CkWidbwX2.mjs → messages-B-4fku2H2.mjs} +16 -0
  80. package/dist/{chunks/messages-Dn1ZDZUy.mjs → messages-BAcH6PtT2.mjs} +16 -0
  81. package/dist/{chunks/messages-2nj1xBDo.mjs → messages-BBq0M604.mjs} +16 -0
  82. package/dist/{messages-Dzqb5lg6.mjs → messages-BBvDbp62.mjs} +16 -0
  83. package/dist/{messages-kuLrhtV2.mjs → messages-BCG_evLg.mjs} +16 -0
  84. package/dist/{messages-DEKkIgU6.mjs → messages-BCuTVHBV.mjs} +16 -0
  85. package/dist/{chunks/messages-CYZL_rhV2.mjs → messages-BGsDZTQp2.mjs} +16 -0
  86. package/dist/{chunks/messages-cMwiuDZM.mjs → messages-BJ7BuFZi.mjs} +16 -0
  87. package/dist/{chunks/messages-Dl16RBg1.mjs → messages-BKjqW08U.mjs} +16 -0
  88. package/dist/{messages-Daza4lOM.mjs → messages-BSe3QDnQ.mjs} +16 -0
  89. package/dist/{chunks/messages-CDV8VcSZ2.mjs → messages-BVKZK-3t.mjs} +16 -0
  90. package/dist/{messages-CcPrYMHE.mjs → messages-BXI3qIos.mjs} +16 -0
  91. package/dist/{messages-B_6S7hBj2.mjs → messages-BcFQFcJ92.mjs} +16 -0
  92. package/dist/{chunks/messages-CmSsyItg.mjs → messages-Bio7KYsr2.mjs} +16 -0
  93. package/dist/{chunks/messages-CqdYWY192.mjs → messages-Bk984gRE2.mjs} +16 -0
  94. package/dist/{chunks/messages-CQgP-Fo22.mjs → messages-BmNaAyKS.mjs} +16 -0
  95. package/dist/{messages-DbPt9d2U.mjs → messages-Bo_FUvVH.mjs} +16 -0
  96. package/dist/{chunks/messages-x6GyZWWT.mjs → messages-BokEflKa.mjs} +16 -0
  97. package/dist/{chunks/messages-C9qPNbrJ2.mjs → messages-BtxaN-xx.mjs} +16 -0
  98. package/dist/{messages-6edZhK922.mjs → messages-BvgTQLf72.mjs} +16 -0
  99. package/dist/{chunks/messages-BVl-X2wo2.mjs → messages-BvgXeMSL2.mjs} +16 -0
  100. package/dist/{messages-BBauvpFc.mjs → messages-BywbKcPC.mjs} +16 -0
  101. package/dist/{chunks/messages-D06U2QNl2.mjs → messages-C30Vz-UZ2.mjs} +16 -0
  102. package/dist/{messages-BwyQiBdm2.mjs → messages-C5XPUD9T2.mjs} +16 -0
  103. package/dist/{chunks/messages-BivhofAQ2.mjs → messages-C6OJvnJg2.mjs} +16 -0
  104. package/dist/{chunks/messages-D6ZvH6hX.mjs → messages-C6Y4Jv2N.mjs} +16 -0
  105. package/dist/{messages-DaFDdCrr.mjs → messages-CBdQ3XP9.mjs} +16 -0
  106. package/dist/{chunks/messages-KihEeXdr.mjs → messages-CBzd_x7H.mjs} +16 -0
  107. package/dist/{messages-CsVZUVra.mjs → messages-CHJ5SOZI.mjs} +16 -0
  108. package/dist/{messages-us2JotS-2.mjs → messages-CM5fsPo02.mjs} +16 -0
  109. package/dist/{chunks/messages-DjhgPQtb.mjs → messages-CVcQD-9u.mjs} +16 -0
  110. package/dist/{messages-BQi_l2vs.mjs → messages-CYX48nfg.mjs} +16 -0
  111. package/dist/{chunks/messages-BF8c-lMm.mjs → messages-Ccd587Yn.mjs} +16 -0
  112. package/dist/{chunks/messages-5WyxUYVR2.mjs → messages-CdlsTFB1.mjs} +16 -0
  113. package/dist/{messages-ke15helG2.mjs → messages-CgzbJ8_l2.mjs} +16 -0
  114. package/dist/{messages-D1-_eTfM.mjs → messages-CjmSrt1D.mjs} +16 -0
  115. package/dist/{chunks/messages-C7YpgZ9m.mjs → messages-CqkRG9mH.mjs} +16 -0
  116. package/dist/{messages-Dtw27ih4.mjs → messages-D3cAcyzj.mjs} +16 -0
  117. package/dist/{messages-CLN3oL77.mjs → messages-DA-o8X3A.mjs} +16 -0
  118. package/dist/{chunks/messages-BtZ8oQXS.mjs → messages-DD5pW0zJ.mjs} +16 -0
  119. package/dist/{chunks/messages-A3Z4jxwt2.mjs → messages-DIRha_gg2.mjs} +16 -0
  120. package/dist/{chunks/messages-rnd6qiJ12.mjs → messages-DJKLtW7u.mjs} +16 -0
  121. package/dist/{messages-h_VN1Kyo2.mjs → messages-DJT4Bt_02.mjs} +16 -0
  122. package/dist/{chunks/messages-D3hNTep_.mjs → messages-DOTJ2NvJ.mjs} +16 -0
  123. package/dist/{chunks/messages-B18MZnaY.mjs → messages-DUBHHfEt.mjs} +16 -0
  124. package/dist/{chunks/messages-92ma9RJD2.mjs → messages-DV9e1DW7.mjs} +16 -0
  125. package/dist/{chunks/messages-DQUUtL5v.mjs → messages-DVQNjdPk.mjs} +16 -0
  126. package/dist/{messages-DflAKRLd.mjs → messages-DZEcrbmH.mjs} +16 -0
  127. package/dist/{messages-BLjz6V7y2.mjs → messages-DbxbxUiK2.mjs} +16 -0
  128. package/dist/{messages-QkGAxuVC2.mjs → messages-Dcyrzdxa2.mjs} +16 -0
  129. package/dist/{chunks/messages-DBZ-uuAV.mjs → messages-Dddxv8-f2.mjs} +16 -0
  130. package/dist/{messages-DVILiJw52.mjs → messages-DjJQoYvP2.mjs} +16 -0
  131. package/dist/{messages-B-lXqB1z2.mjs → messages-Dkg99bfr2.mjs} +16 -0
  132. package/dist/{messages-CCBgCmyq.mjs → messages-DnatBKPm.mjs} +16 -0
  133. package/dist/{messages-Dn-khi3a.mjs → messages-DqyqEw1_.mjs} +16 -0
  134. package/dist/{chunks/messages-D4QXMtW52.mjs → messages-DtrSrdfE2.mjs} +16 -0
  135. package/dist/{messages-BHIdzfAS2.mjs → messages-JhoVMjfX2.mjs} +16 -0
  136. package/dist/{messages-YlWV9cSl.mjs → messages-eTourT12.mjs} +16 -0
  137. package/dist/{messages-DzlmoWqQ.mjs → messages-fLi0P2dP.mjs} +16 -0
  138. package/dist/{messages-C-v50b4r.mjs → messages-hTpeKUaW.mjs} +16 -0
  139. package/dist/{chunks/messages-16UmLAWZ2.mjs → messages-i4S6q64n2.mjs} +16 -0
  140. package/dist/{messages-DP9PhNTo.mjs → messages-pgPcitDH.mjs} +16 -0
  141. package/dist/{chunks/messages-D5iRrf9M.mjs → messages-tK67CBqn.mjs} +16 -0
  142. package/dist/{chunks/messages-BTyEo5Kb.mjs → messages-tsHpMdDT2.mjs} +16 -0
  143. package/dist/{chunks/messages-Ct_H_5cB2.mjs → messages-upqrRZQH2.mjs} +16 -0
  144. package/dist/{messages-CyZZ10br.mjs → messages-xEI8gEDK.mjs} +16 -0
  145. package/dist/react.mjs +3 -3
  146. package/dist/tools.mjs +3 -3
  147. package/dist/vendor.LICENSE.txt +136 -1
  148. package/package.json +2 -1
  149. package/src/components/block/style-manager.ts +1 -1
  150. package/src/components/i18n/locales/am/messages.json +16 -0
  151. package/src/components/i18n/locales/ar/messages.json +16 -0
  152. package/src/components/i18n/locales/az/messages.json +16 -0
  153. package/src/components/i18n/locales/bg/messages.json +16 -0
  154. package/src/components/i18n/locales/bn/messages.json +16 -0
  155. package/src/components/i18n/locales/bs/messages.json +16 -0
  156. package/src/components/i18n/locales/cs/messages.json +16 -0
  157. package/src/components/i18n/locales/da/messages.json +16 -0
  158. package/src/components/i18n/locales/de/messages.json +16 -0
  159. package/src/components/i18n/locales/dv/messages.json +16 -0
  160. package/src/components/i18n/locales/el/messages.json +16 -0
  161. package/src/components/i18n/locales/en/messages.json +16 -0
  162. package/src/components/i18n/locales/es/messages.json +16 -0
  163. package/src/components/i18n/locales/et/messages.json +16 -0
  164. package/src/components/i18n/locales/fa/messages.json +16 -0
  165. package/src/components/i18n/locales/fi/messages.json +16 -0
  166. package/src/components/i18n/locales/fil/messages.json +16 -0
  167. package/src/components/i18n/locales/fr/messages.json +16 -0
  168. package/src/components/i18n/locales/gu/messages.json +16 -0
  169. package/src/components/i18n/locales/he/messages.json +16 -0
  170. package/src/components/i18n/locales/hi/messages.json +16 -0
  171. package/src/components/i18n/locales/hr/messages.json +16 -0
  172. package/src/components/i18n/locales/hu/messages.json +16 -0
  173. package/src/components/i18n/locales/hy/messages.json +16 -0
  174. package/src/components/i18n/locales/id/messages.json +16 -0
  175. package/src/components/i18n/locales/it/messages.json +16 -0
  176. package/src/components/i18n/locales/ja/messages.json +16 -0
  177. package/src/components/i18n/locales/ka/messages.json +16 -0
  178. package/src/components/i18n/locales/km/messages.json +16 -0
  179. package/src/components/i18n/locales/kn/messages.json +16 -0
  180. package/src/components/i18n/locales/ko/messages.json +16 -0
  181. package/src/components/i18n/locales/ku/messages.json +16 -0
  182. package/src/components/i18n/locales/lo/messages.json +16 -0
  183. package/src/components/i18n/locales/lt/messages.json +16 -0
  184. package/src/components/i18n/locales/lv/messages.json +16 -0
  185. package/src/components/i18n/locales/mk/messages.json +16 -0
  186. package/src/components/i18n/locales/ml/messages.json +16 -0
  187. package/src/components/i18n/locales/mn/messages.json +16 -0
  188. package/src/components/i18n/locales/mr/messages.json +16 -0
  189. package/src/components/i18n/locales/ms/messages.json +16 -0
  190. package/src/components/i18n/locales/my/messages.json +16 -0
  191. package/src/components/i18n/locales/ne/messages.json +16 -0
  192. package/src/components/i18n/locales/nl/messages.json +16 -0
  193. package/src/components/i18n/locales/no/messages.json +16 -0
  194. package/src/components/i18n/locales/pa/messages.json +16 -0
  195. package/src/components/i18n/locales/pl/messages.json +16 -0
  196. package/src/components/i18n/locales/ps/messages.json +16 -0
  197. package/src/components/i18n/locales/pt/messages.json +16 -0
  198. package/src/components/i18n/locales/ro/messages.json +16 -0
  199. package/src/components/i18n/locales/ru/messages.json +16 -0
  200. package/src/components/i18n/locales/sd/messages.json +16 -0
  201. package/src/components/i18n/locales/si/messages.json +16 -0
  202. package/src/components/i18n/locales/sk/messages.json +16 -0
  203. package/src/components/i18n/locales/sl/messages.json +16 -0
  204. package/src/components/i18n/locales/sq/messages.json +16 -0
  205. package/src/components/i18n/locales/sr/messages.json +16 -0
  206. package/src/components/i18n/locales/sv/messages.json +16 -0
  207. package/src/components/i18n/locales/sw/messages.json +16 -0
  208. package/src/components/i18n/locales/ta/messages.json +16 -0
  209. package/src/components/i18n/locales/te/messages.json +16 -0
  210. package/src/components/i18n/locales/th/messages.json +16 -0
  211. package/src/components/i18n/locales/tr/messages.json +16 -0
  212. package/src/components/i18n/locales/ug/messages.json +16 -0
  213. package/src/components/i18n/locales/uk/messages.json +16 -0
  214. package/src/components/i18n/locales/ur/messages.json +16 -0
  215. package/src/components/i18n/locales/vi/messages.json +16 -0
  216. package/src/components/i18n/locales/yi/messages.json +16 -0
  217. package/src/components/i18n/locales/zh/messages.json +16 -0
  218. package/src/components/icons/index.ts +56 -0
  219. package/src/components/modules/api/tools.ts +19 -0
  220. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +5 -5
  221. package/src/components/modules/blockManager/blockManager.ts +11 -0
  222. package/src/components/modules/blockManager/event-binder.ts +12 -1
  223. package/src/components/modules/themeManager.ts +3 -1
  224. package/src/components/modules/toolbar/blockSettings.ts +0 -1
  225. package/src/components/modules/toolbar/inline/index.ts +0 -3
  226. package/src/components/modules/uiControllers/controllers/keyboard.ts +29 -0
  227. package/src/components/modules/uiControllers/controllers/selection.ts +14 -2
  228. package/src/components/ui/toolbox.ts +0 -1
  229. package/src/components/utils/popover/popover-position.ts +4 -2
  230. package/src/styles/main.css +1139 -0
  231. package/src/tools/callout/constants.ts +2 -1
  232. package/src/tools/database/database-backend-sync.ts +101 -0
  233. package/src/tools/database/database-board-view.ts +301 -0
  234. package/src/tools/database/database-card-drag.ts +306 -0
  235. package/src/tools/database/database-card-drawer.ts +546 -0
  236. package/src/tools/database/database-column-controls.ts +46 -0
  237. package/src/tools/database/database-column-drag.ts +262 -0
  238. package/src/tools/database/database-keyboard.ts +35 -0
  239. package/src/tools/database/database-list-row-drag.ts +245 -0
  240. package/src/tools/database/database-list-view.ts +333 -0
  241. package/src/tools/database/database-model.ts +246 -0
  242. package/src/tools/database/database-property-type-popover.ts +108 -0
  243. package/src/tools/database/database-tab-bar.ts +532 -0
  244. package/src/tools/database/database-view-popover.ts +109 -0
  245. package/src/tools/database/database-view-renderer.ts +25 -0
  246. package/src/tools/database/index.ts +948 -0
  247. package/src/tools/database/types.ts +144 -0
  248. package/src/tools/index.ts +2 -0
  249. package/types/api/tools.d.ts +18 -0
  250. package/types/index.d.ts +16 -0
  251. package/types/tools/database.d.ts +145 -0
@@ -0,0 +1,948 @@
1
+ import type { API, BlockAPI, BlockTool, BlockToolConstructorOptions, OutputData, ToolboxConfig } from '../../../types';
2
+ import type { DatabaseData, DatabaseConfig, DatabaseRow, ViewType, SelectOption, DatabaseViewConfig, PropertyValue } from './types';
3
+ import { DatabaseModel } from './database-model';
4
+ import { DatabaseBoardView } from './database-board-view';
5
+ import { DatabaseListView } from './database-list-view';
6
+ import type { DatabaseViewRenderer } from './database-view-renderer';
7
+ import { DatabaseBackendSync } from './database-backend-sync';
8
+ import { DatabaseCardDrag } from './database-card-drag';
9
+ import type { CardDragResult } from './database-card-drag';
10
+ import { DatabaseColumnDrag } from './database-column-drag';
11
+ import type { GroupDragResult } from './database-column-drag';
12
+ import { DatabaseColumnControls } from './database-column-controls';
13
+ import { DatabaseListRowDrag } from './database-list-row-drag';
14
+ import type { ListRowDragResult } from './database-list-row-drag';
15
+ import { DatabaseCardDrawer } from './database-card-drawer';
16
+ import { DatabaseKeyboard } from './database-keyboard';
17
+ import { DatabaseTabBar } from './database-tab-bar';
18
+ import { IconDatabase, IconBoard } from '../../components/icons';
19
+ import { nanoid } from 'nanoid';
20
+
21
+ /**
22
+ * DatabaseTool — a multi-view Kanban board block tool for Blok.
23
+ *
24
+ * Orchestrates a single DatabaseModel (schema + rows + view configs), DatabaseView (DOM),
25
+ * DatabaseBackendSync (adapter), and a DatabaseTabBar for view switching.
26
+ */
27
+ export class DatabaseTool implements BlockTool {
28
+ private readonly api: API;
29
+ private readonly block: BlockAPI;
30
+ private readonly readOnly: boolean;
31
+ private readonly config: DatabaseConfig;
32
+
33
+ private activeViewId: string;
34
+ private model: DatabaseModel;
35
+ private view!: DatabaseViewRenderer;
36
+ private sync!: DatabaseBackendSync;
37
+
38
+ private element: HTMLDivElement | null = null;
39
+ private boardContainer: HTMLDivElement | null = null;
40
+ private tabBar: DatabaseTabBar | null = null;
41
+
42
+ private cardDrag: DatabaseCardDrag | null = null;
43
+ private columnDrag: DatabaseColumnDrag | null = null;
44
+ private columnControls: DatabaseColumnControls | null = null;
45
+ private listRowDrag: DatabaseListRowDrag | null = null;
46
+ private cardDrawer: DatabaseCardDrawer | null = null;
47
+ private keyboard: DatabaseKeyboard | null = null;
48
+
49
+ constructor({ data, config, api, block, readOnly }: BlockToolConstructorOptions<DatabaseData, DatabaseConfig>) {
50
+ this.api = api;
51
+ this.block = block;
52
+ this.readOnly = readOnly;
53
+ this.config = config ?? {};
54
+
55
+ this.model = new DatabaseModel(data as DatabaseData | undefined);
56
+ const views = this.model.getViews();
57
+ this.activeViewId = (data as DatabaseData | undefined)?.activeViewId ?? (views.length > 0 ? views[0].id : '');
58
+
59
+ this.activateView(this.activeViewId);
60
+ }
61
+
62
+ static get toolbox(): ToolboxConfig {
63
+ return [
64
+ {
65
+ icon: IconDatabase,
66
+ title: 'Database',
67
+ titleKey: 'database',
68
+ name: 'database',
69
+ searchTerms: ['database', 'kanban', 'board', 'cards', 'columns'],
70
+ },
71
+ {
72
+ icon: IconBoard,
73
+ title: 'Board',
74
+ titleKey: 'board',
75
+ name: 'board',
76
+ searchTerms: ['board', 'kanban', 'cards', 'columns', 'database'],
77
+ },
78
+ ];
79
+ }
80
+
81
+ static get isReadOnlySupported(): boolean {
82
+ return true;
83
+ }
84
+
85
+ render(): HTMLDivElement {
86
+ const wrapper = document.createElement('div');
87
+ wrapper.setAttribute('data-blok-tool', 'database');
88
+ wrapper.setAttribute('data-blok-database-wrapper', '');
89
+ wrapper.style.display = 'flex';
90
+ wrapper.style.flexDirection = 'column';
91
+ this.element = wrapper;
92
+
93
+ if (!this.readOnly) {
94
+ this.tabBar = this.createTabBar();
95
+ wrapper.appendChild(this.tabBar.render());
96
+ }
97
+
98
+ const boardContainer = document.createElement('div');
99
+ boardContainer.setAttribute('data-blok-database-board-container', '');
100
+ boardContainer.style.overflow = 'hidden';
101
+ boardContainer.style.position = 'relative';
102
+ this.boardContainer = boardContainer;
103
+ wrapper.appendChild(boardContainer);
104
+
105
+ const boardEl = this.renderActiveView();
106
+ boardContainer.appendChild(boardEl);
107
+
108
+ if (!this.readOnly) {
109
+ this.attachViewListeners(boardEl);
110
+ this.initSubsystems(boardEl);
111
+ }
112
+
113
+ return wrapper;
114
+ }
115
+
116
+ rendered(): void {
117
+ this.block.stretched = true;
118
+ if (this.config.adapter !== undefined) {
119
+ void this.loadFromBackend();
120
+ }
121
+ }
122
+
123
+ private async loadFromBackend(): Promise<void> {
124
+ const data = await this.sync.syncLoadDatabase();
125
+
126
+ if (data === undefined) {
127
+ return;
128
+ }
129
+
130
+ this.model.hydrate(data);
131
+ const views = this.model.getViews();
132
+
133
+ if (views.length > 0 && !views.some((v) => v.id === this.activeViewId)) {
134
+ this.activeViewId = views[0].id;
135
+ }
136
+
137
+ this.rerenderView();
138
+ }
139
+
140
+ save(_blockContent: HTMLElement): DatabaseData {
141
+ return {
142
+ ...this.model.snapshot(),
143
+ activeViewId: this.activeViewId,
144
+ };
145
+ }
146
+
147
+ validate(savedData: DatabaseData): boolean {
148
+ const hasTitleProp = savedData.schema?.some((p) => p.type === 'title') ?? false;
149
+ const hasViews = savedData.views !== undefined && savedData.views.length > 0;
150
+ const boardViewsValid = savedData.views?.filter((v) => v.type === 'board')
151
+ .every((v) => v.groupBy !== undefined) ?? true;
152
+ return hasTitleProp && hasViews && boardViewsValid;
153
+ }
154
+
155
+ destroy(): void {
156
+ this.cardDrag?.destroy();
157
+ this.columnDrag?.destroy();
158
+ this.columnControls?.destroy();
159
+ this.listRowDrag?.destroy();
160
+ this.listRowDrag = null;
161
+ this.cardDrawer?.destroy();
162
+ this.keyboard?.destroy();
163
+ this.tabBar?.destroy();
164
+ this.sync.flushPendingUpdates();
165
+ this.sync.destroy();
166
+ this.element = null;
167
+ this.boardContainer = null;
168
+ this.tabBar = null;
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // View management
173
+ // ---------------------------------------------------------------------------
174
+
175
+ private activateView(viewId: string): void {
176
+ const viewConfig = this.model.getView(viewId);
177
+
178
+ if (viewConfig === undefined) {
179
+ return;
180
+ }
181
+
182
+ this.activeViewId = viewId;
183
+ this.sync = new DatabaseBackendSync(
184
+ this.config.adapter,
185
+ (error) => {
186
+ this.api.notifier.show({
187
+ message: String(error),
188
+ style: 'error',
189
+ });
190
+ },
191
+ );
192
+ }
193
+
194
+ private switchView(viewId: string): void {
195
+ if (viewId === this.activeViewId || this.boardContainer === null) {
196
+ return;
197
+ }
198
+
199
+ // Destroy per-view subsystems (not cardDrawer)
200
+ this.cardDrag?.destroy();
201
+ this.columnDrag?.destroy();
202
+ this.columnControls?.destroy();
203
+ this.listRowDrag?.destroy();
204
+ this.keyboard?.destroy();
205
+ this.cardDrag = null;
206
+ this.columnDrag = null;
207
+ this.columnControls = null;
208
+ this.listRowDrag = null;
209
+ this.keyboard = null;
210
+
211
+ this.sync.flushPendingUpdates();
212
+ this.sync.destroy();
213
+
214
+ this.activateView(viewId);
215
+
216
+ this.boardContainer.innerHTML = '';
217
+ const newBoardWrapper = this.renderActiveView();
218
+ this.boardContainer.appendChild(newBoardWrapper);
219
+
220
+ if (!this.readOnly) {
221
+ this.attachViewListeners(newBoardWrapper);
222
+ this.initSubsystems(newBoardWrapper);
223
+ }
224
+
225
+ this.rebuildTabBar();
226
+ }
227
+
228
+ addView(type: ViewType): void {
229
+ const statusProp = this.model.getSchema().find((p) => p.type === 'select');
230
+ const defaultName = type === 'list' ? 'List' : 'Board';
231
+ const newView = this.model.addView(defaultName, type, {
232
+ groupBy: type === 'board' ? statusProp?.id : undefined,
233
+ });
234
+ void this.sync.syncCreateView({ id: newView.id, name: newView.name, type: newView.type, position: newView.position, groupBy: newView.groupBy });
235
+ this.switchView(newView.id);
236
+ }
237
+
238
+ renameView(viewId: string, name: string): void {
239
+ this.model.updateView(viewId, { name });
240
+ void this.sync.syncUpdateView({ viewId, changes: { name } });
241
+ }
242
+
243
+ duplicateView(viewId: string): void {
244
+ const sourceView = this.model.getView(viewId);
245
+
246
+ if (sourceView === undefined) {
247
+ return;
248
+ }
249
+
250
+ const newView = this.model.addView(sourceView.name, sourceView.type, {
251
+ groupBy: sourceView.groupBy,
252
+ sorts: [...sourceView.sorts],
253
+ filters: [...sourceView.filters],
254
+ visibleProperties: [...sourceView.visibleProperties],
255
+ });
256
+ void this.sync.syncCreateView({ id: newView.id, name: newView.name, type: newView.type, position: newView.position, groupBy: newView.groupBy });
257
+ this.switchView(newView.id);
258
+ }
259
+
260
+ deleteView(viewId: string): void {
261
+ const views = this.model.getViews();
262
+
263
+ if (views.length <= 1) {
264
+ return;
265
+ }
266
+
267
+ const index = views.findIndex((v) => v.id === viewId);
268
+
269
+ if (index === -1) {
270
+ return;
271
+ }
272
+
273
+ const wasActive = viewId === this.activeViewId;
274
+
275
+ this.model.deleteView(viewId);
276
+ void this.sync.syncDeleteView({ viewId });
277
+
278
+ if (wasActive) {
279
+ const remaining = this.model.getViews();
280
+ const neighborIndex = Math.min(index, remaining.length - 1);
281
+ this.switchView(remaining[neighborIndex].id);
282
+ } else {
283
+ this.rebuildTabBar();
284
+ }
285
+ }
286
+
287
+ reorderView(viewId: string, newPosition: string): void {
288
+ this.model.updateView(viewId, { position: newPosition });
289
+ void this.sync.syncUpdateView({ viewId, changes: { position: newPosition } });
290
+ this.rebuildTabBar();
291
+ }
292
+
293
+ private rebuildTabBar(): void {
294
+ if (this.element === null || this.tabBar === null) {
295
+ return;
296
+ }
297
+
298
+ const oldBarEl = this.element.querySelector('[data-blok-database-tab-bar]');
299
+
300
+ this.tabBar.destroy();
301
+ this.tabBar = this.createTabBar();
302
+ const newBarEl = this.tabBar.render();
303
+
304
+ if (oldBarEl !== null) {
305
+ oldBarEl.replaceWith(newBarEl);
306
+ } else {
307
+ // Tab bar should be first child (before boardContainer)
308
+ this.element.insertBefore(newBarEl, this.boardContainer);
309
+ }
310
+ }
311
+
312
+ private createTabBar(): DatabaseTabBar {
313
+ return new DatabaseTabBar({
314
+ views: this.model.getViews(),
315
+ activeViewId: this.activeViewId,
316
+ onTabClick: (viewId) => this.switchView(viewId),
317
+ onAddView: (type) => this.addView(type),
318
+ onRename: (viewId, name) => this.renameView(viewId, name),
319
+ onDuplicate: (viewId) => this.duplicateView(viewId),
320
+ onDelete: (viewId) => this.deleteView(viewId),
321
+ onReorder: (viewId, newPosition) => this.reorderView(viewId, newPosition),
322
+ });
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Board rendering helpers
327
+ // ---------------------------------------------------------------------------
328
+
329
+ private renderActiveView(): HTMLDivElement {
330
+ const viewConfig = this.model.getView(this.activeViewId);
331
+ const titleProp = this.model.getSchema().find((p) => p.type === 'title');
332
+ const titlePropId = titleProp?.id ?? '';
333
+ const groupByPropId = viewConfig?.groupBy;
334
+
335
+ if (viewConfig?.type === 'list') {
336
+ return this.renderListView(titlePropId, groupByPropId, viewConfig);
337
+ }
338
+
339
+ return this.renderBoardView(titlePropId, groupByPropId);
340
+ }
341
+
342
+ private renderBoardView(titlePropId: string, groupByPropId: string | undefined): HTMLDivElement {
343
+ const options = groupByPropId !== undefined ? this.model.getSelectOptions(groupByPropId) : [];
344
+ const groups: Map<string, DatabaseRow[]> = groupByPropId !== undefined ? this.model.getRowsGroupedBy(groupByPropId) : new Map<string, DatabaseRow[]>();
345
+
346
+ this.view = new DatabaseBoardView({
347
+ readOnly: this.readOnly,
348
+ i18n: this.api.i18n,
349
+ options,
350
+ getRows: (optionId) => groups.get(optionId) ?? [],
351
+ titlePropertyId: titlePropId,
352
+ });
353
+
354
+ return this.view.createView();
355
+ }
356
+
357
+ private renderListView(titlePropId: string, groupByPropId: string | undefined, viewConfig: DatabaseViewConfig): HTMLDivElement {
358
+ const schema = this.model.getSchema();
359
+
360
+ if (groupByPropId !== undefined) {
361
+ const options = this.model.getSelectOptions(groupByPropId);
362
+ const groups = this.model.getRowsGroupedBy(groupByPropId);
363
+
364
+ this.view = new DatabaseListView({
365
+ readOnly: this.readOnly,
366
+ i18n: this.api.i18n,
367
+ rows: [],
368
+ titlePropertyId: titlePropId,
369
+ schema,
370
+ visiblePropertyIds: viewConfig.visibleProperties,
371
+ options,
372
+ getRows: (optionId) => groups.get(optionId) ?? [],
373
+ });
374
+ } else {
375
+ this.view = new DatabaseListView({
376
+ readOnly: this.readOnly,
377
+ i18n: this.api.i18n,
378
+ rows: this.model.getOrderedRows(),
379
+ titlePropertyId: titlePropId,
380
+ schema,
381
+ visiblePropertyIds: viewConfig.visibleProperties,
382
+ });
383
+ }
384
+
385
+ return this.view.createView();
386
+ }
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // Event listeners & subsystems
390
+ // ---------------------------------------------------------------------------
391
+
392
+ /**
393
+ * Attaches a single click listener on the board element for event delegation.
394
+ */
395
+ private attachViewListeners(boardEl: HTMLDivElement): void {
396
+ boardEl.addEventListener('click', (event) => {
397
+ const target = event.target as HTMLElement;
398
+
399
+ const addCardBtn = target.closest('[data-blok-database-add-card]');
400
+
401
+ if (addCardBtn !== null) {
402
+ const optionId = addCardBtn.getAttribute('data-option-id');
403
+
404
+ if (optionId !== null) {
405
+ this.handleAddRow(optionId, boardEl);
406
+ }
407
+
408
+ return;
409
+ }
410
+
411
+ const addColumnBtn = target.closest('[data-blok-database-add-column]');
412
+
413
+ if (addColumnBtn !== null) {
414
+ this.handleAddColumn(boardEl);
415
+
416
+ return;
417
+ }
418
+
419
+ const deleteCardBtn = target.closest('[data-blok-database-delete-card]');
420
+
421
+ if (deleteCardBtn !== null) {
422
+ const rowId = deleteCardBtn.getAttribute('data-row-id');
423
+
424
+ if (rowId !== null) {
425
+ event.stopPropagation();
426
+ this.model.deleteRow(rowId);
427
+ this.view.removeRow(boardEl, rowId);
428
+ void this.sync.syncDeleteRow({ rowId });
429
+ }
430
+
431
+ return;
432
+ }
433
+
434
+ // List: add row
435
+ const addRowBtn = target.closest('[data-blok-database-add-row]');
436
+
437
+ if (addRowBtn !== null) {
438
+ const optionId = addRowBtn.getAttribute('data-option-id');
439
+ this.handleAddListRow(optionId, boardEl);
440
+ return;
441
+ }
442
+
443
+ // List: delete row
444
+ const deleteRowBtn = target.closest('[data-blok-database-delete-row]');
445
+
446
+ if (deleteRowBtn !== null) {
447
+ const rowId = deleteRowBtn.getAttribute('data-row-id');
448
+
449
+ if (rowId !== null) {
450
+ event.stopPropagation();
451
+ this.model.deleteRow(rowId);
452
+ this.view.removeRow(boardEl, rowId);
453
+ void this.sync.syncDeleteRow({ rowId });
454
+ }
455
+
456
+ return;
457
+ }
458
+
459
+ // List: row click
460
+ const listRowEl = target.closest('[data-blok-database-list-row]');
461
+
462
+ if (listRowEl !== null) {
463
+ const rowId = listRowEl.getAttribute('data-row-id');
464
+
465
+ if (rowId !== null) {
466
+ this.handleRowClick(rowId);
467
+ }
468
+
469
+ return;
470
+ }
471
+
472
+ const cardEl = target.closest('[data-blok-database-card]');
473
+
474
+ if (cardEl !== null) {
475
+ const rowId = cardEl.getAttribute('data-row-id');
476
+
477
+ if (rowId !== null) {
478
+ this.handleRowClick(rowId);
479
+ }
480
+ }
481
+ });
482
+ }
483
+
484
+ private handleAddListRow(optionId: string | null, viewEl: HTMLDivElement): void {
485
+ const titleProp = this.model.getSchema().find((p) => p.type === 'title');
486
+ const titlePropId = titleProp?.id ?? '';
487
+ const viewConfig = this.model.getView(this.activeViewId);
488
+ const groupByPropId = viewConfig?.groupBy;
489
+
490
+ const properties: Record<string, PropertyValue> = { [titlePropId]: '' };
491
+
492
+ if (groupByPropId !== undefined && optionId !== null) {
493
+ properties[groupByPropId] = optionId;
494
+ }
495
+
496
+ const row = this.model.addRow(properties);
497
+ this.view.appendRow(viewEl, row);
498
+
499
+ void this.sync.syncCreateRow({
500
+ id: row.id,
501
+ properties: row.properties,
502
+ position: row.position,
503
+ });
504
+ }
505
+
506
+ private handleAddRow(optionId: string, boardEl: HTMLDivElement): void {
507
+ const viewConfig = this.model.getView(this.activeViewId);
508
+ const groupByPropId = viewConfig?.groupBy;
509
+
510
+ if (groupByPropId === undefined) {
511
+ return;
512
+ }
513
+
514
+ const titleProp = this.model.getSchema().find((p) => p.type === 'title');
515
+ const titlePropId = titleProp?.id ?? '';
516
+ const row = this.model.addRow({
517
+ [titlePropId]: '',
518
+ [groupByPropId]: optionId,
519
+ });
520
+
521
+ const columnEl = boardEl.querySelector(`[data-option-id="${optionId}"][data-blok-database-column]`);
522
+
523
+ if (columnEl === null) {
524
+ return;
525
+ }
526
+
527
+ const cardsContainer = columnEl.querySelector('[data-blok-database-cards]');
528
+
529
+ if (cardsContainer === null) {
530
+ return;
531
+ }
532
+
533
+ this.view.appendRow(cardsContainer as HTMLElement, row);
534
+
535
+ void this.sync.syncCreateRow({
536
+ id: row.id,
537
+ properties: row.properties,
538
+ position: row.position,
539
+ });
540
+ }
541
+
542
+ private handleAddColumn(boardEl: HTMLDivElement): void {
543
+ const viewConfig = this.model.getView(this.activeViewId);
544
+ const groupByPropId = viewConfig?.groupBy;
545
+
546
+ if (groupByPropId === undefined) {
547
+ return;
548
+ }
549
+
550
+ const prop = this.model.getProperty(groupByPropId);
551
+
552
+ if (prop?.config === undefined) {
553
+ return;
554
+ }
555
+
556
+ const existingOptions = prop.config.options;
557
+ const lastPos = existingOptions.length > 0 ? existingOptions[existingOptions.length - 1].position : null;
558
+ const newOption: SelectOption = {
559
+ id: nanoid(),
560
+ label: this.api.i18n.t('tools.database.columnTitlePlaceholder'),
561
+ position: DatabaseModel.positionBetween(lastPos, null),
562
+ };
563
+
564
+ this.model.updateProperty(groupByPropId, {
565
+ config: { options: [...existingOptions, newOption] },
566
+ });
567
+
568
+ this.view.appendGroup?.(boardEl, newOption);
569
+ void this.sync.syncUpdateProperty({ propertyId: groupByPropId, changes: { config: { options: [...existingOptions, newOption] } } });
570
+ }
571
+
572
+ /**
573
+ * Initializes all subsystems: card drag, column drag, column controls, card drawer, keyboard.
574
+ * boardEl is the current board element for drag/column operations.
575
+ * cardDrawer is attached to this.element (outer wrapper) so it persists across view switches.
576
+ */
577
+ private initSubsystems(boardEl: HTMLDivElement): void {
578
+ if (this.element === null) {
579
+ return;
580
+ }
581
+
582
+ const viewConfig = this.model.getView(this.activeViewId);
583
+ const isList = viewConfig?.type === 'list';
584
+
585
+ const titleProp = this.model.getSchema().find((p) => p.type === 'title');
586
+ const titlePropId = titleProp?.id ?? '';
587
+ const descriptionProp = this.model.getSchema().find((p) => p.type === 'richText');
588
+ const descriptionPropId = descriptionProp?.id;
589
+
590
+ if (isList) {
591
+ this.listRowDrag = new DatabaseListRowDrag({
592
+ wrapper: boardEl,
593
+ onDrop: (result) => this.handleListRowDrop(result),
594
+ });
595
+ } else {
596
+ this.cardDrag = new DatabaseCardDrag({
597
+ wrapper: boardEl,
598
+ onDrop: (result) => this.handleRowDrop(result),
599
+ });
600
+
601
+ this.columnDrag = new DatabaseColumnDrag({
602
+ wrapper: boardEl,
603
+ onDrop: (result) => this.handleGroupDrop(result),
604
+ });
605
+
606
+ this.columnControls = new DatabaseColumnControls({
607
+ i18n: this.api.i18n,
608
+ onRename: (optionId, label) => this.handleOptionRename(optionId, label),
609
+ onDelete: (optionId) => this.handleOptionDelete(optionId, boardEl),
610
+ });
611
+
612
+ this.makeColumnHeadersEditable(boardEl);
613
+ }
614
+
615
+ // cardDrawer is attached to outer wrapper so it stays across view switches
616
+ if (this.cardDrawer === null) {
617
+ this.cardDrawer = new DatabaseCardDrawer({
618
+ wrapper: this.element,
619
+ readOnly: this.readOnly,
620
+ i18n: this.api.i18n,
621
+ toolsConfig: this.api.tools.getToolsConfig(),
622
+ titlePropertyId: titlePropId,
623
+ descriptionPropertyId: descriptionPropId,
624
+ schema: this.model.getSchema(),
625
+ onTitleChange: (rowId, title) => {
626
+ this.model.updateRow(rowId, { [titlePropId]: title });
627
+ const currentView = this.boardContainer?.querySelector<HTMLElement>('[data-blok-database-board]')
628
+ ?? this.boardContainer?.querySelector<HTMLElement>('[data-blok-database-list]');
629
+
630
+ if (currentView !== null && currentView !== undefined) {
631
+ this.view.updateRowTitle(currentView, rowId, title);
632
+ }
633
+
634
+ this.sync.syncUpdateRow({ rowId, properties: { [titlePropId]: title } });
635
+ },
636
+ onDescriptionChange: (rowId, description: OutputData) => {
637
+ if (descriptionPropId !== undefined) {
638
+ this.model.updateRow(rowId, { [descriptionPropId]: description });
639
+ this.sync.syncUpdateRow({ rowId, properties: { [descriptionPropId]: description } });
640
+ }
641
+ },
642
+ onClose: () => { /* no-op; drawer handles its own DOM cleanup */ },
643
+ onAddProperty: (type) => {
644
+ const prop = this.model.addProperty('Property', type);
645
+ void this.sync.syncCreateProperty({
646
+ id: prop.id,
647
+ name: prop.name,
648
+ type: prop.type,
649
+ position: prop.position,
650
+ });
651
+ this.cardDrawer?.refreshSchema(this.model.getSchema());
652
+ },
653
+ });
654
+ }
655
+
656
+ this.keyboard = new DatabaseKeyboard({
657
+ wrapper: boardEl,
658
+ onEscape: () => {
659
+ if (this.cardDrawer?.isOpen) {
660
+ this.cardDrawer.close();
661
+
662
+ return true;
663
+ }
664
+
665
+ return false;
666
+ },
667
+ });
668
+ this.keyboard.attach();
669
+
670
+ boardEl.addEventListener('pointerdown', (e) => {
671
+ const target = e.target as HTMLElement;
672
+
673
+ // Board: column header drag
674
+ const columnHeader = target.closest('[data-blok-database-column-header]');
675
+
676
+ if (columnHeader !== null) {
677
+ const columnEl = columnHeader.closest<HTMLElement>('[data-blok-database-column]');
678
+ const optId = columnEl?.getAttribute('data-option-id') ?? null;
679
+
680
+ if (optId !== null) {
681
+ e.preventDefault();
682
+ e.stopPropagation();
683
+ this.columnDrag?.beginTracking(optId, e.clientX, e.clientY);
684
+ }
685
+
686
+ return;
687
+ }
688
+
689
+ // Board: card drag
690
+ const cardEl = target.closest('[data-blok-database-card]');
691
+
692
+ if (cardEl !== null) {
693
+ const rowId = cardEl.getAttribute('data-row-id');
694
+
695
+ if (rowId !== null) {
696
+ e.preventDefault();
697
+ e.stopPropagation();
698
+ this.cardDrag?.beginTracking(rowId, e.clientX, e.clientY);
699
+ }
700
+
701
+ return;
702
+ }
703
+
704
+ // List: row drag
705
+ const listRowEl = target.closest('[data-blok-database-list-row]');
706
+
707
+ if (listRowEl !== null) {
708
+ const rowId = listRowEl.getAttribute('data-row-id');
709
+
710
+ if (rowId !== null) {
711
+ e.preventDefault();
712
+ e.stopPropagation();
713
+ this.listRowDrag?.beginTracking(rowId, e.clientX, e.clientY);
714
+ }
715
+ }
716
+ });
717
+ }
718
+
719
+ private makeColumnHeadersEditable(boardEl: HTMLDivElement): void {
720
+ if (this.columnControls === null) {
721
+ return;
722
+ }
723
+
724
+ const headers = Array.from(boardEl.querySelectorAll<HTMLElement>('[data-blok-database-column-header]'));
725
+
726
+ for (const header of headers) {
727
+ const columnEl = header.closest<HTMLElement>('[data-blok-database-column]');
728
+ const optId = columnEl?.getAttribute('data-option-id');
729
+
730
+ if (optId !== null && optId !== undefined) {
731
+ this.columnControls.makeEditable(header, optId);
732
+ }
733
+ }
734
+ }
735
+
736
+ private handleListRowDrop(result: ListRowDragResult): void {
737
+ const { rowId, beforeRowId, afterRowId } = result;
738
+
739
+ const beforeRow = beforeRowId !== null ? this.model.getRow(beforeRowId) : undefined;
740
+ const afterRow = afterRowId !== null ? this.model.getRow(afterRowId) : undefined;
741
+ const position = DatabaseModel.positionBetween(afterRow?.position ?? null, beforeRow?.position ?? null);
742
+
743
+ this.model.moveRow(rowId, position);
744
+ this.rerenderView();
745
+
746
+ void this.sync.syncMoveRow({ rowId, position });
747
+ }
748
+
749
+ private handleRowDrop(result: CardDragResult): void {
750
+ const { rowId, toOptionId, beforeRowId, afterRowId } = result;
751
+ const viewConfig = this.model.getView(this.activeViewId);
752
+ const groupByPropId = viewConfig?.groupBy;
753
+
754
+ if (groupByPropId === undefined) {
755
+ return;
756
+ }
757
+
758
+ const beforeRow = beforeRowId !== null ? this.model.getRow(beforeRowId) : undefined;
759
+ const afterRow = afterRowId !== null ? this.model.getRow(afterRowId) : undefined;
760
+ const position = DatabaseModel.positionBetween(afterRow?.position ?? null, beforeRow?.position ?? null);
761
+
762
+ this.model.updateRow(rowId, { [groupByPropId]: toOptionId });
763
+ this.model.moveRow(rowId, position);
764
+ this.rerenderView();
765
+
766
+ this.sync.syncUpdateRow({ rowId, properties: { [groupByPropId]: toOptionId } });
767
+ void this.sync.syncMoveRow({ rowId, position });
768
+ }
769
+
770
+ private handleGroupDrop(result: GroupDragResult): void {
771
+ const { optionId, beforeOptionId, afterOptionId } = result;
772
+ const viewConfig = this.model.getView(this.activeViewId);
773
+ const groupByPropId = viewConfig?.groupBy;
774
+
775
+ if (groupByPropId === undefined) {
776
+ return;
777
+ }
778
+
779
+ const prop = this.model.getProperty(groupByPropId);
780
+
781
+ if (prop?.config === undefined) {
782
+ return;
783
+ }
784
+
785
+ const options = [...prop.config.options];
786
+ const draggedIdx = options.findIndex((o) => o.id === optionId);
787
+
788
+ if (draggedIdx === -1) {
789
+ return;
790
+ }
791
+
792
+ const beforeOpt = beforeOptionId !== null ? options.find((o) => o.id === beforeOptionId) : undefined;
793
+ const afterOpt = afterOptionId !== null ? options.find((o) => o.id === afterOptionId) : undefined;
794
+ const newPosition = DatabaseModel.positionBetween(afterOpt?.position ?? null, beforeOpt?.position ?? null);
795
+
796
+ options[draggedIdx] = { ...options[draggedIdx], position: newPosition };
797
+ this.model.updateProperty(groupByPropId, { config: { options } });
798
+
799
+ this.moveColumnInDom(optionId, beforeOptionId);
800
+ void this.sync.syncUpdateProperty({ propertyId: groupByPropId, changes: { config: { options } } });
801
+ }
802
+
803
+ /**
804
+ * Moves a column element to a new position in the DOM without full re-render.
805
+ */
806
+ private moveColumnInDom(optionId: string, beforeOptionId: string | null): void {
807
+ const boardEl = this.boardContainer?.querySelector<HTMLElement>('[data-blok-database-board]');
808
+
809
+ if (boardEl === null || boardEl === undefined) {
810
+ return;
811
+ }
812
+
813
+ const columnEl = boardEl.querySelector<HTMLElement>(`[data-option-id="${optionId}"]`);
814
+
815
+ if (columnEl === null) {
816
+ return;
817
+ }
818
+
819
+ if (beforeOptionId !== null) {
820
+ const beforeEl = boardEl.querySelector(`[data-option-id="${beforeOptionId}"]`);
821
+
822
+ if (beforeEl !== null) {
823
+ boardEl.insertBefore(columnEl, beforeEl);
824
+ }
825
+ } else {
826
+ const addColumnBtn = boardEl.querySelector('[data-blok-database-add-column]');
827
+
828
+ if (addColumnBtn !== null) {
829
+ boardEl.insertBefore(columnEl, addColumnBtn);
830
+ } else {
831
+ boardEl.appendChild(columnEl);
832
+ }
833
+ }
834
+ }
835
+
836
+ private handleOptionRename(optionId: string, label: string): void {
837
+ const viewConfig = this.model.getView(this.activeViewId);
838
+ const groupByPropId = viewConfig?.groupBy;
839
+
840
+ if (groupByPropId === undefined) {
841
+ return;
842
+ }
843
+
844
+ const prop = this.model.getProperty(groupByPropId);
845
+
846
+ if (prop?.config === undefined) {
847
+ return;
848
+ }
849
+
850
+ const options = prop.config.options.map((o) => o.id === optionId ? { ...o, label } : o);
851
+ this.model.updateProperty(groupByPropId, { config: { options } });
852
+ void this.sync.syncUpdateProperty({ propertyId: groupByPropId, changes: { config: { options } } });
853
+ }
854
+
855
+ private handleOptionDelete(optionId: string, boardEl: HTMLDivElement): void {
856
+ const viewConfig = this.model.getView(this.activeViewId);
857
+ const groupByPropId = viewConfig?.groupBy;
858
+
859
+ if (groupByPropId === undefined) {
860
+ return;
861
+ }
862
+
863
+ const prop = this.model.getProperty(groupByPropId);
864
+
865
+ if (prop?.config === undefined) {
866
+ return;
867
+ }
868
+
869
+ if (prop.config.options.length <= 1) {
870
+ return;
871
+ }
872
+
873
+ // Delete rows in this group
874
+ const groups = this.model.getRowsGroupedBy(groupByPropId);
875
+ const rowsInGroup = groups.get(optionId) ?? [];
876
+
877
+ for (const row of rowsInGroup) {
878
+ this.model.deleteRow(row.id);
879
+ void this.sync.syncDeleteRow({ rowId: row.id });
880
+ }
881
+
882
+ // Remove the option
883
+ const filteredOptions = prop.config.options.filter((o) => o.id !== optionId);
884
+ this.model.updateProperty(groupByPropId, { config: { options: filteredOptions } });
885
+ this.view.removeGroup?.(boardEl, optionId);
886
+ void this.sync.syncUpdateProperty({ propertyId: groupByPropId, changes: { config: { options: filteredOptions } } });
887
+ }
888
+
889
+ private handleRowClick(rowId: string): void {
890
+ const row = this.model.getRow(rowId);
891
+
892
+ if (row === undefined) {
893
+ return;
894
+ }
895
+
896
+ this.cardDrawer?.open(row);
897
+ }
898
+
899
+ /**
900
+ * Full re-render of the active board: tears down subsystems, rebuilds DOM, re-inits.
901
+ *
902
+ * Board DOM structure:
903
+ * boardContainer
904
+ * boardWrapper (div returned by createBoard / renderActiveBoard)
905
+ * boardArea ([data-blok-database-board] — scrollable area with columns)
906
+ */
907
+ private rerenderView(): void {
908
+ if (this.boardContainer === null) {
909
+ return;
910
+ }
911
+
912
+ // The old board wrapper is the first/only direct child of boardContainer
913
+ const oldBoardWrapper = this.boardContainer.querySelector<HTMLElement>('[data-blok-database-board]')
914
+ ?.closest<HTMLElement>('[data-blok-tool]')
915
+ ?? this.boardContainer.querySelector<HTMLElement>('[data-blok-database-list]')
916
+ ?? this.boardContainer.firstElementChild as HTMLElement | null;
917
+
918
+ const oldBoardArea = this.boardContainer.querySelector<HTMLElement>('[data-blok-database-board]');
919
+ const savedScrollLeft = oldBoardArea?.scrollLeft ?? 0;
920
+
921
+ this.cardDrag?.destroy();
922
+ this.columnDrag?.destroy();
923
+ this.columnControls?.destroy();
924
+ this.listRowDrag?.destroy();
925
+ this.listRowDrag = null;
926
+ this.cardDrawer?.destroy();
927
+ this.cardDrawer = null;
928
+ this.keyboard?.destroy();
929
+
930
+ const newBoardWrapper = this.renderActiveView();
931
+
932
+ if (oldBoardWrapper !== null && oldBoardWrapper !== undefined) {
933
+ oldBoardWrapper.replaceWith(newBoardWrapper);
934
+ } else {
935
+ this.boardContainer.appendChild(newBoardWrapper);
936
+ }
937
+
938
+ // Restore horizontal scroll on the new board area
939
+ const newBoardArea = newBoardWrapper.querySelector<HTMLElement>('[data-blok-database-board]');
940
+
941
+ if (newBoardArea !== null) {
942
+ newBoardArea.scrollLeft = savedScrollLeft;
943
+ }
944
+
945
+ this.attachViewListeners(newBoardWrapper);
946
+ this.initSubsystems(newBoardWrapper);
947
+ }
948
+ }