@jackuait/blok 0.6.0-beta.0 → 0.6.0-beta.10

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/README.md +16 -169
  2. package/bin/blok.mjs +10 -0
  3. package/dist/blok.mjs +2 -2
  4. package/dist/chunks/{blok-Bu9S3SsR.mjs → blok-Buf0btS7.mjs} +2267 -2024
  5. package/dist/chunks/{i18next-loader-CKuXJ0Av.mjs → i18next-loader-CVf_ZfwA.mjs} +1 -1
  6. package/dist/chunks/{index-jtZaryNw.mjs → index-C6jsfLLp.mjs} +1 -1
  7. package/dist/chunks/{inline-tool-convert-CFjyrH30.mjs → inline-tool-convert-BKKEoOqB.mjs} +710 -570
  8. package/dist/chunks/{messages-C5b7hr_E.mjs → messages-1fC8IMyX.mjs} +16 -2
  9. package/dist/chunks/{messages-CQj2JU2j.mjs → messages-7QoX8DkW.mjs} +23 -9
  10. package/dist/{messages-LvFKBBPa.mjs → chunks/messages-7W4d0DwD.mjs} +15 -1
  11. package/dist/{messages-Bn253WWC.mjs → chunks/messages-9SihnaXQ.mjs} +14 -0
  12. package/dist/{messages-Bf6Y3_GI.mjs → chunks/messages-B1Aww8q7.mjs} +16 -2
  13. package/dist/{messages-pA5TvcAj.mjs → chunks/messages-BB5z9Uba.mjs} +14 -0
  14. package/dist/chunks/{messages-wdqp4610.mjs → messages-BC86qLvI.mjs} +17 -3
  15. package/dist/chunks/{messages-o24dK6CU.mjs → messages-BELRf6DU.mjs} +16 -2
  16. package/dist/chunks/{messages-CUZ1x1QD.mjs → messages-BFG6Wlgy.mjs} +16 -2
  17. package/dist/{messages-B5puUm7R.mjs → chunks/messages-BL0tXcDf.mjs} +15 -1
  18. package/dist/chunks/{messages-zS1AXZ0y.mjs → messages-BMXCuEKO.mjs} +19 -5
  19. package/dist/{messages-CyDU5lz9.mjs → chunks/messages-BMv4xwIr.mjs} +16 -2
  20. package/dist/chunks/{messages-BeUhMpsr.mjs → messages-BSbjsyHY.mjs} +25 -11
  21. package/dist/chunks/{messages-JGsXAReJ.mjs → messages-BU2nlrLK.mjs} +16 -2
  22. package/dist/chunks/{messages-srxrv8Yh.mjs → messages-BWF-zUpY.mjs} +17 -3
  23. package/dist/{messages-CXHd9SUK.mjs → chunks/messages-BYyy6Wqf.mjs} +14 -0
  24. package/dist/chunks/{messages-DOlC_Tty.mjs → messages-BdeLo0N9.mjs} +24 -10
  25. package/dist/chunks/{messages-B5jGUnOy.mjs → messages-Bmu_S7GM.mjs} +14 -0
  26. package/dist/chunks/{messages-BmKCChWZ.mjs → messages-BoJc_p1r.mjs} +14 -0
  27. package/dist/chunks/{messages-CvaqJFN-.mjs → messages-BogRq8lt.mjs} +15 -1
  28. package/dist/chunks/{messages-NP1myMGI.mjs → messages-BrPFGbM-.mjs} +14 -0
  29. package/dist/chunks/{messages-D00OjS2n.mjs → messages-C2htQ_3F.mjs} +24 -10
  30. package/dist/chunks/{messages-BiExzWJv.mjs → messages-C99mq906.mjs} +15 -1
  31. package/dist/chunks/{messages-CkFT2gle.mjs → messages-C9eaarcK.mjs} +20 -6
  32. package/dist/chunks/{messages-BrJHUxQL.mjs → messages-CJdUsQ-c.mjs} +15 -1
  33. package/dist/chunks/messages-CKI54h6O.mjs +62 -0
  34. package/dist/{messages-CrsJ1TEJ.mjs → chunks/messages-CLhcMlTc.mjs} +15 -1
  35. package/dist/{messages-CnvW8Slp.mjs → chunks/messages-CMkNSDTo.mjs} +17 -3
  36. package/dist/{messages-BlpqL8vG.mjs → chunks/messages-CQwpzUFp.mjs} +19 -5
  37. package/dist/chunks/{messages-Cu08aLS3.mjs → messages-CVw84KdI.mjs} +21 -7
  38. package/dist/chunks/{messages-B9Oba7sq.mjs → messages-CY8_RyFE.mjs} +15 -1
  39. package/dist/chunks/{messages-B5hdXZwA.mjs → messages-CZygwLwM.mjs} +15 -1
  40. package/dist/chunks/{messages-CVeWVKsV.mjs → messages-CnwibSvh.mjs} +14 -0
  41. package/dist/chunks/{messages-DbVquYKN.mjs → messages-CqWJcCbY.mjs} +14 -0
  42. package/dist/{messages-Dg92dXZ5.mjs → chunks/messages-CvGLfqmV.mjs} +14 -0
  43. package/dist/{messages-AHESHJm_.mjs → chunks/messages-CzTufCHu.mjs} +14 -0
  44. package/dist/chunks/{messages-Cj-t1bdy.mjs → messages-CznZadDf.mjs} +15 -1
  45. package/dist/chunks/{messages-DMQIHGRj.mjs → messages-D-ZtY5v0.mjs} +14 -0
  46. package/dist/chunks/{messages-rRSHQDCX.mjs → messages-D1Hv8XGo.mjs} +14 -0
  47. package/dist/chunks/{messages-0tDXLuyH.mjs → messages-D5C3J9qr.mjs} +15 -1
  48. package/dist/chunks/{messages-RvMHb2Ht.mjs → messages-D5iv1Kox.mjs} +16 -2
  49. package/dist/{messages-zSzDzXej.mjs → chunks/messages-DBRw-7Zc.mjs} +16 -2
  50. package/dist/chunks/{messages-CeCjVKMW.mjs → messages-DBn76jVV.mjs} +16 -2
  51. package/dist/chunks/{messages-C7I_AVH2.mjs → messages-DJDG55Vq.mjs} +16 -2
  52. package/dist/{messages-DDLgIPDF.mjs → chunks/messages-DLfR5bMd.mjs} +16 -2
  53. package/dist/{messages-CbhuIWRJ.mjs → chunks/messages-DT4dP5uK.mjs} +15 -1
  54. package/dist/chunks/{messages-B66ZSDCJ.mjs → messages-DhLKYm2j.mjs} +15 -1
  55. package/dist/{messages-BPqWKx5Z.mjs → chunks/messages-Diu6jAaR.mjs} +17 -3
  56. package/dist/{messages-BA0rcTCY.mjs → chunks/messages-DnIhyAJk.mjs} +18 -4
  57. package/dist/chunks/{messages-DV6shA9b.mjs → messages-DnXLrlHh.mjs} +14 -0
  58. package/dist/chunks/{messages-DcKOuncK.mjs → messages-DprmQg6V.mjs} +16 -2
  59. package/dist/{messages-CJoBtXU6.mjs → chunks/messages-DqM1LFg5.mjs} +14 -0
  60. package/dist/chunks/{messages-Cyi2AMmz.mjs → messages-DvFLX36Q.mjs} +25 -11
  61. package/dist/chunks/{messages-DnbbyJT3.mjs → messages-Dz9L52ol.mjs} +16 -2
  62. package/dist/chunks/{messages-GC2PhgV3.mjs → messages-Dzwxv9v1.mjs} +23 -9
  63. package/dist/chunks/{messages-Q4kc_ZtL.mjs → messages-JELdtT6E.mjs} +15 -1
  64. package/dist/{messages-DY94ykcE.mjs → chunks/messages-LPVfA-8K.mjs} +14 -0
  65. package/dist/{messages-Cr-RJ7YB.mjs → chunks/messages-O5tQus_0.mjs} +14 -0
  66. package/dist/chunks/{messages-CbMyJSzS.mjs → messages-Q7AO_FLv.mjs} +17 -3
  67. package/dist/chunks/{messages-2_xedlYw.mjs → messages-R3hUSvr3.mjs} +15 -1
  68. package/dist/{messages-CUy1vn-b.mjs → chunks/messages-Xq8UmkVs.mjs} +14 -0
  69. package/dist/chunks/{messages-BBJgd5jG.mjs → messages-Z9nEU2xK.mjs} +16 -2
  70. package/dist/chunks/{messages-Cm9aLHeX.mjs → messages-_ErNTNhk.mjs} +15 -1
  71. package/dist/chunks/{messages-JZUhXTuV.mjs → messages-_ncGrKHh.mjs} +16 -2
  72. package/dist/chunks/{messages-ftMcCEuO.mjs → messages-kep5wtm4.mjs} +15 -1
  73. package/dist/chunks/{messages-DteYq0rv.mjs → messages-uKX8WBaD.mjs} +16 -2
  74. package/dist/chunks/{messages-Bdv-IkfG.mjs → messages-w7v1GNaE.mjs} +15 -1
  75. package/dist/cli.mjs +50 -0
  76. package/dist/full.mjs +15 -15
  77. package/dist/locales.mjs +102 -88
  78. package/dist/{messages-C5b7hr_E.mjs → messages-1fC8IMyX.mjs} +16 -2
  79. package/dist/{messages-CQj2JU2j.mjs → messages-7QoX8DkW.mjs} +23 -9
  80. package/dist/{chunks/messages-LvFKBBPa.mjs → messages-7W4d0DwD.mjs} +15 -1
  81. package/dist/{chunks/messages-Bn253WWC.mjs → messages-9SihnaXQ.mjs} +14 -0
  82. package/dist/{chunks/messages-Bf6Y3_GI.mjs → messages-B1Aww8q7.mjs} +16 -2
  83. package/dist/{chunks/messages-pA5TvcAj.mjs → messages-BB5z9Uba.mjs} +14 -0
  84. package/dist/{messages-wdqp4610.mjs → messages-BC86qLvI.mjs} +17 -3
  85. package/dist/{messages-o24dK6CU.mjs → messages-BELRf6DU.mjs} +16 -2
  86. package/dist/{messages-CUZ1x1QD.mjs → messages-BFG6Wlgy.mjs} +16 -2
  87. package/dist/{chunks/messages-B5puUm7R.mjs → messages-BL0tXcDf.mjs} +15 -1
  88. package/dist/{messages-zS1AXZ0y.mjs → messages-BMXCuEKO.mjs} +19 -5
  89. package/dist/{chunks/messages-CyDU5lz9.mjs → messages-BMv4xwIr.mjs} +16 -2
  90. package/dist/{messages-BeUhMpsr.mjs → messages-BSbjsyHY.mjs} +25 -11
  91. package/dist/{messages-JGsXAReJ.mjs → messages-BU2nlrLK.mjs} +16 -2
  92. package/dist/{messages-srxrv8Yh.mjs → messages-BWF-zUpY.mjs} +17 -3
  93. package/dist/{chunks/messages-CXHd9SUK.mjs → messages-BYyy6Wqf.mjs} +14 -0
  94. package/dist/{messages-DOlC_Tty.mjs → messages-BdeLo0N9.mjs} +24 -10
  95. package/dist/{messages-B5jGUnOy.mjs → messages-Bmu_S7GM.mjs} +14 -0
  96. package/dist/{messages-BmKCChWZ.mjs → messages-BoJc_p1r.mjs} +14 -0
  97. package/dist/{messages-CvaqJFN-.mjs → messages-BogRq8lt.mjs} +15 -1
  98. package/dist/{messages-NP1myMGI.mjs → messages-BrPFGbM-.mjs} +14 -0
  99. package/dist/{messages-D00OjS2n.mjs → messages-C2htQ_3F.mjs} +24 -10
  100. package/dist/{messages-BiExzWJv.mjs → messages-C99mq906.mjs} +15 -1
  101. package/dist/{messages-CkFT2gle.mjs → messages-C9eaarcK.mjs} +20 -6
  102. package/dist/{messages-BrJHUxQL.mjs → messages-CJdUsQ-c.mjs} +15 -1
  103. package/dist/messages-CKI54h6O.mjs +62 -0
  104. package/dist/{chunks/messages-CrsJ1TEJ.mjs → messages-CLhcMlTc.mjs} +15 -1
  105. package/dist/{chunks/messages-CnvW8Slp.mjs → messages-CMkNSDTo.mjs} +17 -3
  106. package/dist/{chunks/messages-BlpqL8vG.mjs → messages-CQwpzUFp.mjs} +19 -5
  107. package/dist/{messages-Cu08aLS3.mjs → messages-CVw84KdI.mjs} +21 -7
  108. package/dist/{messages-B9Oba7sq.mjs → messages-CY8_RyFE.mjs} +15 -1
  109. package/dist/{messages-B5hdXZwA.mjs → messages-CZygwLwM.mjs} +15 -1
  110. package/dist/{messages-CVeWVKsV.mjs → messages-CnwibSvh.mjs} +14 -0
  111. package/dist/{messages-DbVquYKN.mjs → messages-CqWJcCbY.mjs} +14 -0
  112. package/dist/{chunks/messages-Dg92dXZ5.mjs → messages-CvGLfqmV.mjs} +14 -0
  113. package/dist/{chunks/messages-AHESHJm_.mjs → messages-CzTufCHu.mjs} +14 -0
  114. package/dist/{messages-Cj-t1bdy.mjs → messages-CznZadDf.mjs} +15 -1
  115. package/dist/{messages-DMQIHGRj.mjs → messages-D-ZtY5v0.mjs} +14 -0
  116. package/dist/{messages-rRSHQDCX.mjs → messages-D1Hv8XGo.mjs} +14 -0
  117. package/dist/{messages-0tDXLuyH.mjs → messages-D5C3J9qr.mjs} +15 -1
  118. package/dist/{messages-RvMHb2Ht.mjs → messages-D5iv1Kox.mjs} +16 -2
  119. package/dist/{chunks/messages-zSzDzXej.mjs → messages-DBRw-7Zc.mjs} +16 -2
  120. package/dist/{messages-CeCjVKMW.mjs → messages-DBn76jVV.mjs} +16 -2
  121. package/dist/{messages-C7I_AVH2.mjs → messages-DJDG55Vq.mjs} +16 -2
  122. package/dist/{chunks/messages-DDLgIPDF.mjs → messages-DLfR5bMd.mjs} +16 -2
  123. package/dist/{chunks/messages-CbhuIWRJ.mjs → messages-DT4dP5uK.mjs} +15 -1
  124. package/dist/{messages-B66ZSDCJ.mjs → messages-DhLKYm2j.mjs} +15 -1
  125. package/dist/{chunks/messages-BPqWKx5Z.mjs → messages-Diu6jAaR.mjs} +17 -3
  126. package/dist/{chunks/messages-BA0rcTCY.mjs → messages-DnIhyAJk.mjs} +18 -4
  127. package/dist/{messages-DV6shA9b.mjs → messages-DnXLrlHh.mjs} +14 -0
  128. package/dist/{messages-DcKOuncK.mjs → messages-DprmQg6V.mjs} +16 -2
  129. package/dist/{chunks/messages-CJoBtXU6.mjs → messages-DqM1LFg5.mjs} +14 -0
  130. package/dist/{messages-Cyi2AMmz.mjs → messages-DvFLX36Q.mjs} +25 -11
  131. package/dist/{messages-DnbbyJT3.mjs → messages-Dz9L52ol.mjs} +16 -2
  132. package/dist/{messages-GC2PhgV3.mjs → messages-Dzwxv9v1.mjs} +23 -9
  133. package/dist/{messages-Q4kc_ZtL.mjs → messages-JELdtT6E.mjs} +15 -1
  134. package/dist/{chunks/messages-DY94ykcE.mjs → messages-LPVfA-8K.mjs} +14 -0
  135. package/dist/{chunks/messages-Cr-RJ7YB.mjs → messages-O5tQus_0.mjs} +14 -0
  136. package/dist/{messages-CbMyJSzS.mjs → messages-Q7AO_FLv.mjs} +17 -3
  137. package/dist/{messages-2_xedlYw.mjs → messages-R3hUSvr3.mjs} +15 -1
  138. package/dist/{chunks/messages-CUy1vn-b.mjs → messages-Xq8UmkVs.mjs} +14 -0
  139. package/dist/{messages-BBJgd5jG.mjs → messages-Z9nEU2xK.mjs} +16 -2
  140. package/dist/{messages-Cm9aLHeX.mjs → messages-_ErNTNhk.mjs} +15 -1
  141. package/dist/{messages-JZUhXTuV.mjs → messages-_ncGrKHh.mjs} +16 -2
  142. package/dist/{messages-ftMcCEuO.mjs → messages-kep5wtm4.mjs} +15 -1
  143. package/dist/{messages-DteYq0rv.mjs → messages-uKX8WBaD.mjs} +16 -2
  144. package/dist/{messages-Bdv-IkfG.mjs → messages-w7v1GNaE.mjs} +15 -1
  145. package/dist/tools.mjs +2005 -1267
  146. package/dist/vendor.LICENSE.txt +1 -1
  147. package/package.json +15 -14
  148. package/src/cli/commands/migration.ts +16 -0
  149. package/src/cli/commands/migrationContent.ts +6 -0
  150. package/src/cli/index.ts +47 -0
  151. package/src/cli/utils/output.ts +10 -0
  152. package/src/components/block-tunes/block-tune-delete.ts +3 -2
  153. package/src/components/blocks.ts +23 -6
  154. package/src/components/constants/data-attributes.ts +2 -0
  155. package/src/components/i18n/locales/am/messages.json +15 -1
  156. package/src/components/i18n/locales/ar/messages.json +14 -0
  157. package/src/components/i18n/locales/az/messages.json +14 -0
  158. package/src/components/i18n/locales/bg/messages.json +14 -0
  159. package/src/components/i18n/locales/bn/messages.json +25 -11
  160. package/src/components/i18n/locales/bs/messages.json +15 -1
  161. package/src/components/i18n/locales/cs/messages.json +14 -0
  162. package/src/components/i18n/locales/da/messages.json +14 -0
  163. package/src/components/i18n/locales/de/messages.json +14 -0
  164. package/src/components/i18n/locales/dv/messages.json +15 -1
  165. package/src/components/i18n/locales/el/messages.json +15 -1
  166. package/src/components/i18n/locales/en/messages.json +14 -0
  167. package/src/components/i18n/locales/es/messages.json +14 -0
  168. package/src/components/i18n/locales/et/messages.json +14 -0
  169. package/src/components/i18n/locales/fa/messages.json +15 -1
  170. package/src/components/i18n/locales/fi/messages.json +15 -1
  171. package/src/components/i18n/locales/fil/messages.json +20 -6
  172. package/src/components/i18n/locales/fr/messages.json +15 -1
  173. package/src/components/i18n/locales/gu/messages.json +15 -1
  174. package/src/components/i18n/locales/he/messages.json +14 -0
  175. package/src/components/i18n/locales/hi/messages.json +23 -9
  176. package/src/components/i18n/locales/hr/messages.json +14 -0
  177. package/src/components/i18n/locales/hu/messages.json +14 -0
  178. package/src/components/i18n/locales/hy/messages.json +16 -2
  179. package/src/components/i18n/locales/id/messages.json +19 -5
  180. package/src/components/i18n/locales/it/messages.json +14 -0
  181. package/src/components/i18n/locales/ja/messages.json +14 -0
  182. package/src/components/i18n/locales/ka/messages.json +15 -1
  183. package/src/components/i18n/locales/km/messages.json +16 -2
  184. package/src/components/i18n/locales/kn/messages.json +16 -2
  185. package/src/components/i18n/locales/ko/messages.json +14 -0
  186. package/src/components/i18n/locales/ku/messages.json +16 -2
  187. package/src/components/i18n/locales/lo/messages.json +15 -1
  188. package/src/components/i18n/locales/lt/messages.json +15 -1
  189. package/src/components/i18n/locales/lv/messages.json +15 -1
  190. package/src/components/i18n/locales/mk/messages.json +16 -2
  191. package/src/components/i18n/locales/ml/messages.json +16 -2
  192. package/src/components/i18n/locales/mn/messages.json +16 -2
  193. package/src/components/i18n/locales/mr/messages.json +24 -10
  194. package/src/components/i18n/locales/ms/messages.json +17 -3
  195. package/src/components/i18n/locales/my/messages.json +16 -2
  196. package/src/components/i18n/locales/ne/messages.json +24 -10
  197. package/src/components/i18n/locales/nl/messages.json +15 -1
  198. package/src/components/i18n/locales/no/messages.json +16 -2
  199. package/src/components/i18n/locales/pa/messages.json +15 -1
  200. package/src/components/i18n/locales/pl/messages.json +14 -0
  201. package/src/components/i18n/locales/ps/messages.json +17 -3
  202. package/src/components/i18n/locales/pt/messages.json +14 -0
  203. package/src/components/i18n/locales/ro/messages.json +15 -1
  204. package/src/components/i18n/locales/ru/messages.json +14 -0
  205. package/src/components/i18n/locales/sd/messages.json +16 -2
  206. package/src/components/i18n/locales/si/messages.json +23 -9
  207. package/src/components/i18n/locales/sk/messages.json +15 -1
  208. package/src/components/i18n/locales/sl/messages.json +16 -2
  209. package/src/components/i18n/locales/sq/messages.json +16 -2
  210. package/src/components/i18n/locales/sr/messages.json +16 -2
  211. package/src/components/i18n/locales/sv/messages.json +16 -2
  212. package/src/components/i18n/locales/sw/messages.json +16 -2
  213. package/src/components/i18n/locales/ta/messages.json +21 -7
  214. package/src/components/i18n/locales/te/messages.json +40 -26
  215. package/src/components/i18n/locales/th/messages.json +19 -5
  216. package/src/components/i18n/locales/tr/messages.json +15 -1
  217. package/src/components/i18n/locales/ug/messages.json +16 -2
  218. package/src/components/i18n/locales/uk/messages.json +15 -1
  219. package/src/components/i18n/locales/ur/messages.json +15 -1
  220. package/src/components/i18n/locales/vi/messages.json +25 -11
  221. package/src/components/i18n/locales/yi/messages.json +16 -2
  222. package/src/components/i18n/locales/zh/messages.json +15 -1
  223. package/src/components/icons/index.ts +104 -83
  224. package/src/components/modules/api/blocks.ts +35 -2
  225. package/src/components/modules/api/history.ts +64 -0
  226. package/src/components/modules/api/index.ts +2 -0
  227. package/src/components/modules/api/readonly.ts +11 -1
  228. package/src/components/modules/blockEvents/composers/markdownShortcuts.ts +12 -1
  229. package/src/components/modules/blockManager/blockManager.ts +7 -0
  230. package/src/components/modules/blockManager/operations.ts +3 -2
  231. package/src/components/modules/blockManager/types.ts +3 -1
  232. package/src/components/modules/blockManager/yjs-sync.ts +12 -2
  233. package/src/components/modules/index.ts +3 -0
  234. package/src/components/modules/normalizeInlineImages.ts +263 -0
  235. package/src/components/modules/readonly.ts +11 -0
  236. package/src/components/modules/rectangleSelection.ts +19 -3
  237. package/src/components/modules/saver.ts +7 -3
  238. package/src/components/modules/toolbar/blockSettings.ts +3 -3
  239. package/src/components/modules/toolbar/index.ts +72 -14
  240. package/src/components/modules/toolbar/plus-button.ts +24 -3
  241. package/src/components/modules/toolbar/settings-toggler.ts +3 -5
  242. package/src/components/modules/ui.ts +46 -68
  243. package/src/components/modules/uiControllers/controllers/blockHover.ts +49 -61
  244. package/src/components/modules/uiControllers/controllers/keyboard.ts +17 -11
  245. package/src/components/modules/uiControllers/handlers/click.ts +0 -12
  246. package/src/components/modules/yjs/index.ts +23 -0
  247. package/src/components/ui/toolbox.ts +41 -6
  248. package/src/components/utils/placeholder.ts +16 -0
  249. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +2 -1
  250. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +6 -1
  251. package/src/components/utils/popover/index.ts +1 -0
  252. package/src/components/utils/popover/popover-abstract.ts +11 -1
  253. package/src/components/utils/popover/popover-desktop.ts +27 -8
  254. package/src/components/utils/popover/popover-registry.ts +188 -0
  255. package/src/components/utils/sanitizer.ts +23 -2
  256. package/src/components/utils/tooltip.ts +2 -24
  257. package/src/styles/main.css +22 -0
  258. package/src/tools/paragraph/index.ts +12 -4
  259. package/src/tools/table/data-normalizer.ts +1 -0
  260. package/src/tools/table/index.ts +283 -346
  261. package/src/tools/table/table-add-controls.ts +353 -47
  262. package/src/tools/table/table-cell-blocks.ts +95 -7
  263. package/src/tools/table/table-cell-selection.ts +648 -0
  264. package/src/tools/table/table-core.ts +21 -32
  265. package/src/tools/table/table-grip-visuals.ts +96 -0
  266. package/src/tools/table/table-heading-toggle.ts +127 -0
  267. package/src/tools/table/table-operations.ts +475 -0
  268. package/src/tools/table/table-resize.ts +27 -6
  269. package/src/tools/table/table-restrictions.ts +64 -0
  270. package/src/tools/table/table-row-col-action-handler.ts +190 -0
  271. package/src/tools/table/table-row-col-controls.ts +265 -211
  272. package/src/tools/table/table-row-col-drag.ts +4 -4
  273. package/src/tools/table/table-row-col-popover.ts +225 -0
  274. package/src/tools/table/types.ts +4 -0
  275. package/src/types-internal/blok-modules.d.ts +2 -0
  276. package/types/api/blocks.d.ts +8 -0
  277. package/types/api/history.d.ts +33 -0
  278. package/types/api/index.d.ts +1 -0
  279. package/types/api/readonly.d.ts +12 -2
  280. package/types/index.d.ts +10 -0
  281. package/types/tools/table.d.ts +67 -0
  282. package/types/tools-entry.d.ts +4 -0
  283. package/types/utils/popover/popover-item.d.ts +6 -0
  284. package/types/utils/popover/popover.d.ts +7 -0
  285. package/dist/chunks/messages-CySyfkMU.mjs +0 -48
  286. package/dist/messages-CySyfkMU.mjs +0 -48
@@ -0,0 +1,648 @@
1
+ import type { I18n } from '../../../types/api';
2
+ import { IconCross } from '../../components/icons';
3
+ import { PopoverDesktop } from '../../components/utils/popover';
4
+ import { twMerge } from '../../components/utils/tw';
5
+
6
+ import { CELL_ATTR, ROW_ATTR } from './table-core';
7
+ import { createGripDotsSvg } from './table-grip-visuals';
8
+
9
+ import { PopoverEvent } from '@/types/utils/popover/popover-event';
10
+ import type { PopoverItemParams } from '@/types/utils/popover/popover-item';
11
+
12
+ const SELECTED_ATTR = 'data-blok-table-cell-selected';
13
+
14
+ const SELECTION_BORDER = '2px solid #3b82f6';
15
+
16
+ const PILL_ATTR = 'data-blok-table-selection-pill';
17
+ const PILL_WIDTH = 16;
18
+ const PILL_HEIGHT = 20;
19
+ const PILL_IDLE_SIZE = 4;
20
+
21
+ const PILL_CLASSES = [
22
+ 'absolute',
23
+ 'z-[3]',
24
+ 'rounded',
25
+ 'select-none',
26
+ 'transition-[opacity,background-color,width]',
27
+ 'duration-150',
28
+ 'flex',
29
+ 'items-center',
30
+ 'justify-center',
31
+ 'overflow-hidden',
32
+ 'cursor-pointer',
33
+ 'bg-blue-500',
34
+ ];
35
+
36
+ interface CellCoord {
37
+ row: number;
38
+ col: number;
39
+ }
40
+
41
+ /**
42
+ * Check if a grip drag or resize is in progress by testing for known drag indicators.
43
+ * Returns true if the grid has an active drag ghost or user-select is disabled.
44
+ */
45
+ const isOtherInteractionActive = (grid: HTMLElement): boolean => {
46
+ return grid.style.userSelect === 'none';
47
+ };
48
+
49
+ /**
50
+ * Handles rectangular cell selection via click-and-drag.
51
+ * Selection starts when a pointer drag crosses from one cell into another.
52
+ * Selected cells are highlighted with a blue outer border around the selection rectangle
53
+ * using an absolutely-positioned overlay div.
54
+ */
55
+ interface CellSelectionOptions {
56
+ grid: HTMLElement;
57
+ rectangleSelection?: { cancelActiveSelection: () => void };
58
+ onSelectionActiveChange?: (hasSelection: boolean) => void;
59
+ onClearContent?: (cells: HTMLElement[]) => void;
60
+ i18n: I18n;
61
+ }
62
+
63
+ export class TableCellSelection {
64
+ private grid: HTMLElement;
65
+ private rectangleSelection?: { cancelActiveSelection: () => void };
66
+ private onSelectionActiveChange: ((hasSelection: boolean) => void) | undefined;
67
+ private onClearContent: ((cells: HTMLElement[]) => void) | undefined;
68
+ private i18n: I18n;
69
+ private anchorCell: CellCoord | null = null;
70
+ private extentCell: CellCoord | null = null;
71
+ private isSelecting = false;
72
+ private hasSelection = false;
73
+ private selectedCells: HTMLElement[] = [];
74
+ private overlay: HTMLElement | null = null;
75
+ private pill: HTMLElement | null = null;
76
+ private pillPopover: PopoverDesktop | null = null;
77
+
78
+ private boundPointerDown: (e: PointerEvent) => void;
79
+ private boundPointerMove: (e: PointerEvent) => void;
80
+ private boundPointerUp: () => void;
81
+ private boundClearSelection: (e: PointerEvent) => void;
82
+ private boundCancelRectangle: (e: MouseEvent) => void;
83
+ private boundKeyDown: (e: KeyboardEvent) => void;
84
+
85
+ constructor(options: CellSelectionOptions) {
86
+ this.grid = options.grid;
87
+ this.rectangleSelection = options.rectangleSelection;
88
+ this.onSelectionActiveChange = options.onSelectionActiveChange;
89
+ this.onClearContent = options.onClearContent;
90
+ this.i18n = options.i18n;
91
+ this.grid.style.position = 'relative';
92
+
93
+ this.boundPointerDown = this.handlePointerDown.bind(this);
94
+ this.boundPointerMove = this.handlePointerMove.bind(this);
95
+ this.boundPointerUp = this.handlePointerUp.bind(this);
96
+ this.boundClearSelection = this.handleClearSelection.bind(this);
97
+ this.boundCancelRectangle = this.handleCancelRectangle.bind(this);
98
+ this.boundKeyDown = this.handleKeyDown.bind(this);
99
+
100
+ this.grid.addEventListener('pointerdown', this.boundPointerDown);
101
+ document.addEventListener('keydown', this.boundKeyDown);
102
+ }
103
+
104
+ public destroy(): void {
105
+ this.destroyPillPopover();
106
+ this.clearSelection();
107
+ this.grid.removeEventListener('pointerdown', this.boundPointerDown);
108
+ document.removeEventListener('pointermove', this.boundPointerMove);
109
+ document.removeEventListener('pointerup', this.boundPointerUp);
110
+ document.removeEventListener('pointerdown', this.boundClearSelection);
111
+ document.removeEventListener('mousemove', this.boundCancelRectangle, true);
112
+ document.removeEventListener('keydown', this.boundKeyDown);
113
+ }
114
+
115
+ /**
116
+ * Programmatically select an entire row.
117
+ */
118
+ public selectRow(rowIndex: number): void {
119
+ const rows = this.grid.querySelectorAll(`[${ROW_ATTR}]`);
120
+ const colCount = rows[0]?.querySelectorAll(`[${CELL_ATTR}]`).length ?? 0;
121
+
122
+ if (colCount === 0) {
123
+ return;
124
+ }
125
+
126
+ this.showProgrammaticSelection(rowIndex, 0, rowIndex, colCount - 1);
127
+ }
128
+
129
+ /**
130
+ * Programmatically select an entire column.
131
+ */
132
+ public selectColumn(colIndex: number): void {
133
+ const rows = this.grid.querySelectorAll(`[${ROW_ATTR}]`);
134
+ const rowCount = rows.length;
135
+
136
+ if (rowCount === 0) {
137
+ return;
138
+ }
139
+
140
+ this.showProgrammaticSelection(0, colIndex, rowCount - 1, colIndex);
141
+ }
142
+
143
+ /**
144
+ * Clear any active programmatic or drag selection.
145
+ */
146
+ public clearActiveSelection(): void {
147
+ this.clearSelection();
148
+ }
149
+
150
+ private handlePointerDown(e: PointerEvent): void {
151
+ // Don't interfere with grip drags, resize, or add-button drags
152
+ if (isOtherInteractionActive(this.grid)) {
153
+ return;
154
+ }
155
+
156
+ // Only respond to primary button
157
+ if (e.button !== 0) {
158
+ return;
159
+ }
160
+
161
+ // Don't start selection from grip elements
162
+ const target = e.target as HTMLElement;
163
+
164
+ if (target.closest('[data-blok-table-grip]') || target.closest('[data-blok-table-resize]') || target.closest(`[${PILL_ATTR}]`)) {
165
+ return;
166
+ }
167
+
168
+ const cell = this.resolveCellCoord(target);
169
+
170
+ if (!cell) {
171
+ return;
172
+ }
173
+
174
+ // If there's an existing selection, clear it first
175
+ if (this.hasSelection) {
176
+ this.clearSelection();
177
+ }
178
+
179
+ this.anchorCell = cell;
180
+ this.isSelecting = false;
181
+
182
+ // Listen to mousemove in capture phase to cancel RectangleSelection before it runs
183
+ document.addEventListener('mousemove', this.boundCancelRectangle, true);
184
+ document.addEventListener('pointermove', this.boundPointerMove);
185
+ document.addEventListener('pointerup', this.boundPointerUp);
186
+ }
187
+
188
+ private handlePointerMove(e: PointerEvent): void {
189
+ if (!this.anchorCell) {
190
+ return;
191
+ }
192
+
193
+ const target = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
194
+
195
+ if (!target) {
196
+ return;
197
+ }
198
+
199
+ const cell = this.resolveCellCoord(target);
200
+
201
+ if (!cell) {
202
+ // Pointer left the grid — clamp to edge
203
+ this.clampExtentToEdge(e);
204
+
205
+ return;
206
+ }
207
+
208
+ // Still in the same cell as anchor — don't start selection yet
209
+ if (!this.isSelecting && cell.row === this.anchorCell.row && cell.col === this.anchorCell.col) {
210
+ return;
211
+ }
212
+
213
+ // Crossed into a different cell — start selection
214
+ if (!this.isSelecting) {
215
+ this.isSelecting = true;
216
+ this.onSelectionActiveChange?.(true);
217
+
218
+ // Clear native text selection
219
+ window.getSelection()?.removeAllRanges();
220
+ this.grid.style.userSelect = 'none';
221
+ }
222
+
223
+ // Update extent and repaint
224
+ if (!this.extentCell || this.extentCell.row !== cell.row || this.extentCell.col !== cell.col) {
225
+ this.extentCell = cell;
226
+ this.paintSelection();
227
+ }
228
+ }
229
+
230
+ private handleCancelRectangle(_e: MouseEvent): void {
231
+ // Cancel RectangleSelection in capture phase, before it processes the event
232
+ if (!this.rectangleSelection) {
233
+ return;
234
+ }
235
+
236
+ this.rectangleSelection.cancelActiveSelection();
237
+
238
+ // Also directly hide the overlay since cancelActiveSelection() doesn't work when called repeatedly
239
+ const overlay = document.querySelector('[data-blok-overlay-rectangle]');
240
+
241
+ if (!overlay) {
242
+ return;
243
+ }
244
+
245
+ const overlayElement = overlay as HTMLElement;
246
+
247
+ overlayElement.style.display = 'none';
248
+ }
249
+
250
+ private handlePointerUp(): void {
251
+ document.removeEventListener('mousemove', this.boundCancelRectangle, true);
252
+ document.removeEventListener('pointermove', this.boundPointerMove);
253
+ document.removeEventListener('pointerup', this.boundPointerUp);
254
+
255
+ if (this.isSelecting) {
256
+ this.grid.style.userSelect = '';
257
+ this.hasSelection = true;
258
+
259
+ // Listen for next pointerdown anywhere to clear selection
260
+ requestAnimationFrame(() => {
261
+ document.addEventListener('pointerdown', this.boundClearSelection);
262
+ });
263
+ }
264
+
265
+ this.isSelecting = false;
266
+ this.anchorCell = null;
267
+ this.extentCell = null;
268
+ }
269
+
270
+ private handleClearSelection(e: PointerEvent): void {
271
+ const target = e.target;
272
+
273
+ if (target instanceof HTMLElement && target.closest(`[${PILL_ATTR}]`)) {
274
+ return;
275
+ }
276
+
277
+ // Don't clear while the pill popover is open — the user may be
278
+ // clicking a popover item whose pointerdown bubbles to the document.
279
+ if (this.pillPopover !== null) {
280
+ return;
281
+ }
282
+
283
+ document.removeEventListener('pointerdown', this.boundClearSelection);
284
+ this.clearSelection();
285
+ }
286
+
287
+ private handleKeyDown(e: KeyboardEvent): void {
288
+ // Only trigger when selection is active
289
+ if (!this.hasSelection) {
290
+ return;
291
+ }
292
+
293
+ // Check for Delete or Backspace
294
+ if (e.key !== 'Delete' && e.key !== 'Backspace') {
295
+ return;
296
+ }
297
+
298
+ // Prevent default behavior
299
+ e.preventDefault();
300
+
301
+ // Clear content and dismiss selection
302
+ this.onClearContent?.([...this.selectedCells]);
303
+ this.clearSelection();
304
+ }
305
+
306
+ private clearSelection(): void {
307
+ const hadSelection = this.hasSelection;
308
+
309
+ this.restoreModifiedCells();
310
+ this.hasSelection = false;
311
+
312
+ if (hadSelection) {
313
+ this.onSelectionActiveChange?.(false);
314
+ }
315
+ }
316
+
317
+ private restoreModifiedCells(): void {
318
+ this.destroyPillPopover();
319
+
320
+ this.selectedCells.forEach(cell => {
321
+ cell.removeAttribute(SELECTED_ATTR);
322
+ });
323
+
324
+ if (this.pill) {
325
+ this.pill.remove();
326
+ this.pill = null;
327
+ }
328
+
329
+ if (this.overlay) {
330
+ this.overlay.remove();
331
+ this.overlay = null;
332
+ }
333
+
334
+ this.selectedCells = [];
335
+ }
336
+
337
+ private showProgrammaticSelection(
338
+ fromRow: number,
339
+ fromCol: number,
340
+ toRow: number,
341
+ toCol: number,
342
+ ): void {
343
+ this.clearSelection();
344
+ this.anchorCell = { row: fromRow, col: fromCol };
345
+ this.extentCell = { row: toRow, col: toCol };
346
+ this.paintSelection();
347
+ this.hasSelection = true;
348
+ this.onSelectionActiveChange?.(true);
349
+ this.anchorCell = null;
350
+ this.extentCell = null;
351
+
352
+ // Listen for next pointerdown anywhere to clear the programmatic selection
353
+ requestAnimationFrame(() => {
354
+ document.addEventListener('pointerdown', this.boundClearSelection);
355
+ });
356
+ }
357
+
358
+ private paintSelection(): void {
359
+ if (!this.anchorCell || !this.extentCell) {
360
+ return;
361
+ }
362
+
363
+ // Clear previous cell markers
364
+ this.selectedCells.forEach(cell => {
365
+ cell.removeAttribute(SELECTED_ATTR);
366
+ });
367
+ this.selectedCells = [];
368
+
369
+ // Compute rectangle bounds
370
+ const minRow = Math.min(this.anchorCell.row, this.extentCell.row);
371
+ const maxRow = Math.max(this.anchorCell.row, this.extentCell.row);
372
+ const minCol = Math.min(this.anchorCell.col, this.extentCell.col);
373
+ const maxCol = Math.max(this.anchorCell.col, this.extentCell.col);
374
+
375
+ const rows = this.grid.querySelectorAll(`[${ROW_ATTR}]`);
376
+
377
+ // Mark selected cells
378
+ this.selectedCells = this.collectCellsInRange(rows, minRow, maxRow, minCol, maxCol);
379
+ this.selectedCells.forEach(cell => {
380
+ cell.setAttribute(SELECTED_ATTR, '');
381
+ });
382
+
383
+ // Calculate overlay position from bounding rects of corner cells
384
+ const firstCell = rows[minRow]?.querySelectorAll(`[${CELL_ATTR}]`)[minCol] as HTMLElement | undefined;
385
+ const lastCell = rows[maxRow]?.querySelectorAll(`[${CELL_ATTR}]`)[maxCol] as HTMLElement | undefined;
386
+
387
+ if (!firstCell || !lastCell) {
388
+ return;
389
+ }
390
+
391
+ const gridRect = this.grid.getBoundingClientRect();
392
+ const firstRect = firstCell.getBoundingClientRect();
393
+ const lastRect = lastCell.getBoundingClientRect();
394
+
395
+ // getBoundingClientRect() measures from the border-box edge, but
396
+ // position:absolute offsets from the padding-box edge. Subtract
397
+ // grid border widths to align with cell edges.
398
+ const gridStyle = getComputedStyle(this.grid);
399
+ const borderTop = parseFloat(gridStyle.borderTopWidth) || 0;
400
+ const borderLeft = parseFloat(gridStyle.borderLeftWidth) || 0;
401
+
402
+ const width = lastRect.right - firstRect.left + 1;
403
+ const height = lastRect.bottom - firstRect.top + 1;
404
+
405
+ // Extend overlay 1px outward to cover adjacent borders:
406
+ // grid border-top/border-left at row 0/col 0, or the previous
407
+ // row's border-bottom / previous column's border-right otherwise.
408
+ const top = firstRect.top - gridRect.top - borderTop - 1;
409
+ const left = firstRect.left - gridRect.left - borderLeft - 1;
410
+
411
+ // Create overlay once, reuse on subsequent paints
412
+ if (!this.overlay) {
413
+ this.overlay = document.createElement('div');
414
+ this.overlay.setAttribute('data-blok-table-selection-overlay', '');
415
+ this.overlay.style.position = 'absolute';
416
+ this.overlay.style.border = SELECTION_BORDER;
417
+ this.overlay.style.pointerEvents = 'none';
418
+ this.overlay.style.boxSizing = 'border-box';
419
+ this.overlay.style.borderRadius = '2px';
420
+ this.grid.appendChild(this.overlay);
421
+ }
422
+
423
+ this.overlay.style.top = `${top}px`;
424
+ this.overlay.style.left = `${left}px`;
425
+ this.overlay.style.width = `${width}px`;
426
+ this.overlay.style.height = `${height}px`;
427
+
428
+ // Create pill once, reuse on subsequent paints
429
+ if (!this.pill) {
430
+ this.pill = this.createPill();
431
+ this.grid.appendChild(this.pill);
432
+ }
433
+
434
+ // Position at center of the 2px right border; translate(-50%,-50%) handles centering
435
+ this.pill.style.left = `${left + width - 1}px`;
436
+ this.pill.style.top = `${top + height / 2}px`;
437
+ }
438
+
439
+ private createPill(): HTMLElement {
440
+ const pill = document.createElement('div');
441
+
442
+ pill.setAttribute(PILL_ATTR, '');
443
+ pill.setAttribute('contenteditable', 'false');
444
+ pill.className = twMerge(PILL_CLASSES);
445
+ pill.style.width = `${PILL_IDLE_SIZE}px`;
446
+ pill.style.height = `${PILL_HEIGHT}px`;
447
+ pill.style.pointerEvents = 'auto';
448
+ pill.style.transform = 'translate(-50%, -50%)';
449
+ pill.style.outline = '2px solid white';
450
+
451
+ const svg = createGripDotsSvg('vertical');
452
+
453
+ svg.classList.remove('text-gray-400');
454
+ svg.classList.add('text-white');
455
+ pill.appendChild(svg);
456
+
457
+ pill.addEventListener('mouseenter', () => {
458
+ if (this.pillPopover === null) {
459
+ this.expandPill();
460
+ }
461
+ });
462
+ pill.addEventListener('mouseleave', () => {
463
+ if (this.pillPopover === null) {
464
+ this.collapsePill();
465
+ }
466
+ });
467
+ pill.addEventListener('pointerdown', (e) => {
468
+ e.stopPropagation();
469
+ e.preventDefault();
470
+ this.openPillPopover();
471
+ });
472
+
473
+ return pill;
474
+ }
475
+
476
+ private openPillPopover(): void {
477
+ this.destroyPillPopover();
478
+
479
+ if (!this.pill) {
480
+ return;
481
+ }
482
+
483
+ this.expandPill();
484
+
485
+ const items: PopoverItemParams[] = [
486
+ {
487
+ icon: IconCross,
488
+ title: this.i18n.t('tools.table.clearSelection'),
489
+ closeOnActivate: true,
490
+ onActivate: (): void => {
491
+ this.onClearContent?.([...this.selectedCells]);
492
+ this.clearSelection();
493
+ },
494
+ },
495
+ ];
496
+
497
+ this.pillPopover = new PopoverDesktop({
498
+ items,
499
+ trigger: this.pill,
500
+ flippable: true,
501
+ });
502
+
503
+ this.pillPopover.on(PopoverEvent.Closed, () => {
504
+ if (this.pillPopover === null) {
505
+ return;
506
+ }
507
+
508
+ this.destroyPillPopover();
509
+
510
+ this.collapsePill();
511
+ });
512
+
513
+ this.pillPopover.show();
514
+ }
515
+
516
+ private expandPill(): void {
517
+ if (!this.pill) {
518
+ return;
519
+ }
520
+
521
+ this.pill.style.width = `${PILL_WIDTH}px`;
522
+
523
+ const svg = this.pill.querySelector('svg');
524
+
525
+ if (svg) {
526
+ svg.classList.remove('opacity-0');
527
+ svg.classList.add('opacity-100');
528
+ }
529
+ }
530
+
531
+ private collapsePill(): void {
532
+ if (!this.pill) {
533
+ return;
534
+ }
535
+
536
+ this.pill.style.width = `${PILL_IDLE_SIZE}px`;
537
+
538
+ const svg = this.pill.querySelector('svg');
539
+
540
+ if (svg) {
541
+ svg.classList.add('opacity-0');
542
+ svg.classList.remove('opacity-100');
543
+ }
544
+ }
545
+
546
+ private destroyPillPopover(): void {
547
+ if (this.pillPopover !== null) {
548
+ const popover = this.pillPopover;
549
+
550
+ this.pillPopover = null;
551
+ popover.destroy();
552
+ }
553
+ }
554
+
555
+ private resolveCellCoord(target: HTMLElement): CellCoord | null {
556
+ const cell = target.closest<HTMLElement>(`[${CELL_ATTR}]`);
557
+
558
+ if (!cell) {
559
+ return null;
560
+ }
561
+
562
+ const row = cell.closest<HTMLElement>(`[${ROW_ATTR}]`);
563
+
564
+ if (!row) {
565
+ return null;
566
+ }
567
+
568
+ // Verify cell is within our grid
569
+ if (!this.grid.contains(row)) {
570
+ return null;
571
+ }
572
+
573
+ const rows = Array.from(this.grid.querySelectorAll(`[${ROW_ATTR}]`));
574
+ const rowIndex = rows.indexOf(row);
575
+
576
+ if (rowIndex < 0) {
577
+ return null;
578
+ }
579
+
580
+ const cells = Array.from(row.querySelectorAll(`[${CELL_ATTR}]`));
581
+ const colIndex = cells.indexOf(cell);
582
+
583
+ if (colIndex < 0) {
584
+ return null;
585
+ }
586
+
587
+ return { row: rowIndex, col: colIndex };
588
+ }
589
+
590
+ private clampExtentToEdge(e: PointerEvent): void {
591
+ if (!this.anchorCell || !this.isSelecting) {
592
+ return;
593
+ }
594
+
595
+ const gridRect = this.grid.getBoundingClientRect();
596
+ const rows = this.grid.querySelectorAll(`[${ROW_ATTR}]`);
597
+ const rowCount = rows.length;
598
+ const colCount = rows[0]?.querySelectorAll(`[${CELL_ATTR}]`).length ?? 0;
599
+
600
+ if (rowCount === 0 || colCount === 0) {
601
+ return;
602
+ }
603
+
604
+ const row = this.clampAxis(e.clientY, gridRect.top, gridRect.bottom, rowCount, this.extentCell?.row ?? this.anchorCell.row);
605
+ const col = this.clampAxis(e.clientX, gridRect.left, gridRect.right, colCount, this.extentCell?.col ?? this.anchorCell.col);
606
+
607
+ const clamped = { row, col };
608
+
609
+ if (!this.extentCell || this.extentCell.row !== clamped.row || this.extentCell.col !== clamped.col) {
610
+ this.extentCell = clamped;
611
+ this.paintSelection();
612
+ }
613
+ }
614
+
615
+ private collectCellsInRange(
616
+ rows: NodeListOf<Element>,
617
+ minRow: number,
618
+ maxRow: number,
619
+ minCol: number,
620
+ maxCol: number,
621
+ ): HTMLElement[] {
622
+ return Array.from(rows)
623
+ .slice(minRow, maxRow + 1)
624
+ .flatMap(row => {
625
+ const cells = row.querySelectorAll(`[${CELL_ATTR}]`);
626
+
627
+ return Array.from(cells)
628
+ .slice(minCol, maxCol + 1)
629
+ .filter((cell): cell is HTMLElement => cell instanceof HTMLElement);
630
+ });
631
+ }
632
+
633
+ /**
634
+ * Clamp a pointer coordinate to an axis range, returning the edge index
635
+ * when outside or the fallback when inside.
636
+ */
637
+ private clampAxis(pointer: number, min: number, max: number, count: number, fallback: number): number {
638
+ if (pointer < min) {
639
+ return 0;
640
+ }
641
+
642
+ if (pointer > max) {
643
+ return count - 1;
644
+ }
645
+
646
+ return fallback;
647
+ }
648
+ }