@jackuait/blok 0.7.0-beta.1 → 0.7.0-beta.2

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 (243) hide show
  1. package/README.md +20 -4
  2. package/dist/blok.mjs +2 -2
  3. package/dist/chunks/{blok-ob9Fwr1L.mjs → blok-D9Rs29Wo.mjs} +1620 -1441
  4. package/dist/chunks/{inline-tool-convert-CvFW2iie.mjs → constants-DmNIR3I8.mjs} +316 -297
  5. package/dist/chunks/{i18next-loader-Bu3vFvye.mjs → i18next-loader-C2-jYpLi.mjs} +1 -1
  6. package/dist/chunks/index-D7V1g7Oq.mjs +130 -0
  7. package/dist/chunks/{messages-D2NOpHn9.mjs → messages-0Pxnqd4N.mjs} +7 -0
  8. package/dist/chunks/{messages-GSByFygY.mjs → messages-0ZXYUq7S.mjs} +7 -0
  9. package/dist/{messages-BUl_Rcnj.mjs → chunks/messages-2OD2uUDS.mjs} +9 -2
  10. package/dist/{messages-CgTq3QhU.mjs → chunks/messages-7cEMfYzh.mjs} +7 -0
  11. package/dist/{messages-DlJbPF2T.mjs → chunks/messages-8mwfda1Q.mjs} +7 -0
  12. package/dist/{messages-D9ndgBnU.mjs → chunks/messages-B-FqWsBM.mjs} +7 -0
  13. package/dist/{messages-B217znr-.mjs → chunks/messages-B1jzqWiQ.mjs} +7 -0
  14. package/dist/{messages-BcpCubnC.mjs → chunks/messages-B5wk4Ezz.mjs} +7 -0
  15. package/dist/chunks/{messages-DRXWF0PV.mjs → messages-BAZ5Ld8x.mjs} +7 -0
  16. package/dist/{messages-CRJ_mchV.mjs → chunks/messages-BBhGp198.mjs} +7 -0
  17. package/dist/chunks/{messages-yHcs38yI.mjs → messages-BC9IjIb7.mjs} +7 -0
  18. package/dist/chunks/{messages-Cr94GzbX.mjs → messages-BFEmpeV-.mjs} +7 -0
  19. package/dist/chunks/{messages-ucTVgS5G.mjs → messages-BGqzTZy0.mjs} +7 -0
  20. package/dist/{messages-begYOTgC.mjs → chunks/messages-BICs1abK.mjs} +7 -0
  21. package/dist/chunks/{messages-DVQvl8Qj.mjs → messages-BJX6rOnd.mjs} +7 -0
  22. package/dist/chunks/{messages-Chb7k3Rg.mjs → messages-BL2bXRhN.mjs} +7 -0
  23. package/dist/{messages-Phkd7XmE.mjs → chunks/messages-BMs5qdlH.mjs} +7 -0
  24. package/dist/chunks/{messages-Cjjo7yHR.mjs → messages-BRsjUNwB.mjs} +7 -0
  25. package/dist/chunks/{messages-D4qqwVgQ.mjs → messages-BSqV8OUR.mjs} +7 -0
  26. package/dist/chunks/{messages-DviiFSv2.mjs → messages-BTqu3DfG.mjs} +7 -0
  27. package/dist/chunks/{messages-0AbcLMLm.mjs → messages-BXnDEsur.mjs} +7 -0
  28. package/dist/{messages-CmR9ftc_.mjs → chunks/messages-BYcre4-6.mjs} +7 -0
  29. package/dist/{messages-wmi-iFkH.mjs → chunks/messages-BZ9LRJf-.mjs} +7 -0
  30. package/dist/chunks/{messages-D00x4S8o.mjs → messages-BgypBy7y.mjs} +7 -0
  31. package/dist/{messages-96kNZDll.mjs → chunks/messages-BsuGf70G.mjs} +7 -0
  32. package/dist/chunks/{messages-v3GipbFl.mjs → messages-BwaoF4lQ.mjs} +7 -0
  33. package/dist/{messages-DDTQgImT.mjs → chunks/messages-C1l8_7-y.mjs} +7 -0
  34. package/dist/{messages-B1FZ8lxU.mjs → chunks/messages-C5NA_r9v.mjs} +7 -0
  35. package/dist/{messages-Cs8zmZ3L.mjs → chunks/messages-C6zgZ5pA.mjs} +7 -0
  36. package/dist/chunks/{messages-ZjUAIWb1.mjs → messages-CAo5ghFI.mjs} +7 -0
  37. package/dist/{messages-D5S1Dnpm.mjs → chunks/messages-CH9qlJ9I.mjs} +7 -0
  38. package/dist/{messages-D7u2bmP2.mjs → chunks/messages-CI0HqAeS.mjs} +7 -0
  39. package/dist/{messages-DH_jBeED.mjs → chunks/messages-CJJtms9k.mjs} +7 -0
  40. package/dist/{messages-CDBLbUOQ.mjs → chunks/messages-CM2hJqk6.mjs} +7 -0
  41. package/dist/chunks/{messages-8IPXkrDl.mjs → messages-CRMiDPIQ.mjs} +7 -0
  42. package/dist/chunks/{messages-Dzzn6XoD.mjs → messages-CWsZuBj1.mjs} +7 -0
  43. package/dist/chunks/{messages-CW4c4cRk.mjs → messages-C_gLHo6A.mjs} +7 -0
  44. package/dist/{messages-CH4hrauY.mjs → chunks/messages-Cbu-NUDn.mjs} +7 -0
  45. package/dist/{messages-RonBBCnh.mjs → chunks/messages-Cjb_MCeh.mjs} +7 -0
  46. package/dist/chunks/{messages-BJ6zrz2j.mjs → messages-ClXYO9Wn.mjs} +7 -0
  47. package/dist/chunks/{messages-CrCYPCk3.mjs → messages-CsH20vhP.mjs} +7 -0
  48. package/dist/{messages-CzK0LEhb.mjs → chunks/messages-CsjAGhzA.mjs} +7 -0
  49. package/dist/chunks/{messages-BZlmVRwn.mjs → messages-Cx7VKFOE.mjs} +7 -0
  50. package/dist/chunks/{messages-0E0AkrNu.mjs → messages-D3JeBwxo.mjs} +7 -0
  51. package/dist/chunks/{messages-D85FqxgY.mjs → messages-D541fieJ.mjs} +7 -0
  52. package/dist/{messages-4v4MuVEc.mjs → chunks/messages-D7XPdglc.mjs} +7 -0
  53. package/dist/{messages-BC8IN4Bf.mjs → chunks/messages-DBhylfvt.mjs} +7 -0
  54. package/dist/chunks/{messages-B8WNljW3.mjs → messages-DCA120lW.mjs} +7 -0
  55. package/dist/chunks/{messages-Cr49Nt3U.mjs → messages-DCf_xZMN.mjs} +7 -0
  56. package/dist/chunks/{messages-VDriF5Qy.mjs → messages-DDwXKCpe.mjs} +7 -0
  57. package/dist/{messages-b1EdvUm0.mjs → chunks/messages-DNKDlxcy.mjs} +7 -0
  58. package/dist/{messages-L_kl2Qvh.mjs → chunks/messages-DPvEjrGK.mjs} +7 -0
  59. package/dist/chunks/{messages-62v-CLC-.mjs → messages-DQ-AkNxA.mjs} +7 -0
  60. package/dist/chunks/{messages-DdK-nFGm.mjs → messages-DVuvkNap.mjs} +7 -0
  61. package/dist/{messages-DnVlmiNT.mjs → chunks/messages-DaglyqUT.mjs} +7 -0
  62. package/dist/{messages-Bm-E4iRC.mjs → chunks/messages-Di0bAfwA.mjs} +7 -0
  63. package/dist/{messages-D1mn7Zd5.mjs → chunks/messages-DuLct0Yr.mjs} +7 -0
  64. package/dist/{messages-8DeO60Oo.mjs → chunks/messages-DzEYYhZh.mjs} +7 -0
  65. package/dist/chunks/{messages-CfiyT2Wi.mjs → messages-DznNGAB2.mjs} +7 -0
  66. package/dist/chunks/{messages-DXktiao_.mjs → messages-DzoIzyu8.mjs} +7 -0
  67. package/dist/{messages-C_4otP7U.mjs → chunks/messages-QYOGmket.mjs} +7 -0
  68. package/dist/chunks/{messages-nefz1S71.mjs → messages-cEjGDAgI.mjs} +7 -0
  69. package/dist/chunks/{messages-jrncnb-H.mjs → messages-ddhvrdpE.mjs} +7 -0
  70. package/dist/chunks/{messages-DzqM3Fel.mjs → messages-mwfNK5nZ.mjs} +7 -0
  71. package/dist/chunks/{messages-Cl6ayUaq.mjs → messages-nG_vNDte.mjs} +7 -0
  72. package/dist/{messages-C4jL-90N.mjs → chunks/messages-tDq3Owh7.mjs} +7 -0
  73. package/dist/{messages-BI43k_BD.mjs → chunks/messages-x6VJVZKx.mjs} +7 -0
  74. package/dist/full.mjs +2 -2
  75. package/dist/locales.mjs +87 -80
  76. package/dist/{messages-D2NOpHn9.mjs → messages-0Pxnqd4N.mjs} +7 -0
  77. package/dist/{messages-GSByFygY.mjs → messages-0ZXYUq7S.mjs} +7 -0
  78. package/dist/{chunks/messages-BUl_Rcnj.mjs → messages-2OD2uUDS.mjs} +9 -2
  79. package/dist/{chunks/messages-CgTq3QhU.mjs → messages-7cEMfYzh.mjs} +7 -0
  80. package/dist/{chunks/messages-DlJbPF2T.mjs → messages-8mwfda1Q.mjs} +7 -0
  81. package/dist/{chunks/messages-D9ndgBnU.mjs → messages-B-FqWsBM.mjs} +7 -0
  82. package/dist/{chunks/messages-B217znr-.mjs → messages-B1jzqWiQ.mjs} +7 -0
  83. package/dist/{chunks/messages-BcpCubnC.mjs → messages-B5wk4Ezz.mjs} +7 -0
  84. package/dist/{messages-DRXWF0PV.mjs → messages-BAZ5Ld8x.mjs} +7 -0
  85. package/dist/{chunks/messages-CRJ_mchV.mjs → messages-BBhGp198.mjs} +7 -0
  86. package/dist/{messages-yHcs38yI.mjs → messages-BC9IjIb7.mjs} +7 -0
  87. package/dist/{messages-Cr94GzbX.mjs → messages-BFEmpeV-.mjs} +7 -0
  88. package/dist/{messages-ucTVgS5G.mjs → messages-BGqzTZy0.mjs} +7 -0
  89. package/dist/{chunks/messages-begYOTgC.mjs → messages-BICs1abK.mjs} +7 -0
  90. package/dist/{messages-DVQvl8Qj.mjs → messages-BJX6rOnd.mjs} +7 -0
  91. package/dist/{messages-Chb7k3Rg.mjs → messages-BL2bXRhN.mjs} +7 -0
  92. package/dist/{chunks/messages-Phkd7XmE.mjs → messages-BMs5qdlH.mjs} +7 -0
  93. package/dist/{messages-Cjjo7yHR.mjs → messages-BRsjUNwB.mjs} +7 -0
  94. package/dist/{messages-D4qqwVgQ.mjs → messages-BSqV8OUR.mjs} +7 -0
  95. package/dist/{messages-DviiFSv2.mjs → messages-BTqu3DfG.mjs} +7 -0
  96. package/dist/{messages-0AbcLMLm.mjs → messages-BXnDEsur.mjs} +7 -0
  97. package/dist/{chunks/messages-CmR9ftc_.mjs → messages-BYcre4-6.mjs} +7 -0
  98. package/dist/{chunks/messages-wmi-iFkH.mjs → messages-BZ9LRJf-.mjs} +7 -0
  99. package/dist/{messages-D00x4S8o.mjs → messages-BgypBy7y.mjs} +7 -0
  100. package/dist/{chunks/messages-96kNZDll.mjs → messages-BsuGf70G.mjs} +7 -0
  101. package/dist/{messages-v3GipbFl.mjs → messages-BwaoF4lQ.mjs} +7 -0
  102. package/dist/{chunks/messages-DDTQgImT.mjs → messages-C1l8_7-y.mjs} +7 -0
  103. package/dist/{chunks/messages-B1FZ8lxU.mjs → messages-C5NA_r9v.mjs} +7 -0
  104. package/dist/{chunks/messages-Cs8zmZ3L.mjs → messages-C6zgZ5pA.mjs} +7 -0
  105. package/dist/{messages-ZjUAIWb1.mjs → messages-CAo5ghFI.mjs} +7 -0
  106. package/dist/{chunks/messages-D5S1Dnpm.mjs → messages-CH9qlJ9I.mjs} +7 -0
  107. package/dist/{chunks/messages-D7u2bmP2.mjs → messages-CI0HqAeS.mjs} +7 -0
  108. package/dist/{chunks/messages-DH_jBeED.mjs → messages-CJJtms9k.mjs} +7 -0
  109. package/dist/{chunks/messages-CDBLbUOQ.mjs → messages-CM2hJqk6.mjs} +7 -0
  110. package/dist/{messages-8IPXkrDl.mjs → messages-CRMiDPIQ.mjs} +7 -0
  111. package/dist/{messages-Dzzn6XoD.mjs → messages-CWsZuBj1.mjs} +7 -0
  112. package/dist/{messages-CW4c4cRk.mjs → messages-C_gLHo6A.mjs} +7 -0
  113. package/dist/{chunks/messages-CH4hrauY.mjs → messages-Cbu-NUDn.mjs} +7 -0
  114. package/dist/{chunks/messages-RonBBCnh.mjs → messages-Cjb_MCeh.mjs} +7 -0
  115. package/dist/{messages-BJ6zrz2j.mjs → messages-ClXYO9Wn.mjs} +7 -0
  116. package/dist/{messages-CrCYPCk3.mjs → messages-CsH20vhP.mjs} +7 -0
  117. package/dist/{chunks/messages-CzK0LEhb.mjs → messages-CsjAGhzA.mjs} +7 -0
  118. package/dist/{messages-BZlmVRwn.mjs → messages-Cx7VKFOE.mjs} +7 -0
  119. package/dist/{messages-0E0AkrNu.mjs → messages-D3JeBwxo.mjs} +7 -0
  120. package/dist/{messages-D85FqxgY.mjs → messages-D541fieJ.mjs} +7 -0
  121. package/dist/{chunks/messages-4v4MuVEc.mjs → messages-D7XPdglc.mjs} +7 -0
  122. package/dist/{chunks/messages-BC8IN4Bf.mjs → messages-DBhylfvt.mjs} +7 -0
  123. package/dist/{messages-B8WNljW3.mjs → messages-DCA120lW.mjs} +7 -0
  124. package/dist/{messages-Cr49Nt3U.mjs → messages-DCf_xZMN.mjs} +7 -0
  125. package/dist/{messages-VDriF5Qy.mjs → messages-DDwXKCpe.mjs} +7 -0
  126. package/dist/{chunks/messages-b1EdvUm0.mjs → messages-DNKDlxcy.mjs} +7 -0
  127. package/dist/{chunks/messages-L_kl2Qvh.mjs → messages-DPvEjrGK.mjs} +7 -0
  128. package/dist/{messages-62v-CLC-.mjs → messages-DQ-AkNxA.mjs} +7 -0
  129. package/dist/{messages-DdK-nFGm.mjs → messages-DVuvkNap.mjs} +7 -0
  130. package/dist/{chunks/messages-DnVlmiNT.mjs → messages-DaglyqUT.mjs} +7 -0
  131. package/dist/{chunks/messages-Bm-E4iRC.mjs → messages-Di0bAfwA.mjs} +7 -0
  132. package/dist/{chunks/messages-D1mn7Zd5.mjs → messages-DuLct0Yr.mjs} +7 -0
  133. package/dist/{chunks/messages-8DeO60Oo.mjs → messages-DzEYYhZh.mjs} +7 -0
  134. package/dist/{messages-CfiyT2Wi.mjs → messages-DznNGAB2.mjs} +7 -0
  135. package/dist/{messages-DXktiao_.mjs → messages-DzoIzyu8.mjs} +7 -0
  136. package/dist/{chunks/messages-C_4otP7U.mjs → messages-QYOGmket.mjs} +7 -0
  137. package/dist/{messages-nefz1S71.mjs → messages-cEjGDAgI.mjs} +7 -0
  138. package/dist/{messages-jrncnb-H.mjs → messages-ddhvrdpE.mjs} +7 -0
  139. package/dist/{messages-DzqM3Fel.mjs → messages-mwfNK5nZ.mjs} +7 -0
  140. package/dist/{messages-Cl6ayUaq.mjs → messages-nG_vNDte.mjs} +7 -0
  141. package/dist/{chunks/messages-C4jL-90N.mjs → messages-tDq3Owh7.mjs} +7 -0
  142. package/dist/{chunks/messages-BI43k_BD.mjs → messages-x6VJVZKx.mjs} +7 -0
  143. package/dist/tools.mjs +442 -337
  144. package/package.json +1 -1
  145. package/src/components/i18n/locales/am/messages.json +7 -0
  146. package/src/components/i18n/locales/ar/messages.json +7 -0
  147. package/src/components/i18n/locales/az/messages.json +7 -0
  148. package/src/components/i18n/locales/bg/messages.json +7 -0
  149. package/src/components/i18n/locales/bn/messages.json +7 -0
  150. package/src/components/i18n/locales/bs/messages.json +7 -0
  151. package/src/components/i18n/locales/cs/messages.json +7 -0
  152. package/src/components/i18n/locales/da/messages.json +7 -0
  153. package/src/components/i18n/locales/de/messages.json +7 -0
  154. package/src/components/i18n/locales/dv/messages.json +7 -0
  155. package/src/components/i18n/locales/el/messages.json +7 -0
  156. package/src/components/i18n/locales/en/messages.json +7 -0
  157. package/src/components/i18n/locales/es/messages.json +7 -0
  158. package/src/components/i18n/locales/et/messages.json +7 -0
  159. package/src/components/i18n/locales/fa/messages.json +7 -0
  160. package/src/components/i18n/locales/fi/messages.json +7 -0
  161. package/src/components/i18n/locales/fil/messages.json +7 -0
  162. package/src/components/i18n/locales/fr/messages.json +7 -0
  163. package/src/components/i18n/locales/gu/messages.json +7 -0
  164. package/src/components/i18n/locales/he/messages.json +7 -0
  165. package/src/components/i18n/locales/hi/messages.json +7 -0
  166. package/src/components/i18n/locales/hr/messages.json +7 -0
  167. package/src/components/i18n/locales/hu/messages.json +7 -0
  168. package/src/components/i18n/locales/hy/messages.json +7 -0
  169. package/src/components/i18n/locales/id/messages.json +7 -0
  170. package/src/components/i18n/locales/it/messages.json +7 -0
  171. package/src/components/i18n/locales/ja/messages.json +7 -0
  172. package/src/components/i18n/locales/ka/messages.json +7 -0
  173. package/src/components/i18n/locales/km/messages.json +7 -0
  174. package/src/components/i18n/locales/kn/messages.json +7 -0
  175. package/src/components/i18n/locales/ko/messages.json +7 -0
  176. package/src/components/i18n/locales/ku/messages.json +7 -0
  177. package/src/components/i18n/locales/lo/messages.json +7 -0
  178. package/src/components/i18n/locales/lt/messages.json +7 -0
  179. package/src/components/i18n/locales/lv/messages.json +7 -0
  180. package/src/components/i18n/locales/mk/messages.json +7 -0
  181. package/src/components/i18n/locales/ml/messages.json +7 -0
  182. package/src/components/i18n/locales/mn/messages.json +7 -0
  183. package/src/components/i18n/locales/mr/messages.json +7 -0
  184. package/src/components/i18n/locales/ms/messages.json +7 -0
  185. package/src/components/i18n/locales/my/messages.json +7 -0
  186. package/src/components/i18n/locales/ne/messages.json +7 -0
  187. package/src/components/i18n/locales/nl/messages.json +7 -0
  188. package/src/components/i18n/locales/no/messages.json +7 -0
  189. package/src/components/i18n/locales/pa/messages.json +7 -0
  190. package/src/components/i18n/locales/pl/messages.json +7 -0
  191. package/src/components/i18n/locales/ps/messages.json +7 -0
  192. package/src/components/i18n/locales/pt/messages.json +7 -0
  193. package/src/components/i18n/locales/ro/messages.json +7 -0
  194. package/src/components/i18n/locales/ru/messages.json +7 -0
  195. package/src/components/i18n/locales/sd/messages.json +7 -0
  196. package/src/components/i18n/locales/si/messages.json +7 -0
  197. package/src/components/i18n/locales/sk/messages.json +7 -0
  198. package/src/components/i18n/locales/sl/messages.json +7 -0
  199. package/src/components/i18n/locales/sq/messages.json +7 -0
  200. package/src/components/i18n/locales/sr/messages.json +7 -0
  201. package/src/components/i18n/locales/sv/messages.json +7 -0
  202. package/src/components/i18n/locales/sw/messages.json +7 -0
  203. package/src/components/i18n/locales/ta/messages.json +7 -0
  204. package/src/components/i18n/locales/te/messages.json +7 -0
  205. package/src/components/i18n/locales/th/messages.json +7 -0
  206. package/src/components/i18n/locales/tr/messages.json +7 -0
  207. package/src/components/i18n/locales/ug/messages.json +7 -0
  208. package/src/components/i18n/locales/uk/messages.json +7 -0
  209. package/src/components/i18n/locales/ur/messages.json +7 -0
  210. package/src/components/i18n/locales/vi/messages.json +7 -0
  211. package/src/components/i18n/locales/yi/messages.json +7 -0
  212. package/src/components/i18n/locales/zh/messages.json +7 -0
  213. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +44 -2
  214. package/src/components/modules/blockEvents/index.ts +1 -3
  215. package/src/components/modules/blockManager/blockManager.ts +16 -0
  216. package/src/components/modules/blockManager/operations.ts +106 -9
  217. package/src/components/modules/blockSelection.ts +2 -0
  218. package/src/components/modules/caret.ts +49 -4
  219. package/src/components/modules/drag/DragController.ts +34 -2
  220. package/src/components/modules/paste/handlers/blok-data-handler.ts +50 -3
  221. package/src/components/modules/toolbar/index.ts +11 -16
  222. package/src/components/modules/ui.ts +12 -0
  223. package/src/components/ui/toolbox.ts +19 -3
  224. package/src/components/utils/notifier/draw.ts +116 -14
  225. package/src/components/utils/notifier/index.ts +31 -4
  226. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +2 -2
  227. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +6 -7
  228. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +2 -2
  229. package/src/components/utils/popover/popover-abstract.ts +2 -0
  230. package/src/components/utils/popover/popover-desktop.ts +39 -2
  231. package/src/styles/main.css +46 -0
  232. package/src/tools/header/index.ts +124 -21
  233. package/src/tools/table/index.ts +2 -3
  234. package/src/tools/table/table-add-controls.ts +29 -1
  235. package/src/tools/table/table-cell-blocks.ts +93 -0
  236. package/src/tools/toggle/constants.ts +2 -2
  237. package/src/tools/toggle/dom-builder.ts +31 -4
  238. package/src/tools/toggle/index.ts +26 -4
  239. package/src/tools/toggle/toggle-keyboard.ts +19 -2
  240. package/src/tools/toggle/toggle-lifecycle.ts +1 -0
  241. package/src/tools/toggle/toggle-shortcuts.ts +14 -8
  242. package/types/utils/popover/popover.d.ts +8 -0
  243. package/dist/chunks/index-CZmRzRIX.mjs +0 -78
@@ -391,9 +391,11 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
391
391
  protected toggleNothingFoundMessage(isDisplayed: boolean): void {
392
392
  if (isDisplayed) {
393
393
  this.nodes.nothingFoundMessage.classList.remove('hidden');
394
+ this.nodes.nothingFoundMessage.classList.add('animate-[fade-in_150ms_ease_forwards]');
394
395
  this.nodes.nothingFoundMessage.setAttribute(DATA_ATTR.nothingFoundDisplayed, 'true');
395
396
  } else {
396
397
  this.nodes.nothingFoundMessage.classList.add('hidden');
398
+ this.nodes.nothingFoundMessage.classList.remove('animate-[fade-in_150ms_ease_forwards]');
397
399
  this.nodes.nothingFoundMessage.removeAttribute(DATA_ATTR.nothingFoundDisplayed);
398
400
  }
399
401
  }
@@ -60,6 +60,12 @@ export class PopoverDesktop extends PopoverAbstract {
60
60
  */
61
61
  private trigger: HTMLElement | undefined;
62
62
 
63
+ /**
64
+ * Optional element whose left edge is used for horizontal positioning
65
+ * instead of the trigger's left edge.
66
+ */
67
+ private leftAlignElement: HTMLElement | undefined;
68
+
63
69
  /**
64
70
  * Popover size cache
65
71
  */
@@ -78,6 +84,10 @@ export class PopoverDesktop extends PopoverAbstract {
78
84
  this.trigger = params.trigger;
79
85
  }
80
86
 
87
+ if (params.leftAlignElement) {
88
+ this.leftAlignElement = params.leftAlignElement;
89
+ }
90
+
81
91
  if (params.nestingLevel !== undefined) {
82
92
  this.nestingLevel = params.nestingLevel;
83
93
  }
@@ -191,7 +201,13 @@ export class PopoverDesktop extends PopoverAbstract {
191
201
  this.nodes.popover.style.setProperty(CSSVariables.PopoverLeft, '0px');
192
202
  }
193
203
 
194
- this.nodes.popover.style.setProperty(CSSVariables.PopoverHeight, this.size.height + 'px');
204
+ const measuredSize = this.size;
205
+
206
+ this.nodes.popover.style.setProperty(CSSVariables.PopoverHeight, measuredSize.height + 'px');
207
+
208
+ if (this.params.width === undefined || this.params.width === 'auto') {
209
+ this.nodes.popover.style.setProperty('--width', measuredSize.width + 'px');
210
+ }
195
211
 
196
212
  if (!this.trigger && !this.shouldOpenBottom) {
197
213
  this.setOpenTop(true);
@@ -270,7 +286,7 @@ export class PopoverDesktop extends PopoverAbstract {
270
286
  (rect.top - offset - popoverRect.height > window.scrollY);
271
287
  const top = shouldFlipTop ? rect.top - offset - popoverRect.height + window.scrollY : initialTop;
272
288
 
273
- const initialLeft = rect.left + window.scrollX;
289
+ const initialLeft = (this.leftAlignElement?.getBoundingClientRect().left ?? rect.left) + window.scrollX;
274
290
  const shouldFlipLeft = initialLeft + popoverRect.width > windowWidth + window.scrollX;
275
291
  const left = shouldFlipLeft ? Math.max(0, rect.right - popoverRect.width + window.scrollX) : initialLeft;
276
292
 
@@ -695,6 +711,18 @@ export class PopoverDesktop extends PopoverAbstract {
695
711
  // Cast data.items to PopoverItemDefault[] since we know that's what filterItems passes
696
712
  const matchingItems = data.items as unknown as PopoverItemDefault[];
697
713
 
714
+ /**
715
+ * When nothing is found, disable transitions so items hide instantly.
716
+ * The "Nothing found" message fade-in provides the visual transition;
717
+ * animating the last items' collapse simultaneously causes a jarring
718
+ * height bounce in the popover container.
719
+ */
720
+ if (isNothingFound) {
721
+ this.items.forEach(item => {
722
+ item.getElement()?.style.setProperty('transition-duration', '0s');
723
+ });
724
+ }
725
+
698
726
  this.items
699
727
  .forEach((item) => {
700
728
  const isDefaultItem = item instanceof PopoverItemDefault;
@@ -705,6 +733,15 @@ export class PopoverDesktop extends PopoverAbstract {
705
733
 
706
734
  item.toggleHidden(isHidden);
707
735
  });
736
+
737
+ if (isNothingFound) {
738
+ // Force reflow so the instant hide takes effect, then restore transitions
739
+ this.nodes.popoverContainer.offsetHeight;
740
+ this.items.forEach(item => {
741
+ item.getElement()?.style.removeProperty('transition-duration');
742
+ });
743
+ }
744
+
708
745
  this.toggleNothingFoundMessage(isNothingFound);
709
746
 
710
747
  /** List of elements available for keyboard navigation considering search query applied */
@@ -108,6 +108,9 @@
108
108
  --animate-wobble: wobble 400ms;
109
109
  --animate-rotation: rotation 1.2s infinite linear;
110
110
  --animate-notify-bounce-in: notifyBounceIn 600ms 1;
111
+ --animate-notify-slide-in: notifySlideIn 400ms cubic-bezier(0.16, 1, 0.3, 1) both;
112
+ --animate-notify-slide-out: notifySlideOut 250ms cubic-bezier(0.4, 0, 1, 1) both;
113
+ --animate-notify-progress: notifyProgress linear forwards;
111
114
 
112
115
  /* Keyframes */
113
116
  @keyframes fade-in {
@@ -166,6 +169,22 @@
166
169
  70% { transform: scale(0.9); }
167
170
  100% { transform: scale(1); }
168
171
  }
172
+
173
+ @keyframes notifySlideIn {
174
+ 0% { opacity: 0; transform: translateY(16px); }
175
+ 70% { opacity: 1; transform: translateY(-2px); }
176
+ 100% { opacity: 1; transform: translateY(0); }
177
+ }
178
+
179
+ @keyframes notifySlideOut {
180
+ 0% { opacity: 1; transform: translateY(0); }
181
+ 100% { opacity: 0; transform: translateY(8px); }
182
+ }
183
+
184
+ @keyframes notifyProgress {
185
+ 0% { width: 100%; }
186
+ 100% { width: 0%; }
187
+ }
169
188
  }
170
189
 
171
190
  /*
@@ -308,6 +327,19 @@
308
327
  @apply p-0 m-0 min-h-[1.6em];
309
328
  }
310
329
 
330
+ /**
331
+ * Exclude bare 'color' from transition-property inside table cells.
332
+ * StyleManager applies transition-colors (which includes 'color') for smooth
333
+ * selection-state changes, but inside table cells inherited text-color changes
334
+ * trigger a 150ms flash from black → target. Keep all other transition
335
+ * properties so background-color selection highlighting still animates.
336
+ */
337
+ [data-blok-table-cell] [data-blok-element-content] {
338
+ transition-property: background-color, border-color, outline-color,
339
+ text-decoration-color, fill, stroke, --tw-gradient-from,
340
+ --tw-gradient-via, --tw-gradient-to;
341
+ }
342
+
311
343
  /**
312
344
  * Table heading styles
313
345
  * Applied to first row (heading row) and first column (heading column) cells
@@ -324,3 +356,17 @@
324
356
  [data-blok-table-haze][data-blok-table-haze-visible] {
325
357
  @apply opacity-100;
326
358
  }
359
+
360
+ /**
361
+ * Slash search input appearance
362
+ * When the user types "/" to open the toolbox, the contenteditable
363
+ * transforms to look like a search input with a placeholder.
364
+ */
365
+ [data-blok-slash-search] {
366
+ @apply bg-[#F8F8F8] rounded-lg transition-colors duration-150 max-w-[240px];
367
+ }
368
+
369
+ [data-blok-slash-search]::after {
370
+ content: attr(data-blok-slash-search);
371
+ @apply text-gray-text font-medium text-base pointer-events-none;
372
+ }
@@ -23,7 +23,8 @@ import { IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconHeading, IconToggle
23
23
  import { PLACEHOLDER_CLASSES, setupPlaceholder } from '../../components/utils/placeholder';
24
24
  import { translateToolTitle } from '../../components/utils/tools';
25
25
  import { twMerge } from '../../components/utils/tw';
26
- import { ARROW_ICON, ARROW_STYLES, TOGGLE_ATTR, TOGGLE_WRAPPER_STYLES } from '../toggle/constants';
26
+ import { ARROW_ICON, TOGGLE_ATTR, TOGGLE_WRAPPER_STYLES } from '../toggle/constants';
27
+ import { buildArrow } from '../toggle/dom-builder';
27
28
  import { updateArrowState, updateChildrenVisibility } from '../toggle/toggle-lifecycle';
28
29
 
29
30
  /**
@@ -216,6 +217,22 @@ export class Header implements BlockTool {
216
217
  normalized.isToggleable = true;
217
218
  }
218
219
 
220
+ /**
221
+ * Sanitize text to remove any previously saved arrow HTML (backwards compatibility)
222
+ */
223
+ if (normalized.text) {
224
+ const temp = document.createElement('div');
225
+
226
+ temp.innerHTML = normalized.text;
227
+
228
+ const arrowEl = temp.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
229
+
230
+ if (arrowEl) {
231
+ arrowEl.remove();
232
+ normalized.text = temp.innerHTML;
233
+ }
234
+ }
235
+
219
236
  return normalized;
220
237
  }
221
238
 
@@ -238,6 +255,42 @@ export class Header implements BlockTool {
238
255
  }
239
256
  }
240
257
 
258
+ /**
259
+ * Expand the toggle heading (no-op if not toggleable or already expanded).
260
+ * Can be called externally via block.call('expand').
261
+ */
262
+ public expand(): void {
263
+ if (!this._data.isToggleable || this._isOpen) {
264
+ return;
265
+ }
266
+
267
+ this._isOpen = true;
268
+
269
+ if (this._arrowElement && this._element) {
270
+ updateArrowState(this._arrowElement, this._element, this._isOpen);
271
+ }
272
+
273
+ this.updateChildrenVisibility();
274
+ }
275
+
276
+ /**
277
+ * Collapse the toggle heading (no-op if not toggleable or already collapsed).
278
+ * Can be called externally via block.call('collapse').
279
+ */
280
+ public collapse(): void {
281
+ if (!this._data.isToggleable || !this._isOpen) {
282
+ return;
283
+ }
284
+
285
+ this._isOpen = false;
286
+
287
+ if (this._arrowElement && this._element) {
288
+ updateArrowState(this._arrowElement, this._element, this._isOpen);
289
+ }
290
+
291
+ this.updateChildrenVisibility();
292
+ }
293
+
241
294
  /**
242
295
  * Returns header block tunes config
243
296
  *
@@ -357,7 +410,34 @@ export class Header implements BlockTool {
357
410
  * @param data - saved data to merge with current block
358
411
  */
359
412
  public merge(data: HeaderData): void {
360
- this._element.insertAdjacentHTML('beforeend', data.text);
413
+ /**
414
+ * Strip any arrow HTML from incoming data to prevent injection of toggle markup
415
+ */
416
+ const tempDiv = document.createElement('div');
417
+ tempDiv.innerHTML = data.text;
418
+ const arrowInData = tempDiv.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
419
+
420
+ if (arrowInData) {
421
+ arrowInData.remove();
422
+ }
423
+
424
+ const cleanText = tempDiv.innerHTML;
425
+
426
+ /**
427
+ * Strip arrow from current element, append text, then re-add arrow.
428
+ * This ensures text is appended to the content, not interleaved with toggle markup.
429
+ */
430
+ const arrowEl = this._element.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
431
+
432
+ if (arrowEl) {
433
+ arrowEl.remove();
434
+ }
435
+
436
+ this._element.insertAdjacentHTML('beforeend', cleanText);
437
+
438
+ if (arrowEl && this._data.isToggleable) {
439
+ this._element.prepend(arrowEl);
440
+ }
361
441
  }
362
442
 
363
443
  /**
@@ -378,11 +458,27 @@ export class Header implements BlockTool {
378
458
  * @returns saved data
379
459
  */
380
460
  public save(toolsContent: HTMLHeadingElement): HeaderData {
461
+ /**
462
+ * Strip arrow element before reading innerHTML to avoid saving toggle markup
463
+ */
464
+ const arrowEl = toolsContent.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
465
+
466
+ if (arrowEl) {
467
+ arrowEl.remove();
468
+ }
469
+
381
470
  const data: HeaderData = {
382
471
  text: toolsContent.innerHTML,
383
472
  level: this.currentLevel.number,
384
473
  };
385
474
 
475
+ /**
476
+ * Re-add arrow after reading so the DOM is not mutated
477
+ */
478
+ if (arrowEl && this._data.isToggleable) {
479
+ toolsContent.prepend(arrowEl);
480
+ }
481
+
386
482
  if (this._data.isToggleable === true) {
387
483
  data.isToggleable = true;
388
484
  }
@@ -426,7 +522,24 @@ export class Header implements BlockTool {
426
522
  * @returns Current data
427
523
  */
428
524
  public get data(): HeaderData {
525
+ /**
526
+ * Strip arrow element before reading innerHTML to avoid capturing toggle markup
527
+ */
528
+ const arrowEl = this._element.querySelector(`[${TOGGLE_ATTR.toggleArrow}]`);
529
+
530
+ if (arrowEl) {
531
+ arrowEl.remove();
532
+ }
533
+
429
534
  this._data.text = this._element.innerHTML;
535
+
536
+ /**
537
+ * Re-add arrow after reading so the DOM is not mutated
538
+ */
539
+ if (arrowEl && this._data.isToggleable) {
540
+ this._element.prepend(arrowEl);
541
+ }
542
+
430
543
  this._data.level = this.currentLevel.number;
431
544
 
432
545
  return this._data;
@@ -568,25 +681,7 @@ export class Header implements BlockTool {
568
681
  * @returns The arrow element
569
682
  */
570
683
  private buildArrow(): HTMLElement {
571
- const arrow = document.createElement('div');
572
- arrow.className = ARROW_STYLES;
573
- arrow.setAttribute(TOGGLE_ATTR.toggleArrow, '');
574
- arrow.setAttribute('role', 'button');
575
- arrow.setAttribute('tabindex', '-1');
576
- arrow.setAttribute('aria-label', 'Expand');
577
- arrow.contentEditable = 'false';
578
- arrow.innerHTML = ARROW_ICON;
579
-
580
- if (this._isOpen) {
581
- arrow.style.transform = 'rotate(90deg)';
582
- }
583
-
584
- arrow.addEventListener('click', (event: MouseEvent) => {
585
- event.stopPropagation();
586
- this.toggleOpen();
587
- });
588
-
589
- return arrow;
684
+ return buildArrow(this._isOpen, () => this.toggleOpen(), { contentEditableFalse: true });
590
685
  }
591
686
 
592
687
  /**
@@ -596,6 +691,14 @@ export class Header implements BlockTool {
596
691
  private toggleIsToggleable(): void {
597
692
  const wasToggleable = this._data.isToggleable === true;
598
693
 
694
+ /**
695
+ * If disabling toggle, ensure children are visible before removing toggle state
696
+ */
697
+ if (wasToggleable) {
698
+ updateChildrenVisibility(this.api, this.blockId ?? '', true);
699
+ this._isOpen = false;
700
+ }
701
+
599
702
  this.data = {
600
703
  level: this._data.level,
601
704
  text: this._data.text,
@@ -71,6 +71,7 @@ const WRAPPER_EDIT_CLASSES = [
71
71
  'after:left-0',
72
72
  'after:right-0',
73
73
  'after:h-10',
74
+ 'after:pointer-events-none',
74
75
  ];
75
76
 
76
77
  /**
@@ -405,9 +406,7 @@ export class Table implements BlockTool {
405
406
  applyCellColors(gridEl, this.model.snapshot().content);
406
407
 
407
408
  if (this.isNewTable) {
408
- const firstEditable = gridEl.querySelector<HTMLElement>('[contenteditable="true"]');
409
-
410
- firstEditable?.focus();
409
+ this.cellSelection?.selectRange({ minRow: 0, maxRow: 0, minCol: 0, maxCol: 0 });
411
410
  }
412
411
  }
413
412
 
@@ -79,6 +79,7 @@ export class TableAddControls {
79
79
  private dragState: DragState | null = null;
80
80
 
81
81
  private boundMouseMove: (e: MouseEvent) => void;
82
+ private boundDocumentMouseMove: (e: MouseEvent) => void;
82
83
  private boundMouseLeave: () => void;
83
84
  private boundAddRowClick: () => void;
84
85
  private boundAddColClick: () => void;
@@ -110,6 +111,7 @@ export class TableAddControls {
110
111
  this.onDragEnd = options.onDragEnd;
111
112
  this.getNewColumnWidth = options.getNewColumnWidth;
112
113
  this.boundMouseMove = this.handleMouseMove.bind(this);
114
+ this.boundDocumentMouseMove = this.handleDocumentMouseMove.bind(this);
113
115
  this.boundMouseLeave = this.handleMouseLeave.bind(this);
114
116
  this.boundPointerMove = this.handlePointerMove.bind(this);
115
117
  this.boundPointerUp = this.handlePointerUp.bind(this);
@@ -126,10 +128,10 @@ export class TableAddControls {
126
128
 
127
129
  this.wrapper.addEventListener('mousemove', this.boundMouseMove);
128
130
  this.wrapper.addEventListener('mouseleave', this.boundMouseLeave);
131
+ document.addEventListener('mousemove', this.boundDocumentMouseMove);
129
132
 
130
133
  this.addRowBtn.addEventListener('pointerdown', this.boundRowPointerDown);
131
134
  this.addColBtn.addEventListener('pointerdown', this.boundColPointerDown);
132
-
133
135
  }
134
136
 
135
137
  /**
@@ -212,6 +214,7 @@ export class TableAddControls {
212
214
  public destroy(): void {
213
215
  this.wrapper.removeEventListener('mousemove', this.boundMouseMove);
214
216
  this.wrapper.removeEventListener('mouseleave', this.boundMouseLeave);
217
+ document.removeEventListener('mousemove', this.boundDocumentMouseMove);
215
218
  this.addRowBtn.removeEventListener('pointerdown', this.boundRowPointerDown);
216
219
  this.addColBtn.removeEventListener('pointerdown', this.boundColPointerDown);
217
220
 
@@ -399,6 +402,31 @@ export class TableAddControls {
399
402
  this.scheduleHideCol();
400
403
  }
401
404
 
405
+ /**
406
+ * Document-level mousemove handler.
407
+ * Catches mouse movements outside the wrapper (e.g. in the ::after
408
+ * pseudo-element zone below the grid, which has pointer-events-none).
409
+ * Only delegates to handleMouseMove when the cursor is within the
410
+ * proximity zone around the grid to avoid unnecessary work.
411
+ */
412
+ private handleDocumentMouseMove(e: MouseEvent): void {
413
+ if (this.wrapper.contains(e.target as Node)) {
414
+ return;
415
+ }
416
+
417
+ const gridRect = this.grid.getBoundingClientRect();
418
+ const margin = PROXIMITY_PX;
419
+ const nearGrid =
420
+ e.clientX >= gridRect.left - margin &&
421
+ e.clientX <= gridRect.right + margin &&
422
+ e.clientY >= gridRect.top - margin &&
423
+ e.clientY <= gridRect.bottom + margin;
424
+
425
+ if (nearGrid) {
426
+ this.handleMouseMove(e);
427
+ }
428
+ }
429
+
402
430
  private showRow(): void {
403
431
  this.clearRowTimeout();
404
432
 
@@ -86,6 +86,9 @@ export class TableCellBlocks {
86
86
  /** Events deferred during structural operations, replayed or discarded afterward. */
87
87
  private deferredEvents: Array<unknown> = [];
88
88
 
89
+ /** When true, handleBlockMutation skips claiming so exitTableForward's new block stays outside the grid. */
90
+ private isExitingTable = false;
91
+
89
92
  constructor(options: TableCellBlocksOptions) {
90
93
  this.api = options.api;
91
94
  this.gridElement = options.gridElement;
@@ -140,6 +143,12 @@ export class TableCellBlocks {
140
143
 
141
144
  return;
142
145
  }
146
+
147
+ // ArrowDown at last row -> exit table
148
+ if (event.key === 'ArrowDown' && position.row === this.getRowCount() - 1) {
149
+ event.preventDefault();
150
+ this.exitTableForward();
151
+ }
143
152
  }
144
153
 
145
154
  /**
@@ -161,7 +170,12 @@ export class TableCellBlocks {
161
170
 
162
171
  if (nextRow < this.getRowCount()) {
163
172
  this.navigateToCell({ row: nextRow, col: 0 });
173
+
174
+ return;
164
175
  }
176
+
177
+ // At the very last cell — exit the table by focusing or creating a block below
178
+ this.exitTableForward();
165
179
  }
166
180
 
167
181
  /**
@@ -182,7 +196,82 @@ export class TableCellBlocks {
182
196
 
183
197
  if (prevRow >= 0) {
184
198
  this.navigateToCell({ row: prevRow, col: this.getColumnCount() - 1 }, true);
199
+
200
+ return;
185
201
  }
202
+
203
+ // At the very first cell — exit the table by focusing the block above
204
+ this.exitTableBackward();
205
+ }
206
+
207
+ /**
208
+ * Exit the table by focusing the first block after it, or creating one if none exists.
209
+ */
210
+ private exitTableForward(): void {
211
+ const tableIndex = this.api.blocks.getBlockIndex(this.tableBlockId);
212
+
213
+ if (tableIndex === undefined) {
214
+ return;
215
+ }
216
+
217
+ const blockAfterTable = this.findFirstBlockAfterTable(tableIndex);
218
+
219
+ if (blockAfterTable !== null) {
220
+ this.api.caret.setToBlock(blockAfterTable.id, 'start');
221
+
222
+ return;
223
+ }
224
+
225
+ /**
226
+ * No block after the table — create a new default block.
227
+ * Set isExitingTable so handleBlockMutation does not claim the new block
228
+ * into a cell (the block-added event fires synchronously during insert).
229
+ */
230
+ this.isExitingTable = true;
231
+
232
+ try {
233
+ const totalBlocks = this.api.blocks.getBlocksCount();
234
+ const newBlock = this.api.blocks.insert(undefined, {}, {}, totalBlocks, true);
235
+
236
+ this.api.caret.setToBlock(newBlock.id, 'start');
237
+ } finally {
238
+ this.isExitingTable = false;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Exit the table backward by focusing the block before the table.
244
+ * If no block exists before the table, do nothing.
245
+ */
246
+ private exitTableBackward(): void {
247
+ const tableIndex = this.api.blocks.getBlockIndex(this.tableBlockId);
248
+
249
+ if (tableIndex === undefined || tableIndex === 0) {
250
+ return;
251
+ }
252
+
253
+ // The block immediately before the table in the flat array
254
+ const blockBefore = this.api.blocks.getBlockByIndex(tableIndex - 1);
255
+
256
+ if (blockBefore) {
257
+ this.api.caret.setToBlock(blockBefore.id, 'end');
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Scan the flat block array starting after the table block, skipping all blocks
263
+ * whose holder is inside the table grid, and return the first non-child block.
264
+ * Returns null if no such block exists.
265
+ */
266
+ private findFirstBlockAfterTable(tableIndex: number): { id: string } | null {
267
+ const totalBlocks = this.api.blocks.getBlocksCount();
268
+ const candidates = Array.from({ length: totalBlocks - tableIndex - 1 }, (_, offset) =>
269
+ this.api.blocks.getBlockByIndex(tableIndex + 1 + offset)
270
+ );
271
+
272
+ return candidates.find(block =>
273
+ block !== null && block !== undefined && !this.gridElement.contains(block.holder)
274
+ ) ?? null;
186
275
  }
187
276
 
188
277
  /**
@@ -481,6 +570,10 @@ export class TableCellBlocks {
481
570
  return;
482
571
  }
483
572
 
573
+ if (this.isExitingTable) {
574
+ return;
575
+ }
576
+
484
577
  if (!this.isBlockMutationEvent(data)) {
485
578
  return;
486
579
  }
@@ -32,12 +32,12 @@ export const TOGGLE_WRAPPER_STYLES = 'flex items-start';
32
32
  /**
33
33
  * Styles for the toggle arrow button
34
34
  */
35
- export const ARROW_STYLES = 'flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-pointer select-none rounded hover:bg-black/5 transition-all duration-200 ease-in-out mt-px';
35
+ export const ARROW_STYLES = 'flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-pointer select-none rounded hover:bg-black/5 transition-all duration-200 ease-in-out mt-px focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none';
36
36
 
37
37
  /**
38
38
  * SVG icon for the toggle arrow
39
39
  */
40
- export const ARROW_ICON = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.5 2.5L8.5 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
40
+ export const ARROW_ICON = '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.5 2.5L8.5 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
41
41
 
42
42
  /**
43
43
  * Data attributes specific to the toggle tool
@@ -70,20 +70,39 @@ export const buildToggleItem = (context: ToggleDOMBuilderContext): ToggleBuildRe
70
70
  return { wrapper, arrowElement, contentElement };
71
71
  };
72
72
 
73
+ /**
74
+ * Options for building arrow element
75
+ */
76
+ export interface BuildArrowOptions {
77
+ /** Set contentEditable="false" on the arrow (used by Header to prevent caret entering arrow) */
78
+ contentEditableFalse?: boolean;
79
+ }
80
+
73
81
  /**
74
82
  * Build the arrow element for toggling open/closed state.
75
83
  *
76
84
  * @param isOpen - Whether the toggle is currently open
77
85
  * @param onArrowClick - Callback when arrow is clicked
86
+ * @param options - Optional configuration
78
87
  * @returns The arrow element
79
88
  */
80
- const buildArrow = (isOpen: boolean, onArrowClick: () => void): HTMLElement => {
81
- const arrow = document.createElement('div');
89
+ export const buildArrow = (
90
+ isOpen: boolean,
91
+ onArrowClick: () => void,
92
+ options: BuildArrowOptions = {}
93
+ ): HTMLElement => {
94
+ const arrow = document.createElement('span');
82
95
  arrow.className = ARROW_STYLES;
83
96
  arrow.setAttribute(TOGGLE_ATTR.toggleArrow, '');
84
97
  arrow.setAttribute('role', 'button');
85
- arrow.setAttribute('tabindex', '-1');
86
- arrow.setAttribute('aria-label', 'Toggle');
98
+ arrow.setAttribute('tabindex', '0');
99
+ arrow.setAttribute('aria-label', isOpen ? 'Collapse' : 'Expand');
100
+ arrow.setAttribute('aria-expanded', String(isOpen));
101
+
102
+ if (options.contentEditableFalse === true) {
103
+ arrow.contentEditable = 'false';
104
+ }
105
+
87
106
  arrow.innerHTML = ARROW_ICON;
88
107
 
89
108
  if (isOpen) {
@@ -95,6 +114,14 @@ const buildArrow = (isOpen: boolean, onArrowClick: () => void): HTMLElement => {
95
114
  onArrowClick();
96
115
  });
97
116
 
117
+ arrow.addEventListener('keydown', (event: KeyboardEvent) => {
118
+ if (event.key === 'Enter' || event.key === ' ') {
119
+ event.preventDefault();
120
+ event.stopPropagation();
121
+ onArrowClick();
122
+ }
123
+ });
124
+
98
125
  return arrow;
99
126
  };
100
127