@jackuait/blok 0.10.3 → 0.10.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 (286) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-3wc3aInM.mjs → blok-DSP5zC_s.mjs} +2374 -2159
  3. package/dist/chunks/{constants-Bp622jic.mjs → constants-B0Tns0mh.mjs} +318 -227
  4. package/dist/chunks/{i18next-loader-CDnSPae_.mjs → i18next-loader-D_v0EmDP.mjs} +1 -1
  5. package/dist/chunks/{lightweight-i18n-DZmo8dAI.mjs → lightweight-i18n-DTYoSr_o.mjs} +21 -0
  6. package/dist/{messages-bkGniiaz.mjs → chunks/messages--X-ZzYSb.mjs} +24 -3
  7. package/dist/chunks/{messages-DrfRYiM32.mjs → messages--tSqMtkx2.mjs} +24 -3
  8. package/dist/chunks/{messages-CW35K1pq.mjs → messages-3w1e16T2.mjs} +25 -4
  9. package/dist/chunks/{messages-BHMiK51R.mjs → messages-B5BaA6AV.mjs} +24 -3
  10. package/dist/{messages-DKHbt-7l2.mjs → chunks/messages-B5ZVSxG4.mjs} +25 -4
  11. package/dist/chunks/{messages-kGmxkeFH.mjs → messages-B9PUHq4t.mjs} +24 -3
  12. package/dist/chunks/{messages-BlxwW7M6.mjs → messages-BAxCMQgm.mjs} +24 -3
  13. package/dist/chunks/{messages-BRAoJpOu.mjs → messages-BDhWRN6Y.mjs} +25 -4
  14. package/dist/{messages-CdEASHDp2.mjs → chunks/messages-BGpx2ecP2.mjs} +24 -3
  15. package/dist/{messages-C6ONf71u2.mjs → chunks/messages-BHUQs6402.mjs} +24 -3
  16. package/dist/{messages-BrOWqNCu2.mjs → chunks/messages-BIXCvkbG.mjs} +24 -3
  17. package/dist/chunks/{messages-DMoERagV2.mjs → messages-BKeL1zk-2.mjs} +24 -3
  18. package/dist/chunks/{messages-DjvaiALg2.mjs → messages-Bedied602.mjs} +24 -3
  19. package/dist/chunks/{messages-oMc7qugU2.mjs → messages-Bg8uBETk2.mjs} +24 -3
  20. package/dist/{messages-CRNogopy2.mjs → chunks/messages-Blg1X6YL.mjs} +24 -3
  21. package/dist/chunks/{messages-euM2m3wQ.mjs → messages-BmDMK9Dg.mjs} +24 -3
  22. package/dist/{messages-D3rwCtKn.mjs → chunks/messages-BmDw1fZF2.mjs} +24 -3
  23. package/dist/{messages-qfvXgPpu2.mjs → chunks/messages-BqKIunt0.mjs} +24 -3
  24. package/dist/chunks/{messages-BfAcUavP.mjs → messages-BszGK0ox.mjs} +26 -5
  25. package/dist/{messages-BckDk9aq2.mjs → chunks/messages-Buwm8xrX2.mjs} +24 -3
  26. package/dist/chunks/{messages-CC_noR8y.mjs → messages-BvoElEE_.mjs} +24 -3
  27. package/dist/chunks/{messages-zt6zdYWh.mjs → messages-ByHXyVi6.mjs} +23 -2
  28. package/dist/chunks/{messages-DBMaLL8b2.mjs → messages-C9HPruJj2.mjs} +24 -3
  29. package/dist/{messages-DQORja0D.mjs → chunks/messages-CDtmVcBc.mjs} +26 -5
  30. package/dist/chunks/{messages-wl8YrvGG.mjs → messages-CEWKcK-Q.mjs} +23 -2
  31. package/dist/chunks/{messages-CZSlfnkO2.mjs → messages-CRLvJfNU2.mjs} +22 -1
  32. package/dist/chunks/{messages-BRoa9tGl.mjs → messages-CXjSY5Fy.mjs} +24 -3
  33. package/dist/{messages-BesJaI6A.mjs → chunks/messages-CdWS0hvw.mjs} +29 -8
  34. package/dist/{messages-JNrYldAa2.mjs → chunks/messages-Cic0Y1z6.mjs} +24 -3
  35. package/dist/chunks/{messages-CTCe595D2.mjs → messages-CmGixnBb2.mjs} +24 -3
  36. package/dist/chunks/{messages-JSQjKQ8I.mjs → messages-CrCalcxK.mjs} +24 -3
  37. package/dist/chunks/{messages-DUr9WAkD.mjs → messages-D3wlt9eE.mjs} +22 -1
  38. package/dist/{messages-CdduYw-q.mjs → chunks/messages-D5NWb-B9.mjs} +23 -2
  39. package/dist/chunks/{messages-CRF7nNrO.mjs → messages-D5lY22F4.mjs} +24 -3
  40. package/dist/{messages-_PLyRfVw.mjs → chunks/messages-DGYjioKM.mjs} +26 -5
  41. package/dist/{messages-Che99vKP.mjs → chunks/messages-DIH2U0ZD.mjs} +24 -3
  42. package/dist/chunks/{messages-BJ-vT1SU2.mjs → messages-DIUegdwv2.mjs} +24 -3
  43. package/dist/chunks/{messages-BgM91Lxm2.mjs → messages-DKZHsfXy2.mjs} +25 -4
  44. package/dist/chunks/{messages-1_6UkKLS.mjs → messages-DKjUcEfs.mjs} +24 -3
  45. package/dist/{messages-C1vc5584.mjs → chunks/messages-DOPK-Xif2.mjs} +24 -3
  46. package/dist/chunks/{messages-DB_-5Xln.mjs → messages-DSMFg80t.mjs} +25 -4
  47. package/dist/{messages-A96tMxeU.mjs → chunks/messages-DT6eM0I22.mjs} +22 -1
  48. package/dist/chunks/{messages-DUeiPraX.mjs → messages-DZTQIS9F.mjs} +24 -3
  49. package/dist/{messages-apA6BStA.mjs → chunks/messages-DalLfJNC.mjs} +24 -3
  50. package/dist/chunks/{messages-QilfinOn2.mjs → messages-DggaAu362.mjs} +24 -3
  51. package/dist/chunks/{messages-CxxyR4vY.mjs → messages-DgoJDjHm.mjs} +24 -3
  52. package/dist/chunks/{messages-ElIGUi0O2.mjs → messages-DhgEjOjw2.mjs} +25 -4
  53. package/dist/chunks/{messages-sDdNf8O9.mjs → messages-Dk-d3mmM.mjs} +22 -1
  54. package/dist/{messages-BE_z-zrb.mjs → chunks/messages-DlTG06-j.mjs} +25 -4
  55. package/dist/chunks/{messages-C0IFfhnp.mjs → messages-Dle6Hv30.mjs} +22 -1
  56. package/dist/{messages-DBhvm8NK.mjs → chunks/messages-DoD9yWMu2.mjs} +22 -1
  57. package/dist/{messages-D0lLw9KM.mjs → chunks/messages-DpnhbDoK2.mjs} +24 -3
  58. package/dist/{messages-DODrhcop.mjs → chunks/messages-DqGv-lj6.mjs} +24 -3
  59. package/dist/{messages-CZbcxlZt2.mjs → chunks/messages-DslnYTHU2.mjs} +24 -3
  60. package/dist/chunks/{messages-Du2BffA7.mjs → messages-IjEU7XAq.mjs} +24 -3
  61. package/dist/chunks/{messages-BK8Cp2d0.mjs → messages-JRjsu_bC.mjs} +24 -3
  62. package/dist/chunks/{messages-CszmHAvQ.mjs → messages-PkO_43Eb.mjs} +24 -3
  63. package/dist/chunks/{messages-CQBo3lmL2.mjs → messages-W2zpf0Mu2.mjs} +24 -3
  64. package/dist/{messages-Do7Xjy0n.mjs → chunks/messages-adEATsLK.mjs} +23 -2
  65. package/dist/{messages-E8NjqzWq2.mjs → chunks/messages-b8XGiuAa2.mjs} +23 -2
  66. package/dist/{messages-CisR4PNV.mjs → chunks/messages-bteFpBer.mjs} +24 -3
  67. package/dist/chunks/{messages-DVr1sqfI2.mjs → messages-cR48x2Aq2.mjs} +24 -3
  68. package/dist/chunks/{messages-4Ck88DYZ2.mjs → messages-f-YBthdd2.mjs} +24 -3
  69. package/dist/{messages-Xc0KUbYl.mjs → chunks/messages-fUpUvsRN.mjs} +25 -4
  70. package/dist/{messages-Be_2RHZD.mjs → chunks/messages-gNK1EolD.mjs} +25 -4
  71. package/dist/{messages-DfFZ6Yj5.mjs → chunks/messages-vqR3RAU4.mjs} +24 -3
  72. package/dist/{messages-BbJ7ZXY8.mjs → chunks/messages-wSX_4fK1.mjs} +24 -3
  73. package/dist/chunks/{tools-BC1jRfoS.mjs → tools-KZmhOKEo.mjs} +3407 -1299
  74. package/dist/full.mjs +10 -10
  75. package/dist/locales.mjs +88 -67
  76. package/dist/{chunks/messages-D22e9h7V2.mjs → messages--zgIPYtb.mjs} +22 -1
  77. package/dist/{chunks/messages-EDMC5ukV.mjs → messages-4jULpVvw.mjs} +24 -3
  78. package/dist/{messages-DK6dA0O2.mjs → messages-AbwSlRf_.mjs} +26 -5
  79. package/dist/{messages-Ddq3Ce3E2.mjs → messages-B-dl7Pwb2.mjs} +25 -4
  80. package/dist/{chunks/messages-BAlZjPcl.mjs → messages-B3rCRhb1.mjs} +24 -3
  81. package/dist/{chunks/messages-DSrdy9Nw2.mjs → messages-B73L8nKc2.mjs} +24 -3
  82. package/dist/{messages-ClGvlFcH2.mjs → messages-BAjsD2Vm2.mjs} +24 -3
  83. package/dist/{chunks/messages-BbEW9bQz.mjs → messages-BBodSb5B2.mjs} +24 -3
  84. package/dist/{messages-DopaMHC42.mjs → messages-BTpn6H9x2.mjs} +22 -1
  85. package/dist/{messages-D0005ti32.mjs → messages-BWb0GjTj2.mjs} +24 -3
  86. package/dist/{messages-Q5sQeVap2.mjs → messages-BbCgnri12.mjs} +24 -3
  87. package/dist/{chunks/messages-BeGZqQwz.mjs → messages-Bfon_Y3q.mjs} +25 -4
  88. package/dist/{messages-CTTmWn4Y2.mjs → messages-BhOtow9J2.mjs} +24 -3
  89. package/dist/{messages-98nQiC7t2.mjs → messages-BoB7m0uu2.mjs} +25 -4
  90. package/dist/{messages-CT-Kdas6.mjs → messages-BsOZPjeg.mjs} +24 -3
  91. package/dist/{messages-D6VIFnSW.mjs → messages-BuzKUYPV.mjs} +24 -3
  92. package/dist/{chunks/messages-DEBy3nuJ2.mjs → messages-BzOKMdMQ.mjs} +22 -1
  93. package/dist/{messages-DOGbHYv-2.mjs → messages-C2H3PeQW2.mjs} +24 -3
  94. package/dist/{chunks/messages-DxHh0O8j2.mjs → messages-C5KNzSTk.mjs} +24 -3
  95. package/dist/{chunks/messages-eFd4YYzt.mjs → messages-CIDYjE6l.mjs} +24 -3
  96. package/dist/{messages-Dqu4aX9s.mjs → messages-CJmyF5ye.mjs} +23 -2
  97. package/dist/{messages-qbKjjvgd2.mjs → messages-CTwiYb2a2.mjs} +22 -1
  98. package/dist/{chunks/messages-CPBN4zWc.mjs → messages-CUHoJaDO.mjs} +24 -3
  99. package/dist/{messages-D05jqBIa2.mjs → messages-CVeuqUjQ2.mjs} +24 -3
  100. package/dist/{chunks/messages-BMD37y3q2.mjs → messages-CW75b4sz.mjs} +24 -3
  101. package/dist/{chunks/messages-CD_MnBln.mjs → messages-CXd7YeWV.mjs} +24 -3
  102. package/dist/{messages-DVL0KZE5.mjs → messages-CjlYkunD.mjs} +24 -3
  103. package/dist/{chunks/messages-BKN3YVIj.mjs → messages-CrGx64yd.mjs} +25 -4
  104. package/dist/{messages-BK_LsgY4.mjs → messages-Ct0jbgHd.mjs} +24 -3
  105. package/dist/{messages-BmH2cQHQ.mjs → messages-CtKrCYD1.mjs} +24 -3
  106. package/dist/{messages-neGD3WGq.mjs → messages-Cu2eLjfJ.mjs} +24 -3
  107. package/dist/{messages-Brd5R-da2.mjs → messages-CvwQ-fK_2.mjs} +24 -3
  108. package/dist/{messages-uwK7ktqk.mjs → messages-Cy6UYN9b.mjs} +22 -1
  109. package/dist/{chunks/messages-ChK7v1PV.mjs → messages-D01vnfA9.mjs} +24 -3
  110. package/dist/{messages-DpwMKDV0.mjs → messages-D0BAV45W.mjs} +24 -3
  111. package/dist/{messages-LMaR2_bE.mjs → messages-D5LGpdga.mjs} +24 -3
  112. package/dist/{messages-BcVB3osF.mjs → messages-DATK4ftv.mjs} +25 -4
  113. package/dist/{chunks/messages-Bz0-KNEB.mjs → messages-DEl77Urv2.mjs} +25 -4
  114. package/dist/{messages-DSmxJWju2.mjs → messages-DHNjM2nv2.mjs} +22 -1
  115. package/dist/{messages-DYuD5-rO.mjs → messages-DMW8b_sO.mjs} +24 -3
  116. package/dist/{chunks/messages-DPzHD51Y.mjs → messages-DNmgANKr.mjs} +23 -2
  117. package/dist/{chunks/messages-C1S9ztpF.mjs → messages-DQaa3cEM.mjs} +25 -4
  118. package/dist/{chunks/messages-a07QVz8U.mjs → messages-DSlD4bV9.mjs} +24 -3
  119. package/dist/{messages-DM4Gjc9h.mjs → messages-DTzbkXFt.mjs} +24 -3
  120. package/dist/{messages-LYJbLq_F.mjs → messages-DbZihxtV.mjs} +24 -3
  121. package/dist/{messages-C_Qn9SbQ.mjs → messages-DeE8fyKQ.mjs} +24 -3
  122. package/dist/{chunks/messages-Q7-4ZJLB2.mjs → messages-Dg-TpO2S2.mjs} +24 -3
  123. package/dist/{messages-BiTMwiKH.mjs → messages-DgP82LzF.mjs} +23 -2
  124. package/dist/{chunks/messages-QMOmwcZb.mjs → messages-Dh3Vt7WC.mjs} +23 -2
  125. package/dist/{messages-C0GSBBCo2.mjs → messages-DiVVyZD22.mjs} +25 -4
  126. package/dist/{chunks/messages-BONyZroH.mjs → messages-DpRnGjoe.mjs} +26 -5
  127. package/dist/{chunks/messages-8Ld7P_9j2.mjs → messages-DqZmrFaa2.mjs} +24 -3
  128. package/dist/{chunks/messages-CxiURE2X.mjs → messages-Dra9Fz5Q.mjs} +26 -5
  129. package/dist/{chunks/messages-Clku7Cf-2.mjs → messages-DuPBRgEF2.mjs} +23 -2
  130. package/dist/{messages-53w0fPZS2.mjs → messages-DuxEyAY-2.mjs} +25 -4
  131. package/dist/{chunks/messages-D7dx_6k8.mjs → messages-FmJD6lHR2.mjs} +24 -3
  132. package/dist/{chunks/messages-DTN1XGll.mjs → messages-ONielGl92.mjs} +24 -3
  133. package/dist/{chunks/messages-CIfUm1Oa.mjs → messages-RzgUqCGb2.mjs} +24 -3
  134. package/dist/{messages-DpJGbx3q.mjs → messages-U22AKGKs.mjs} +24 -3
  135. package/dist/{messages-2ZWBTerL.mjs → messages-XufeeRfO.mjs} +24 -3
  136. package/dist/{messages-CnuH-BZK2.mjs → messages-fprUm7SO2.mjs} +24 -3
  137. package/dist/{messages-C7lJg8fy2.mjs → messages-gFNdz3Tn2.mjs} +24 -3
  138. package/dist/{chunks/messages-C15z2r5U.mjs → messages-h_n5GBm6.mjs} +29 -8
  139. package/dist/{chunks/messages-CvANwuht2.mjs → messages-oOogIpsL2.mjs} +24 -3
  140. package/dist/{chunks/messages-DtoId_bw2.mjs → messages-otwg3KcK.mjs} +24 -3
  141. package/dist/{messages-D81w6AmW.mjs → messages-rDDoBMWb.mjs} +24 -3
  142. package/dist/{messages-Dnd5YSWv.mjs → messages-trQPxZIu.mjs} +24 -3
  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 +103 -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 +24 -3
  151. package/src/components/i18n/locales/ar/messages.json +24 -3
  152. package/src/components/i18n/locales/az/messages.json +24 -3
  153. package/src/components/i18n/locales/bg/messages.json +23 -2
  154. package/src/components/i18n/locales/bn/messages.json +24 -3
  155. package/src/components/i18n/locales/bs/messages.json +26 -5
  156. package/src/components/i18n/locales/cs/messages.json +25 -4
  157. package/src/components/i18n/locales/da/messages.json +25 -4
  158. package/src/components/i18n/locales/de/messages.json +24 -3
  159. package/src/components/i18n/locales/dv/messages.json +24 -3
  160. package/src/components/i18n/locales/el/messages.json +24 -3
  161. package/src/components/i18n/locales/en/messages.json +21 -0
  162. package/src/components/i18n/locales/es/messages.json +25 -4
  163. package/src/components/i18n/locales/et/messages.json +23 -2
  164. package/src/components/i18n/locales/fa/messages.json +24 -3
  165. package/src/components/i18n/locales/fi/messages.json +24 -3
  166. package/src/components/i18n/locales/fil/messages.json +29 -8
  167. package/src/components/i18n/locales/fr/messages.json +24 -3
  168. package/src/components/i18n/locales/gu/messages.json +24 -3
  169. package/src/components/i18n/locales/he/messages.json +24 -3
  170. package/src/components/i18n/locales/hi/messages.json +24 -3
  171. package/src/components/i18n/locales/hr/messages.json +26 -5
  172. package/src/components/i18n/locales/hu/messages.json +25 -4
  173. package/src/components/i18n/locales/hy/messages.json +24 -3
  174. package/src/components/i18n/locales/id/messages.json +23 -2
  175. package/src/components/i18n/locales/it/messages.json +26 -5
  176. package/src/components/i18n/locales/ja/messages.json +24 -3
  177. package/src/components/i18n/locales/ka/messages.json +24 -3
  178. package/src/components/i18n/locales/km/messages.json +22 -1
  179. package/src/components/i18n/locales/kn/messages.json +24 -3
  180. package/src/components/i18n/locales/ko/messages.json +24 -3
  181. package/src/components/i18n/locales/ku/messages.json +24 -3
  182. package/src/components/i18n/locales/lo/messages.json +22 -1
  183. package/src/components/i18n/locales/lt/messages.json +24 -3
  184. package/src/components/i18n/locales/lv/messages.json +24 -3
  185. package/src/components/i18n/locales/mk/messages.json +24 -3
  186. package/src/components/i18n/locales/ml/messages.json +24 -3
  187. package/src/components/i18n/locales/mn/messages.json +24 -3
  188. package/src/components/i18n/locales/mr/messages.json +24 -3
  189. package/src/components/i18n/locales/ms/messages.json +22 -1
  190. package/src/components/i18n/locales/my/messages.json +24 -3
  191. package/src/components/i18n/locales/ne/messages.json +24 -3
  192. package/src/components/i18n/locales/nl/messages.json +24 -3
  193. package/src/components/i18n/locales/no/messages.json +25 -4
  194. package/src/components/i18n/locales/pa/messages.json +24 -3
  195. package/src/components/i18n/locales/pl/messages.json +25 -4
  196. package/src/components/i18n/locales/ps/messages.json +24 -3
  197. package/src/components/i18n/locales/pt/messages.json +24 -3
  198. package/src/components/i18n/locales/ro/messages.json +24 -3
  199. package/src/components/i18n/locales/ru/messages.json +23 -2
  200. package/src/components/i18n/locales/sd/messages.json +24 -3
  201. package/src/components/i18n/locales/si/messages.json +24 -3
  202. package/src/components/i18n/locales/sk/messages.json +25 -4
  203. package/src/components/i18n/locales/sl/messages.json +25 -4
  204. package/src/components/i18n/locales/sq/messages.json +25 -4
  205. package/src/components/i18n/locales/sr/messages.json +24 -3
  206. package/src/components/i18n/locales/sv/messages.json +24 -3
  207. package/src/components/i18n/locales/sw/messages.json +22 -1
  208. package/src/components/i18n/locales/ta/messages.json +24 -3
  209. package/src/components/i18n/locales/te/messages.json +24 -3
  210. package/src/components/i18n/locales/th/messages.json +22 -1
  211. package/src/components/i18n/locales/tr/messages.json +24 -3
  212. package/src/components/i18n/locales/ug/messages.json +24 -3
  213. package/src/components/i18n/locales/uk/messages.json +23 -2
  214. package/src/components/i18n/locales/ur/messages.json +24 -3
  215. package/src/components/i18n/locales/vi/messages.json +22 -1
  216. package/src/components/i18n/locales/yi/messages.json +24 -3
  217. package/src/components/i18n/locales/zh/messages.json +24 -3
  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 +35 -1
  221. package/src/components/inline-tools/inline-tool-italic.ts +37 -1
  222. package/src/components/inline-tools/inline-tool-strikethrough.ts +35 -1
  223. package/src/components/inline-tools/inline-tool-underline.ts +35 -1
  224. package/src/components/inline-tools/services/bold-normalization-pass.ts +2 -4
  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 +7 -1
  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/ui.ts +1 -0
  244. package/src/components/modules/uiControllers/controllers/keyboard.ts +79 -0
  245. package/src/components/modules/uiControllers/controllers/selection.ts +14 -2
  246. package/src/components/modules/yjs/document-store.ts +22 -0
  247. package/src/components/modules/yjs/index.ts +10 -0
  248. package/src/components/modules/yjs/serializer.ts +20 -0
  249. package/src/components/ui/toolbox.ts +0 -1
  250. package/src/components/utils/id-generator.ts +11 -0
  251. package/src/components/utils/key-icon.ts +187 -0
  252. package/src/components/utils/popover/components/hint/hint.ts +3 -1
  253. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +18 -5
  254. package/src/components/utils/popover/popover-abstract.ts +45 -0
  255. package/src/components/utils/popover/popover-desktop.ts +1 -0
  256. package/src/components/utils/popover/popover-position.ts +4 -2
  257. package/src/components/utils/popover/popover.const.ts +2 -0
  258. package/src/components/utils.ts +1 -0
  259. package/src/styles/main.css +1272 -0
  260. package/src/tools/code/index.ts +4 -0
  261. package/src/tools/database/database-backend-sync.ts +132 -0
  262. package/src/tools/database/database-board-view.ts +410 -0
  263. package/src/tools/database/database-card-drag.ts +306 -0
  264. package/src/tools/database/database-card-drawer.ts +546 -0
  265. package/src/tools/database/database-column-controls.ts +141 -0
  266. package/src/tools/database/database-column-drag.ts +262 -0
  267. package/src/tools/database/database-keyboard.ts +35 -0
  268. package/src/tools/database/database-list-row-drag.ts +245 -0
  269. package/src/tools/database/database-list-view.ts +333 -0
  270. package/src/tools/database/database-model.ts +214 -0
  271. package/src/tools/database/database-property-type-popover.ts +108 -0
  272. package/src/tools/database/database-tab-bar.ts +558 -0
  273. package/src/tools/database/database-view-popover.ts +129 -0
  274. package/src/tools/database/database-view-renderer.ts +25 -0
  275. package/src/tools/database/database-view.ts +269 -0
  276. package/src/tools/database/index.ts +1223 -0
  277. package/src/tools/database/types.ts +168 -0
  278. package/src/tools/database-row/index.ts +74 -0
  279. package/src/tools/index.ts +4 -0
  280. package/types/api/tools.d.ts +18 -0
  281. package/types/configs/blok-config.d.ts +27 -0
  282. package/types/data-formats/output-data.d.ts +13 -0
  283. package/types/index.d.ts +17 -0
  284. package/types/tools/database.d.ts +152 -0
  285. package/types/tools-entry.d.ts +7 -4
  286. package/types/utils/popover/popover.d.ts +6 -0
@@ -0,0 +1,333 @@
1
+ import type { I18n } from '../../../types';
2
+ import type { DatabaseRow, PropertyDefinition, PropertyValue, SelectOption } from './types';
3
+ import type { DatabaseViewRenderer } from './database-view-renderer';
4
+
5
+ interface DatabaseListViewOptions {
6
+ readOnly: boolean;
7
+ i18n: I18n;
8
+ rows: DatabaseRow[];
9
+ titlePropertyId: string;
10
+ schema: PropertyDefinition[];
11
+ visiblePropertyIds: string[];
12
+ options?: SelectOption[];
13
+ getRows?: (optionId: string) => DatabaseRow[];
14
+ }
15
+
16
+ /**
17
+ * DOM rendering layer for the flat list view.
18
+ * Renders rows as a flat list with optional property badges.
19
+ * All interactive elements use data-blok-database-* attributes for test selectors and event delegation.
20
+ */
21
+ export class DatabaseListView implements DatabaseViewRenderer {
22
+ private readonly readOnly: boolean;
23
+ private readonly i18n: I18n;
24
+ private readonly rows: DatabaseRow[];
25
+ private readonly titlePropertyId: string;
26
+ private readonly schema: PropertyDefinition[];
27
+ private readonly visiblePropertyIds: string[];
28
+ private readonly groupOptions: SelectOption[] | undefined;
29
+ private readonly getGroupRows: ((optionId: string) => DatabaseRow[]) | undefined;
30
+
31
+ constructor({ readOnly, i18n, rows, titlePropertyId, schema, visiblePropertyIds, options, getRows }: DatabaseListViewOptions) {
32
+ this.readOnly = readOnly;
33
+ this.i18n = i18n;
34
+ this.rows = rows;
35
+ this.titlePropertyId = titlePropertyId;
36
+ this.schema = schema;
37
+ this.visiblePropertyIds = visiblePropertyIds;
38
+ this.groupOptions = options;
39
+ this.getGroupRows = getRows;
40
+ }
41
+
42
+ /**
43
+ * Creates the full list DOM from row data.
44
+ * When options are provided, renders grouped sections; otherwise renders a flat list.
45
+ */
46
+ createView(): HTMLDivElement {
47
+ const wrapper = document.createElement('div');
48
+
49
+ wrapper.setAttribute('data-blok-database-list', '');
50
+
51
+ if (this.groupOptions !== undefined && this.groupOptions.length > 0 && this.getGroupRows !== undefined) {
52
+ for (const option of this.groupOptions) {
53
+ const rows = this.getGroupRows(option.id);
54
+ const groupEl = this.createGroupElement(option, rows);
55
+
56
+ wrapper.appendChild(groupEl);
57
+ }
58
+ } else {
59
+ wrapper.setAttribute('role', 'list');
60
+ wrapper.setAttribute('aria-label', 'List view');
61
+
62
+ for (const row of this.rows) {
63
+ const rowEl = this.createRowElement(row);
64
+
65
+ wrapper.appendChild(rowEl);
66
+ }
67
+
68
+ if (!this.readOnly) {
69
+ const addRowBtn = this.createAddRowButton();
70
+
71
+ wrapper.appendChild(addRowBtn);
72
+ }
73
+ }
74
+
75
+ return wrapper;
76
+ }
77
+
78
+ /**
79
+ * Creates a collapsible group element for a select option.
80
+ */
81
+ private createGroupElement(option: SelectOption, rows: DatabaseRow[]): HTMLDivElement {
82
+ const groupEl = document.createElement('div');
83
+
84
+ groupEl.setAttribute('data-blok-database-list-group', '');
85
+ groupEl.setAttribute('data-option-id', option.id);
86
+
87
+ // Header
88
+ const header = document.createElement('div');
89
+
90
+ header.setAttribute('data-blok-database-list-group-header', '');
91
+
92
+ const toggle = document.createElement('span');
93
+
94
+ toggle.setAttribute('data-blok-database-list-group-toggle', '');
95
+ toggle.textContent = '▼';
96
+
97
+ const dot = document.createElement('span');
98
+
99
+ dot.setAttribute('data-blok-database-list-group-dot', '');
100
+
101
+ if (option.color !== undefined) {
102
+ dot.style.backgroundColor = `var(--blok-color-${option.color}-text)`;
103
+ }
104
+
105
+ const title = document.createElement('span');
106
+
107
+ title.setAttribute('data-blok-database-list-group-title', '');
108
+ title.textContent = option.label;
109
+
110
+ const count = document.createElement('span');
111
+
112
+ count.setAttribute('data-blok-database-list-group-count', '');
113
+ count.textContent = String(rows.length);
114
+
115
+ header.appendChild(toggle);
116
+ header.appendChild(dot);
117
+ header.appendChild(title);
118
+ header.appendChild(count);
119
+
120
+ // Rows container
121
+ const rowsContainer = document.createElement('div');
122
+
123
+ rowsContainer.setAttribute('data-blok-database-list-rows', '');
124
+ rowsContainer.setAttribute('role', 'list');
125
+
126
+ for (const row of rows) {
127
+ const rowEl = this.createRowElement(row);
128
+
129
+ rowsContainer.appendChild(rowEl);
130
+ }
131
+
132
+ // Add-row button
133
+ const addRowBtn = this.readOnly ? null : this.createAddRowButton(option.id);
134
+
135
+ // Collapse/expand toggle
136
+ header.addEventListener('click', () => {
137
+ const collapsed = rowsContainer.style.display === 'none';
138
+
139
+ rowsContainer.style.display = collapsed ? '' : 'none';
140
+ toggle.textContent = collapsed ? '▼' : '▶';
141
+
142
+ if (addRowBtn !== null) {
143
+ addRowBtn.style.display = collapsed ? '' : 'none';
144
+ }
145
+ });
146
+
147
+ groupEl.appendChild(header);
148
+ groupEl.appendChild(rowsContainer);
149
+
150
+ if (addRowBtn !== null) {
151
+ groupEl.appendChild(addRowBtn);
152
+ }
153
+
154
+ return groupEl;
155
+ }
156
+
157
+ /**
158
+ * Creates a row element and inserts it before the add-row button (if present), else appends.
159
+ */
160
+ appendRow(container: HTMLElement, row: DatabaseRow): void {
161
+ const rowEl = this.createRowElement(row);
162
+ const addRowBtn = container.querySelector('[data-blok-database-add-row]');
163
+
164
+ if (addRowBtn !== null) {
165
+ container.insertBefore(rowEl, addRowBtn);
166
+ } else {
167
+ container.appendChild(rowEl);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Removes a row element from the wrapper by its data-row-id.
173
+ */
174
+ removeRow(wrapper: HTMLElement, rowId: string): void {
175
+ const rowEl = wrapper.querySelector(`[data-row-id="${rowId}"]`);
176
+
177
+ rowEl?.remove();
178
+ }
179
+
180
+ /**
181
+ * Updates the visible title of a row element found by its data-row-id.
182
+ */
183
+ updateRowTitle(wrapper: HTMLElement, rowId: string, title: string): void {
184
+ const rowEl = wrapper.querySelector(`[data-row-id="${rowId}"]`);
185
+ const titleEl = rowEl?.querySelector('[data-blok-database-list-row-title]');
186
+
187
+ if (titleEl === null || titleEl === undefined) {
188
+ return;
189
+ }
190
+
191
+ if (title) {
192
+ titleEl.textContent = title;
193
+ titleEl.removeAttribute('data-placeholder');
194
+ } else {
195
+ titleEl.textContent = this.i18n.t('tools.database.rowTitlePlaceholder');
196
+ titleEl.setAttribute('data-placeholder', '');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Creates a single row element.
202
+ */
203
+ private createRowElement(row: DatabaseRow): HTMLDivElement {
204
+ const rowEl = document.createElement('div');
205
+ const title = (row.properties[this.titlePropertyId] as string) ?? '';
206
+
207
+ rowEl.setAttribute('data-blok-database-list-row', '');
208
+ rowEl.setAttribute('data-row-id', row.id);
209
+ rowEl.setAttribute('role', 'listitem');
210
+
211
+ const titleEl = document.createElement('div');
212
+
213
+ titleEl.setAttribute('data-blok-database-list-row-title', '');
214
+
215
+ if (title) {
216
+ titleEl.textContent = title;
217
+ } else {
218
+ titleEl.textContent = this.i18n.t('tools.database.rowTitlePlaceholder');
219
+ titleEl.setAttribute('data-placeholder', '');
220
+ }
221
+
222
+ rowEl.appendChild(titleEl);
223
+
224
+ const propertiesEl = this.createPropertiesElement(row);
225
+
226
+ rowEl.appendChild(propertiesEl);
227
+
228
+ return rowEl;
229
+ }
230
+
231
+ /**
232
+ * Creates the properties container with badges, open button, and delete button.
233
+ */
234
+ private createPropertiesElement(row: DatabaseRow): HTMLDivElement {
235
+ const propertiesEl = document.createElement('div');
236
+
237
+ propertiesEl.setAttribute('data-blok-database-list-row-properties', '');
238
+
239
+ for (const propId of this.visiblePropertyIds) {
240
+ const propDef = this.schema.find(p => p.id === propId);
241
+
242
+ if (propDef === undefined) {
243
+ continue;
244
+ }
245
+
246
+ const value = row.properties[propId];
247
+ const badge = this.createPropertyBadge(propDef, value);
248
+
249
+ if (badge !== null) {
250
+ propertiesEl.appendChild(badge);
251
+ }
252
+ }
253
+
254
+ const openBtn = document.createElement('button');
255
+
256
+ openBtn.setAttribute('data-blok-database-list-row-open', '');
257
+ openBtn.setAttribute('aria-label', this.i18n.t('tools.database.openRow'));
258
+ propertiesEl.appendChild(openBtn);
259
+
260
+ if (!this.readOnly) {
261
+ const deleteBtn = document.createElement('button');
262
+
263
+ deleteBtn.setAttribute('data-blok-database-delete-row', '');
264
+ deleteBtn.setAttribute('data-row-id', row.id);
265
+ deleteBtn.setAttribute('aria-label', this.i18n.t('tools.database.deleteRow'));
266
+ deleteBtn.textContent = '\u00d7';
267
+ propertiesEl.appendChild(deleteBtn);
268
+ }
269
+
270
+ return propertiesEl;
271
+ }
272
+
273
+ /**
274
+ * Creates a property badge span for a given property definition and value.
275
+ * Returns null if the value is undefined/null or if the select option is not found.
276
+ */
277
+ private createPropertyBadge(propDef: PropertyDefinition, value: PropertyValue): HTMLSpanElement | null {
278
+ if (value === undefined || value === null) {
279
+ return null;
280
+ }
281
+
282
+ const badge = document.createElement('span');
283
+
284
+ badge.setAttribute('data-blok-database-list-row-property', '');
285
+ badge.setAttribute('data-property-id', propDef.id);
286
+
287
+ if (propDef.type === 'select') {
288
+ const options = propDef.config?.options ?? [];
289
+ const option = options.find(o => o.id === value);
290
+
291
+ if (option === undefined) {
292
+ return null;
293
+ }
294
+
295
+ badge.textContent = option.label;
296
+
297
+ if (option.color !== undefined) {
298
+ badge.style.backgroundColor = `var(--blok-color-${option.color}-bg)`;
299
+ badge.style.color = `var(--blok-color-${option.color}-text)`;
300
+ }
301
+ } else if (propDef.type === 'checkbox') {
302
+ const checkbox = document.createElement('input');
303
+
304
+ checkbox.type = 'checkbox';
305
+ checkbox.checked = value === true;
306
+ badge.appendChild(checkbox);
307
+ } else if (typeof value === 'string' || typeof value === 'number') {
308
+ badge.textContent = String(value);
309
+ } else {
310
+ return null;
311
+ }
312
+
313
+ return badge;
314
+ }
315
+
316
+ /**
317
+ * Creates the "+ New" add-row button.
318
+ * When optionId is provided, sets data-option-id on the button.
319
+ */
320
+ private createAddRowButton(optionId?: string): HTMLButtonElement {
321
+ const addRowBtn = document.createElement('button');
322
+
323
+ addRowBtn.setAttribute('data-blok-database-add-row', '');
324
+ addRowBtn.setAttribute('aria-label', this.i18n.t('tools.database.addRow'));
325
+ addRowBtn.textContent = '+ ' + this.i18n.t('tools.database.newRow');
326
+
327
+ if (optionId !== undefined) {
328
+ addRowBtn.setAttribute('data-option-id', optionId);
329
+ }
330
+
331
+ return addRowBtn;
332
+ }
333
+ }
@@ -0,0 +1,214 @@
1
+ import { generateKeyBetween } from 'fractional-indexing';
2
+ import { nanoid } from 'nanoid';
3
+ import type {
4
+ DatabaseData,
5
+ DatabaseRow,
6
+ DatabaseViewConfig,
7
+ PropertyConfig,
8
+ PropertyDefinition,
9
+ PropertyType,
10
+ PropertyValue,
11
+ SelectOption,
12
+ ViewType,
13
+ } from './types';
14
+
15
+ export class DatabaseModel {
16
+ private schema: PropertyDefinition[];
17
+ private rows: DatabaseRow[] = [];
18
+ private views: DatabaseViewConfig[];
19
+ private activeViewId: string;
20
+
21
+ constructor(data?: Partial<DatabaseData>) {
22
+ if (data?.schema !== undefined && data.schema.length > 0) {
23
+ this.schema = data.schema.map((p) => ({ ...p }));
24
+ } else {
25
+ this.schema = DatabaseModel.createDefaultSchema();
26
+ }
27
+
28
+ if (data?.views !== undefined && data.views.length > 0) {
29
+ this.views = data.views.map((v) => ({ ...v, sorts: [...v.sorts], filters: [...v.filters], visibleProperties: [...v.visibleProperties] }));
30
+ } else {
31
+ const statusProp = this.schema.find((p) => p.type === 'select');
32
+ this.views = [DatabaseModel.createDefaultView(statusProp?.id)];
33
+ }
34
+
35
+ this.activeViewId = data?.activeViewId || (this.views.length > 0 ? this.views[0].id : '');
36
+ }
37
+
38
+ // ─── Schema ───
39
+
40
+ getSchema(): PropertyDefinition[] {
41
+ return [...this.schema];
42
+ }
43
+
44
+ getProperty(propertyId: string): PropertyDefinition | undefined {
45
+ return this.schema.find((p) => p.id === propertyId);
46
+ }
47
+
48
+ addProperty(name: string, type: PropertyType, config?: PropertyConfig): PropertyDefinition {
49
+ const lastPosition = this.schema.length > 0 ? this.schema[this.schema.length - 1].position : null;
50
+ const prop: PropertyDefinition = {
51
+ id: nanoid(),
52
+ name,
53
+ type,
54
+ position: generateKeyBetween(lastPosition, null),
55
+ ...(config !== undefined ? { config } : {}),
56
+ };
57
+ this.schema.push(prop);
58
+ return prop;
59
+ }
60
+
61
+ updateProperty(propertyId: string, changes: Partial<Pick<PropertyDefinition, 'name' | 'config'>>): void {
62
+ const prop = this.schema.find((p) => p.id === propertyId);
63
+ if (prop === undefined) return;
64
+ if (changes.name !== undefined) prop.name = changes.name;
65
+ if (changes.config !== undefined) prop.config = changes.config;
66
+ }
67
+
68
+ deleteProperty(propertyId: string): void {
69
+ this.schema = this.schema.filter((p) => p.id !== propertyId);
70
+ }
71
+
72
+ // ─── Row projection ───
73
+
74
+ setRows(rows: DatabaseRow[]): void {
75
+ this.rows = rows;
76
+ }
77
+
78
+ getOrderedRows(): DatabaseRow[] {
79
+ return [...this.rows].sort((a, b) => a.position.localeCompare(b.position));
80
+ }
81
+
82
+ getRow(rowId: string): DatabaseRow | undefined {
83
+ return this.rows.find((r) => r.id === rowId);
84
+ }
85
+
86
+ createRowData(properties?: Record<string, PropertyValue>): DatabaseRow {
87
+ const ordered = this.getOrderedRows();
88
+ const lastPosition = ordered.length > 0 ? ordered[ordered.length - 1].position : null;
89
+ return {
90
+ id: nanoid(),
91
+ position: generateKeyBetween(lastPosition, null),
92
+ properties: properties ?? {},
93
+ };
94
+ }
95
+
96
+ // ─── View-oriented queries ───
97
+
98
+ getRowsGroupedBy(propertyId: string): Map<string, DatabaseRow[]> {
99
+ const groups = new Map<string, DatabaseRow[]>();
100
+ const ordered = this.getOrderedRows();
101
+ for (const row of ordered) {
102
+ const rawValue = row.properties[propertyId];
103
+ const key = this.toGroupKey(rawValue);
104
+ const existing = groups.get(key);
105
+ if (existing !== undefined) {
106
+ existing.push(row);
107
+ } else {
108
+ groups.set(key, [row]);
109
+ }
110
+ }
111
+ return groups;
112
+ }
113
+
114
+ getSelectOptions(propertyId: string): SelectOption[] {
115
+ const prop = this.getProperty(propertyId);
116
+ if (prop === undefined || (prop.type !== 'select' && prop.type !== 'multiSelect')) return [];
117
+ const options = prop.config?.options ?? [];
118
+ return [...options].sort((a, b) => (a.position < b.position ? -1 : 1));
119
+ }
120
+
121
+ // ─── Views ───
122
+
123
+ getViews(): DatabaseViewConfig[] {
124
+ return [...this.views];
125
+ }
126
+
127
+ getView(viewId: string): DatabaseViewConfig | undefined {
128
+ return this.views.find((v) => v.id === viewId);
129
+ }
130
+
131
+ addView(name: string, type: ViewType, config: Partial<Pick<DatabaseViewConfig, 'groupBy' | 'sorts' | 'filters' | 'visibleProperties'>> = {}): DatabaseViewConfig {
132
+ const sorted = [...this.views].sort((a, b) => (a.position < b.position ? -1 : 1));
133
+ const lastPosition = sorted.length > 0 ? sorted[sorted.length - 1].position : null;
134
+ const view: DatabaseViewConfig = {
135
+ id: nanoid(),
136
+ name,
137
+ type,
138
+ position: generateKeyBetween(lastPosition, null),
139
+ groupBy: config.groupBy,
140
+ sorts: config.sorts ?? [],
141
+ filters: config.filters ?? [],
142
+ visibleProperties: config.visibleProperties ?? [],
143
+ };
144
+ this.views.push(view);
145
+ return view;
146
+ }
147
+
148
+ updateView(viewId: string, changes: Partial<Pick<DatabaseViewConfig, 'name' | 'type' | 'position' | 'groupBy' | 'sorts' | 'filters' | 'visibleProperties'>>): void {
149
+ const view = this.views.find((v) => v.id === viewId);
150
+ if (view === undefined) return;
151
+ Object.assign(view, changes);
152
+ }
153
+
154
+ deleteView(viewId: string): void {
155
+ this.views = this.views.filter((v) => v.id !== viewId);
156
+ }
157
+
158
+ // ─── Hydrate ───
159
+
160
+ hydrate(data: Partial<DatabaseData>): void {
161
+ if (data.schema !== undefined) {
162
+ this.schema = structuredClone(data.schema);
163
+ }
164
+ if (data.views !== undefined) {
165
+ this.views = structuredClone(data.views);
166
+ }
167
+ }
168
+
169
+ // ─── Snapshot ───
170
+
171
+ snapshot(): DatabaseData {
172
+ return {
173
+ schema: structuredClone(this.schema),
174
+ views: structuredClone(this.views),
175
+ activeViewId: this.activeViewId,
176
+ };
177
+ }
178
+
179
+ // ─── Static helpers ───
180
+
181
+ private toGroupKey(value: PropertyValue | undefined): string {
182
+ if (value === undefined || value === null) return '';
183
+ if (typeof value === 'string') return value;
184
+ if (typeof value === 'boolean' || typeof value === 'number') return String(value);
185
+ return '';
186
+ }
187
+
188
+ static positionBetween(after: string | null, before: string | null): string {
189
+ return generateKeyBetween(after, before);
190
+ }
191
+
192
+ private static createDefaultSchema(): PropertyDefinition[] {
193
+ return [
194
+ { id: nanoid(), name: 'Title', type: 'title', position: 'a0' },
195
+ {
196
+ id: nanoid(), name: 'Status', type: 'select', position: 'a1',
197
+ config: {
198
+ options: [
199
+ { id: nanoid(), label: 'Not started', color: 'gray', position: 'a0' },
200
+ { id: nanoid(), label: 'In progress', color: 'blue', position: 'a1' },
201
+ { id: nanoid(), label: 'Done', color: 'green', position: 'a2' },
202
+ ],
203
+ },
204
+ },
205
+ ];
206
+ }
207
+
208
+ private static createDefaultView(groupByPropertyId?: string): DatabaseViewConfig {
209
+ return {
210
+ id: nanoid(), name: 'Board', type: 'board', position: 'a0',
211
+ groupBy: groupByPropertyId, sorts: [], filters: [], visibleProperties: [],
212
+ };
213
+ }
214
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ IconText,
3
+ IconHash,
4
+ IconList,
5
+ IconListBulleted,
6
+ IconCalendar,
7
+ IconListChecklist,
8
+ IconGlobe,
9
+ } from '../../components/icons';
10
+ import type { PropertyType } from './types';
11
+
12
+ interface PropertyTypeOption {
13
+ type: PropertyType;
14
+ icon: string;
15
+ label: string;
16
+ }
17
+
18
+ const PROPERTY_TYPES: PropertyTypeOption[] = [
19
+ { type: 'text', icon: IconText, label: 'Text' },
20
+ { type: 'number', icon: IconHash, label: 'Number' },
21
+ { type: 'select', icon: IconList, label: 'Select' },
22
+ { type: 'multiSelect', icon: IconListBulleted, label: 'Multi-select' },
23
+ { type: 'date', icon: IconCalendar, label: 'Date' },
24
+ { type: 'checkbox', icon: IconListChecklist, label: 'Checkbox' },
25
+ { type: 'url', icon: IconGlobe, label: 'URL' },
26
+ ];
27
+
28
+ export interface PropertyTypePopoverOptions {
29
+ onSelect: (type: PropertyType) => void;
30
+ }
31
+
32
+ export class DatabasePropertyTypePopover {
33
+ private readonly onSelect: (type: PropertyType) => void;
34
+ private popoverEl: HTMLElement | null = null;
35
+ private boundOutsideClick: ((e: MouseEvent) => void) | null = null;
36
+
37
+ constructor(options: PropertyTypePopoverOptions) {
38
+ this.onSelect = options.onSelect;
39
+ }
40
+
41
+ open(anchor: HTMLElement): void {
42
+ this.close();
43
+
44
+ const popover = document.createElement('div');
45
+ popover.setAttribute('data-blok-popover', '');
46
+ popover.setAttribute('data-blok-popover-opened', '');
47
+ popover.setAttribute('data-blok-database-property-type-popover', '');
48
+ popover.style.position = 'fixed';
49
+ popover.style.zIndex = '1000';
50
+
51
+ const rect = anchor.getBoundingClientRect();
52
+ popover.style.top = `${rect.bottom + 4}px`;
53
+ popover.style.left = `${rect.left}px`;
54
+
55
+ const heading = document.createElement('div');
56
+ heading.setAttribute('data-blok-database-property-type-heading', '');
57
+ heading.textContent = 'Property type';
58
+ popover.appendChild(heading);
59
+
60
+ for (const option of PROPERTY_TYPES) {
61
+ const item = document.createElement('div');
62
+ item.setAttribute('data-blok-database-property-type-option', option.type);
63
+
64
+ const iconEl = document.createElement('div');
65
+ iconEl.setAttribute('data-blok-database-property-type-option-icon', '');
66
+ iconEl.innerHTML = option.icon;
67
+ item.appendChild(iconEl);
68
+
69
+ const label = document.createElement('span');
70
+ label.textContent = option.label;
71
+ item.appendChild(label);
72
+
73
+ item.addEventListener('click', () => {
74
+ this.onSelect(option.type);
75
+ this.close();
76
+ });
77
+
78
+ popover.appendChild(item);
79
+ }
80
+
81
+ document.body.appendChild(popover);
82
+ this.popoverEl = popover;
83
+
84
+ this.boundOutsideClick = (e: MouseEvent): void => {
85
+ const target = e.target as HTMLElement;
86
+ if (!popover.contains(target) && !anchor.contains(target)) {
87
+ this.close();
88
+ }
89
+ };
90
+
91
+ document.addEventListener('mousedown', this.boundOutsideClick);
92
+ }
93
+
94
+ close(): void {
95
+ if (this.popoverEl !== null) {
96
+ this.popoverEl.remove();
97
+ this.popoverEl = null;
98
+ }
99
+ if (this.boundOutsideClick !== null) {
100
+ document.removeEventListener('mousedown', this.boundOutsideClick);
101
+ this.boundOutsideClick = null;
102
+ }
103
+ }
104
+
105
+ destroy(): void {
106
+ this.close();
107
+ }
108
+ }