@ckeditor/ckeditor5-table 0.0.0-nightly-next-20260127.0 → 0.0.0-nightly-20260128.0

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 (356) hide show
  1. package/build/table.js +5 -0
  2. package/build/translations/af.js +1 -0
  3. package/build/translations/ar.js +1 -0
  4. package/build/translations/ast.js +1 -0
  5. package/build/translations/az.js +1 -0
  6. package/build/translations/be.js +1 -0
  7. package/build/translations/bg.js +1 -0
  8. package/build/translations/bn.js +1 -0
  9. package/build/translations/bs.js +1 -0
  10. package/build/translations/ca.js +1 -0
  11. package/build/translations/cs.js +1 -0
  12. package/build/translations/da.js +1 -0
  13. package/build/translations/de-ch.js +1 -0
  14. package/build/translations/de.js +1 -0
  15. package/build/translations/el.js +1 -0
  16. package/build/translations/en-au.js +1 -0
  17. package/build/translations/en-gb.js +1 -0
  18. package/build/translations/eo.js +1 -0
  19. package/build/translations/es-co.js +1 -0
  20. package/build/translations/es.js +1 -0
  21. package/build/translations/et.js +1 -0
  22. package/build/translations/eu.js +1 -0
  23. package/build/translations/fa.js +1 -0
  24. package/build/translations/fi.js +1 -0
  25. package/build/translations/fr.js +1 -0
  26. package/build/translations/gl.js +1 -0
  27. package/build/translations/gu.js +1 -0
  28. package/build/translations/he.js +1 -0
  29. package/build/translations/hi.js +1 -0
  30. package/build/translations/hr.js +1 -0
  31. package/build/translations/hu.js +1 -0
  32. package/build/translations/hy.js +1 -0
  33. package/build/translations/id.js +1 -0
  34. package/build/translations/it.js +1 -0
  35. package/build/translations/ja.js +1 -0
  36. package/build/translations/jv.js +1 -0
  37. package/build/translations/kk.js +1 -0
  38. package/build/translations/km.js +1 -0
  39. package/build/translations/kn.js +1 -0
  40. package/build/translations/ko.js +1 -0
  41. package/build/translations/ku.js +1 -0
  42. package/build/translations/lt.js +1 -0
  43. package/build/translations/lv.js +1 -0
  44. package/build/translations/ms.js +1 -0
  45. package/build/translations/nb.js +1 -0
  46. package/build/translations/ne.js +1 -0
  47. package/build/translations/nl.js +1 -0
  48. package/build/translations/no.js +1 -0
  49. package/build/translations/oc.js +1 -0
  50. package/build/translations/pl.js +1 -0
  51. package/build/translations/pt-br.js +1 -0
  52. package/build/translations/pt.js +1 -0
  53. package/build/translations/ro.js +1 -0
  54. package/build/translations/ru.js +1 -0
  55. package/build/translations/si.js +1 -0
  56. package/build/translations/sk.js +1 -0
  57. package/build/translations/sl.js +1 -0
  58. package/build/translations/sq.js +1 -0
  59. package/build/translations/sr-latn.js +1 -0
  60. package/build/translations/sr.js +1 -0
  61. package/build/translations/sv.js +1 -0
  62. package/build/translations/th.js +1 -0
  63. package/build/translations/ti.js +1 -0
  64. package/build/translations/tk.js +1 -0
  65. package/build/translations/tr.js +1 -0
  66. package/build/translations/tt.js +1 -0
  67. package/build/translations/ug.js +1 -0
  68. package/build/translations/uk.js +1 -0
  69. package/build/translations/ur.js +1 -0
  70. package/build/translations/uz.js +1 -0
  71. package/build/translations/vi.js +1 -0
  72. package/build/translations/zh-cn.js +1 -0
  73. package/build/translations/zh.js +1 -0
  74. package/ckeditor5-metadata.json +7 -16
  75. package/dist/index-content.css +30 -30
  76. package/dist/index-editor.css +170 -104
  77. package/dist/index.css +237 -147
  78. package/dist/index.css.map +1 -1
  79. package/dist/index.js +2433 -448
  80. package/dist/index.js.map +1 -1
  81. package/lang/contexts.json +82 -0
  82. package/lang/translations/af.po +332 -0
  83. package/lang/translations/ar.po +332 -0
  84. package/lang/translations/ast.po +332 -0
  85. package/lang/translations/az.po +332 -0
  86. package/lang/translations/be.po +332 -0
  87. package/lang/translations/bg.po +332 -0
  88. package/lang/translations/bn.po +334 -0
  89. package/lang/translations/bs.po +332 -0
  90. package/lang/translations/ca.po +332 -0
  91. package/lang/translations/cs.po +332 -0
  92. package/lang/translations/da.po +332 -0
  93. package/lang/translations/de-ch.po +332 -0
  94. package/lang/translations/de.po +332 -0
  95. package/lang/translations/el.po +332 -0
  96. package/lang/translations/en-au.po +332 -0
  97. package/lang/translations/en-gb.po +332 -0
  98. package/lang/translations/en.po +332 -0
  99. package/lang/translations/eo.po +332 -0
  100. package/lang/translations/es-co.po +332 -0
  101. package/lang/translations/es.po +332 -0
  102. package/lang/translations/et.po +332 -0
  103. package/lang/translations/eu.po +332 -0
  104. package/lang/translations/fa.po +332 -0
  105. package/lang/translations/fi.po +332 -0
  106. package/lang/translations/fr.po +332 -0
  107. package/lang/translations/gl.po +332 -0
  108. package/lang/translations/gu.po +332 -0
  109. package/lang/translations/he.po +332 -0
  110. package/lang/translations/hi.po +332 -0
  111. package/lang/translations/hr.po +332 -0
  112. package/lang/translations/hu.po +332 -0
  113. package/lang/translations/hy.po +332 -0
  114. package/lang/translations/id.po +332 -0
  115. package/lang/translations/it.po +332 -0
  116. package/lang/translations/ja.po +332 -0
  117. package/lang/translations/jv.po +332 -0
  118. package/lang/translations/kk.po +332 -0
  119. package/lang/translations/km.po +332 -0
  120. package/lang/translations/kn.po +332 -0
  121. package/lang/translations/ko.po +332 -0
  122. package/lang/translations/ku.po +332 -0
  123. package/lang/translations/lt.po +332 -0
  124. package/lang/translations/lv.po +332 -0
  125. package/lang/translations/ms.po +332 -0
  126. package/lang/translations/nb.po +332 -0
  127. package/lang/translations/ne.po +332 -0
  128. package/lang/translations/nl.po +332 -0
  129. package/lang/translations/no.po +332 -0
  130. package/lang/translations/oc.po +332 -0
  131. package/lang/translations/pl.po +332 -0
  132. package/lang/translations/pt-br.po +332 -0
  133. package/lang/translations/pt.po +332 -0
  134. package/lang/translations/ro.po +332 -0
  135. package/lang/translations/ru.po +332 -0
  136. package/lang/translations/si.po +332 -0
  137. package/lang/translations/sk.po +332 -0
  138. package/lang/translations/sl.po +332 -0
  139. package/lang/translations/sq.po +332 -0
  140. package/lang/translations/sr-latn.po +332 -0
  141. package/lang/translations/sr.po +332 -0
  142. package/lang/translations/sv.po +332 -0
  143. package/lang/translations/th.po +332 -0
  144. package/lang/translations/ti.po +332 -0
  145. package/lang/translations/tk.po +332 -0
  146. package/lang/translations/tr.po +332 -0
  147. package/lang/translations/tt.po +332 -0
  148. package/lang/translations/ug.po +332 -0
  149. package/lang/translations/uk.po +332 -0
  150. package/lang/translations/ur.po +332 -0
  151. package/lang/translations/uz.po +332 -0
  152. package/lang/translations/vi.po +332 -0
  153. package/lang/translations/zh-cn.po +332 -0
  154. package/lang/translations/zh.po +332 -0
  155. package/package.json +49 -26
  156. package/{dist → src}/augmentation.d.ts +15 -0
  157. package/src/augmentation.js +5 -0
  158. package/{dist → src}/commands/insertcolumncommand.d.ts +1 -1
  159. package/src/commands/insertcolumncommand.js +71 -0
  160. package/{dist → src}/commands/insertrowcommand.d.ts +1 -1
  161. package/src/commands/insertrowcommand.js +70 -0
  162. package/{dist → src}/commands/inserttablecommand.d.ts +1 -1
  163. package/src/commands/inserttablecommand.js +69 -0
  164. package/{dist → src}/commands/inserttablelayoutcommand.d.ts +1 -1
  165. package/src/commands/inserttablelayoutcommand.js +65 -0
  166. package/{dist → src}/commands/mergecellcommand.d.ts +3 -3
  167. package/src/commands/mergecellcommand.js +206 -0
  168. package/{dist → src}/commands/mergecellscommand.d.ts +1 -1
  169. package/src/commands/mergecellscommand.js +94 -0
  170. package/{dist → src}/commands/removecolumncommand.d.ts +1 -1
  171. package/src/commands/removecolumncommand.js +109 -0
  172. package/{dist → src}/commands/removerowcommand.d.ts +1 -1
  173. package/src/commands/removerowcommand.js +82 -0
  174. package/{dist → src}/commands/selectcolumncommand.d.ts +1 -1
  175. package/src/commands/selectcolumncommand.js +60 -0
  176. package/{dist → src}/commands/selectrowcommand.d.ts +1 -1
  177. package/src/commands/selectrowcommand.js +56 -0
  178. package/{dist → src}/commands/setheadercolumncommand.d.ts +1 -1
  179. package/src/commands/setheadercolumncommand.js +76 -0
  180. package/{dist → src}/commands/setheaderrowcommand.d.ts +1 -1
  181. package/src/commands/setheaderrowcommand.js +83 -0
  182. package/{dist → src}/commands/splitcellcommand.d.ts +1 -1
  183. package/src/commands/splitcellcommand.js +58 -0
  184. package/{dist → src}/converters/downcast.d.ts +2 -2
  185. package/src/converters/downcast.js +298 -0
  186. package/{dist → src}/converters/table-caption-post-fixer.d.ts +1 -1
  187. package/src/converters/table-caption-post-fixer.js +55 -0
  188. package/{dist → src}/converters/table-cell-paragraph-post-fixer.d.ts +1 -1
  189. package/src/converters/table-cell-paragraph-post-fixer.js +109 -0
  190. package/{dist → src}/converters/table-cell-refresh-handler.d.ts +1 -1
  191. package/src/converters/table-cell-refresh-handler.js +47 -0
  192. package/{dist → src}/converters/table-headings-refresh-handler.d.ts +1 -1
  193. package/src/converters/table-headings-refresh-handler.js +51 -0
  194. package/{dist → src}/converters/table-layout-post-fixer.d.ts +1 -1
  195. package/src/converters/table-layout-post-fixer.js +369 -0
  196. package/{dist → src}/converters/tableproperties.d.ts +2 -2
  197. package/src/converters/tableproperties.js +444 -0
  198. package/{dist → src}/converters/upcasttable.d.ts +1 -1
  199. package/src/converters/upcasttable.js +385 -0
  200. package/{dist → src}/index.d.ts +5 -2
  201. package/src/index.js +98 -0
  202. package/{dist → src}/plaintableoutput.d.ts +1 -1
  203. package/src/plaintableoutput.js +49 -0
  204. package/{dist → src}/table.d.ts +2 -2
  205. package/src/table.js +50 -0
  206. package/{dist → src}/tablecaption/tablecaptionediting.d.ts +2 -2
  207. package/src/tablecaption/tablecaptionediting.js +136 -0
  208. package/{dist → src}/tablecaption/tablecaptionui.d.ts +1 -1
  209. package/src/tablecaption/tablecaptionui.js +64 -0
  210. package/{dist → src}/tablecaption/toggletablecaptioncommand.d.ts +1 -1
  211. package/src/tablecaption/toggletablecaptioncommand.js +105 -0
  212. package/{dist → src}/tablecaption/utils.d.ts +1 -1
  213. package/src/tablecaption/utils.js +61 -0
  214. package/{dist → src}/tablecaption.d.ts +1 -1
  215. package/src/tablecaption.js +34 -0
  216. package/{dist → src}/tablecellproperties/commands/tablecellbackgroundcolorcommand.d.ts +1 -1
  217. package/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js +30 -0
  218. package/{dist → src}/tablecellproperties/commands/tablecellbordercolorcommand.d.ts +2 -2
  219. package/src/tablecellproperties/commands/tablecellbordercolorcommand.js +44 -0
  220. package/{dist → src}/tablecellproperties/commands/tablecellborderstylecommand.d.ts +2 -2
  221. package/src/tablecellproperties/commands/tablecellborderstylecommand.js +44 -0
  222. package/{dist → src}/tablecellproperties/commands/tablecellborderwidthcommand.d.ts +2 -2
  223. package/src/tablecellproperties/commands/tablecellborderwidthcommand.js +64 -0
  224. package/{dist → src}/tablecellproperties/commands/tablecellheightcommand.d.ts +1 -1
  225. package/src/tablecellproperties/commands/tablecellheightcommand.js +51 -0
  226. package/{dist → src}/tablecellproperties/commands/tablecellhorizontalalignmentcommand.d.ts +1 -1
  227. package/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js +30 -0
  228. package/{dist → src}/tablecellproperties/commands/tablecellpaddingcommand.d.ts +2 -2
  229. package/src/tablecellproperties/commands/tablecellpaddingcommand.js +64 -0
  230. package/{dist → src}/tablecellproperties/commands/tablecellpropertycommand.d.ts +2 -2
  231. package/src/tablecellproperties/commands/tablecellpropertycommand.js +138 -0
  232. package/{dist → src}/tablecellproperties/commands/tablecelltypecommand.d.ts +6 -2
  233. package/src/tablecellproperties/commands/tablecelltypecommand.js +167 -0
  234. package/{dist → src}/tablecellproperties/commands/tablecellverticalalignmentcommand.d.ts +1 -1
  235. package/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js +38 -0
  236. package/{dist → src}/tablecellproperties/tablecellpropertiesediting.d.ts +1 -1
  237. package/src/tablecellproperties/tablecellpropertiesediting.js +412 -0
  238. package/{dist → src}/tablecellproperties/tablecellpropertiesui.d.ts +2 -2
  239. package/src/tablecellproperties/tablecellpropertiesui.js +385 -0
  240. package/src/tablecellproperties/tablecellpropertiesuiexperimental.d.ts +128 -0
  241. package/src/tablecellproperties/tablecellpropertiesuiexperimental.js +408 -0
  242. package/src/tablecellproperties/ui/tablecellpropertiesview.d.ts +229 -0
  243. package/src/tablecellproperties/ui/tablecellpropertiesview.js +612 -0
  244. package/{dist/tablecellproperties/ui/tablecellpropertiesview.d.ts → src/tablecellproperties/ui/tablecellpropertiesviewexperimental.d.ts} +12 -11
  245. package/src/tablecellproperties/ui/tablecellpropertiesviewexperimental.js +744 -0
  246. package/{dist → src}/tablecellproperties.d.ts +1 -1
  247. package/src/tablecellproperties.js +40 -0
  248. package/{dist → src}/tablecellwidth/commands/tablecellwidthcommand.d.ts +1 -1
  249. package/src/tablecellwidth/commands/tablecellwidthcommand.js +51 -0
  250. package/{dist → src}/tablecellwidth/tablecellwidthediting.d.ts +1 -1
  251. package/src/tablecellwidth/tablecellwidthediting.js +53 -0
  252. package/{dist → src}/tableclipboard.d.ts +3 -3
  253. package/src/tableclipboard.js +500 -0
  254. package/src/tablecolumnresize/constants.js +33 -0
  255. package/{dist → src}/tablecolumnresize/converters.d.ts +1 -1
  256. package/src/tablecolumnresize/converters.js +62 -0
  257. package/{dist → src}/tablecolumnresize/tablecolumnresizeediting.d.ts +2 -2
  258. package/src/tablecolumnresize/tablecolumnresizeediting.js +734 -0
  259. package/{dist → src}/tablecolumnresize/tablewidthscommand.d.ts +2 -2
  260. package/src/tablecolumnresize/tablewidthscommand.js +61 -0
  261. package/{dist → src}/tablecolumnresize/utils.d.ts +2 -2
  262. package/src/tablecolumnresize/utils.js +370 -0
  263. package/{dist → src}/tablecolumnresize.d.ts +1 -1
  264. package/src/tablecolumnresize.js +36 -0
  265. package/{dist → src}/tableconfig.d.ts +6 -26
  266. package/src/tableconfig.js +5 -0
  267. package/{dist → src}/tableediting.d.ts +3 -3
  268. package/src/tableediting.js +246 -0
  269. package/{dist → src}/tablekeyboard.d.ts +3 -3
  270. package/src/tablekeyboard.js +273 -0
  271. package/{dist → src}/tablelayout/commands/tabletypecommand.d.ts +1 -1
  272. package/src/tablelayout/commands/tabletypecommand.js +68 -0
  273. package/{dist → src}/tablelayout/tablelayoutediting.d.ts +1 -1
  274. package/src/tablelayout/tablelayoutediting.js +295 -0
  275. package/{dist → src}/tablelayout/tablelayoutui.d.ts +1 -1
  276. package/src/tablelayout/tablelayoutui.js +196 -0
  277. package/{dist → src}/tablelayout.d.ts +1 -1
  278. package/src/tablelayout.js +37 -0
  279. package/{dist → src}/tablemouse/mouseeventsobserver.d.ts +1 -1
  280. package/src/tablemouse/mouseeventsobserver.js +34 -0
  281. package/{dist → src}/tablemouse.d.ts +1 -1
  282. package/src/tablemouse.js +178 -0
  283. package/{dist → src}/tableproperties/commands/tablealignmentcommand.d.ts +1 -1
  284. package/src/tableproperties/commands/tablealignmentcommand.js +30 -0
  285. package/{dist → src}/tableproperties/commands/tablebackgroundcolorcommand.d.ts +1 -1
  286. package/src/tableproperties/commands/tablebackgroundcolorcommand.js +30 -0
  287. package/{dist → src}/tableproperties/commands/tablebordercolorcommand.d.ts +2 -2
  288. package/src/tableproperties/commands/tablebordercolorcommand.js +44 -0
  289. package/{dist → src}/tableproperties/commands/tableborderstylecommand.d.ts +2 -2
  290. package/src/tableproperties/commands/tableborderstylecommand.js +44 -0
  291. package/{dist → src}/tableproperties/commands/tableborderwidthcommand.d.ts +2 -2
  292. package/src/tableproperties/commands/tableborderwidthcommand.js +64 -0
  293. package/{dist → src}/tableproperties/commands/tableheightcommand.d.ts +1 -1
  294. package/src/tableproperties/commands/tableheightcommand.js +54 -0
  295. package/{dist → src}/tableproperties/commands/tablepropertycommand.d.ts +2 -2
  296. package/src/tableproperties/commands/tablepropertycommand.js +103 -0
  297. package/{dist → src}/tableproperties/commands/tablewidthcommand.d.ts +1 -1
  298. package/src/tableproperties/commands/tablewidthcommand.js +54 -0
  299. package/{dist → src}/tableproperties/tablepropertiesediting.d.ts +1 -1
  300. package/src/tableproperties/tablepropertiesediting.js +546 -0
  301. package/{dist → src}/tableproperties/tablepropertiesui.d.ts +2 -2
  302. package/src/tableproperties/tablepropertiesui.js +374 -0
  303. package/src/tableproperties/tablepropertiesuiexperimental.d.ts +136 -0
  304. package/src/tableproperties/tablepropertiesuiexperimental.js +375 -0
  305. package/{dist → src}/tableproperties/ui/tablepropertiesview.d.ts +2 -10
  306. package/src/tableproperties/ui/tablepropertiesview.js +520 -0
  307. package/src/tableproperties/ui/tablepropertiesviewexperimental.d.ts +216 -0
  308. package/src/tableproperties/ui/tablepropertiesviewexperimental.js +544 -0
  309. package/{dist → src}/tableproperties.d.ts +1 -1
  310. package/src/tableproperties.js +40 -0
  311. package/{dist → src}/tableselection.d.ts +2 -2
  312. package/src/tableselection.js +323 -0
  313. package/{dist → src}/tabletoolbar.d.ts +2 -2
  314. package/src/tabletoolbar.js +63 -0
  315. package/{dist → src}/tableui.d.ts +1 -1
  316. package/src/tableui.js +335 -0
  317. package/{dist → src}/tableutils.d.ts +2 -2
  318. package/src/tableutils.js +1282 -0
  319. package/{dist → src}/tablewalker.d.ts +1 -1
  320. package/src/tablewalker.js +489 -0
  321. package/{dist → src}/ui/colorinputview.d.ts +2 -2
  322. package/src/ui/colorinputview.js +305 -0
  323. package/{dist → src}/ui/inserttableview.d.ts +2 -2
  324. package/src/ui/inserttableview.js +192 -0
  325. package/{dist → src}/utils/common.d.ts +2 -2
  326. package/src/utils/common.js +118 -0
  327. package/{dist → src}/utils/structure.d.ts +1 -1
  328. package/src/utils/structure.js +452 -0
  329. package/{dist → src}/utils/table-properties.d.ts +1 -1
  330. package/src/utils/table-properties.js +121 -0
  331. package/{dist → src}/utils/ui/contextualballoon.d.ts +2 -2
  332. package/src/utils/ui/contextualballoon.js +111 -0
  333. package/{dist → src}/utils/ui/table-properties.d.ts +2 -2
  334. package/src/utils/ui/table-properties.js +390 -0
  335. package/src/utils/ui/table-propertiesexperimental.d.ts +215 -0
  336. package/src/utils/ui/table-propertiesexperimental.js +391 -0
  337. package/{dist → src}/utils/ui/widget.d.ts +1 -1
  338. package/src/utils/ui/widget.js +56 -0
  339. package/theme/colorinput.css +39 -0
  340. package/theme/formrow-experimental.css +15 -0
  341. package/theme/formrow.css +13 -0
  342. package/theme/inserttable.css +10 -0
  343. package/theme/table.css +144 -0
  344. package/theme/tablecaption.css +66 -0
  345. package/theme/tablecellproperties-experimental.css +4 -0
  346. package/theme/tablecellproperties.css +28 -0
  347. package/theme/tablecolumnresize.css +62 -0
  348. package/theme/tableediting.css +10 -0
  349. package/theme/tableform-experimental.css +61 -0
  350. package/theme/tableform.css +64 -0
  351. package/theme/tablelayout.css +74 -0
  352. package/theme/tableproperties-experimental.css +78 -0
  353. package/theme/tableproperties.css +18 -0
  354. package/theme/tableselection.css +10 -0
  355. package/dist/tablecellproperties/tablecellpropertiesutils.d.ts +0 -18
  356. /package/{dist → src}/tablecolumnresize/constants.d.ts +0 -0
@@ -0,0 +1,1282 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ /**
6
+ * @module table/tableutils
7
+ */
8
+ import { CKEditorError } from 'ckeditor5/src/utils.js';
9
+ import { Plugin } from 'ckeditor5/src/core.js';
10
+ import { TableWalker } from './tablewalker.js';
11
+ import { createEmptyTableCell, updateNumericAttribute, isEntireCellsLineHeader, isTableCellTypeEnabled } from './utils/common.js';
12
+ import { removeEmptyColumns, removeEmptyRows } from './utils/structure.js';
13
+ import { getTableColumnElements } from './tablecolumnresize/utils.js';
14
+ /**
15
+ * The table utilities plugin.
16
+ */
17
+ export class TableUtils extends Plugin {
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ static get pluginName() {
22
+ return 'TableUtils';
23
+ }
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ static get isOfficialPlugin() {
28
+ return true;
29
+ }
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ init() {
34
+ this.decorate('insertColumns');
35
+ this.decorate('insertRows');
36
+ }
37
+ /**
38
+ * Returns the table cell location as an object with table row and table column indexes.
39
+ *
40
+ * For instance, in the table below:
41
+ *
42
+ * 0 1 2 3
43
+ * +---+---+---+---+
44
+ * 0 | a | b | c |
45
+ * + + +---+
46
+ * 1 | | | d |
47
+ * +---+---+ +---+
48
+ * 2 | e | | f |
49
+ * +---+---+---+---+
50
+ *
51
+ * the method will return:
52
+ *
53
+ * ```ts
54
+ * const cellA = table.getNodeByPath( [ 0, 0 ] );
55
+ * editor.plugins.get( 'TableUtils' ).getCellLocation( cellA );
56
+ * // will return { row: 0, column: 0 }
57
+ *
58
+ * const cellD = table.getNodeByPath( [ 1, 0 ] );
59
+ * editor.plugins.get( 'TableUtils' ).getCellLocation( cellD );
60
+ * // will return { row: 1, column: 3 }
61
+ * ```
62
+ *
63
+ * @returns Returns a `{row, column}` object.
64
+ */
65
+ getCellLocation(tableCell) {
66
+ const tableRow = tableCell.parent;
67
+ const table = tableRow.parent;
68
+ const rowIndex = table.getChildIndex(tableRow);
69
+ const tableWalker = new TableWalker(table, { row: rowIndex });
70
+ for (const { cell, row, column } of tableWalker) {
71
+ if (cell === tableCell) {
72
+ return { row, column };
73
+ }
74
+ }
75
+ // Should be unreachable code.
76
+ /* istanbul ignore next -- @preserve */
77
+ return undefined;
78
+ }
79
+ /**
80
+ * Creates an empty table with a proper structure. The table needs to be inserted into the model,
81
+ * for example, by using the {@link module:engine/model/model~Model#insertContent} function.
82
+ *
83
+ * ```ts
84
+ * model.change( ( writer ) => {
85
+ * // Create a table of 2 rows and 7 columns:
86
+ * const table = tableUtils.createTable( writer, { rows: 2, columns: 7 } );
87
+ *
88
+ * // Insert a table to the model at the best position taking the current selection:
89
+ * model.insertContent( table );
90
+ * }
91
+ * ```
92
+ *
93
+ * @param writer The model writer.
94
+ * @param options.rows The number of rows to create. Default value is 2.
95
+ * @param options.columns The number of columns to create. Default value is 2.
96
+ * @param options.headingRows The number of heading rows. Default value is 0.
97
+ * @param options.headingColumns The number of heading columns. Default value is 0.
98
+ * @returns The created table element.
99
+ */
100
+ createTable(writer, options) {
101
+ const table = writer.createElement('table');
102
+ const rows = options.rows || 2;
103
+ const columns = options.columns || 2;
104
+ createEmptyRows(writer, table, 0, rows, columns);
105
+ if (options.headingRows) {
106
+ this.setHeadingRowsCount(writer, table, Math.min(options.headingRows, rows));
107
+ }
108
+ if (options.headingColumns) {
109
+ this.setHeadingColumnsCount(writer, table, Math.min(options.headingColumns, columns));
110
+ }
111
+ return table;
112
+ }
113
+ /**
114
+ * Inserts rows into a table.
115
+ *
116
+ * ```ts
117
+ * editor.plugins.get( 'TableUtils' ).insertRows( table, { at: 1, rows: 2 } );
118
+ * ```
119
+ *
120
+ * Assuming the table on the left, the above code will transform it to the table on the right:
121
+ *
122
+ * row index
123
+ * 0 +---+---+---+ `at` = 1, +---+---+---+ 0
124
+ * | a | b | c | `rows` = 2, | a | b | c |
125
+ * 1 + +---+---+ <-- insert here + +---+---+ 1
126
+ * | | d | e | | | | |
127
+ * 2 + +---+---+ will give: + +---+---+ 2
128
+ * | | f | g | | | | |
129
+ * 3 +---+---+---+ + +---+---+ 3
130
+ * | | d | e |
131
+ * + +---+---+ 4
132
+ * + + f | g |
133
+ * +---+---+---+ 5
134
+ *
135
+ * @param table The table model element where the rows will be inserted.
136
+ * @param options.at The row index at which the rows will be inserted. Default value is 0.
137
+ * @param options.rows The number of rows to insert. Default value is 1.
138
+ * @param options.copyStructureFromAbove The flag for copying row structure. Note that
139
+ * the row structure will not be copied if this option is not provided.
140
+ */
141
+ insertRows(table, options = {}) {
142
+ const model = this.editor.model;
143
+ const insertAt = options.at || 0;
144
+ const rowsToInsert = options.rows || 1;
145
+ const isCopyStructure = options.copyStructureFromAbove !== undefined;
146
+ const copyStructureFrom = options.copyStructureFromAbove ? insertAt - 1 : insertAt;
147
+ const cellTypeEnabled = isTableCellTypeEnabled(this.editor);
148
+ const rows = this.getRows(table);
149
+ const columns = this.getColumns(table);
150
+ if (insertAt > rows) {
151
+ /**
152
+ * The `options.at` points at a row position that does not exist.
153
+ *
154
+ * @error tableutils-insertrows-insert-out-of-range
155
+ */
156
+ throw new CKEditorError('tableutils-insertrows-insert-out-of-range', this, { options });
157
+ }
158
+ model.change(writer => {
159
+ let headingRows = table.getAttribute('headingRows') || 0;
160
+ const headingColumns = table.getAttribute('headingColumns') || 0;
161
+ // Inserting rows inside heading section requires to update `headingRows` attribute as the heading section will grow.
162
+ if (headingRows > insertAt) {
163
+ headingRows += rowsToInsert;
164
+ this.setHeadingRowsCount(writer, table, headingRows, {
165
+ shallow: true
166
+ });
167
+ }
168
+ // Inserting at the end or at the beginning of a table doesn't require to calculate anything special.
169
+ if (!isCopyStructure && (insertAt === 0 || insertAt === rows)) {
170
+ const rows = createEmptyRows(writer, table, insertAt, rowsToInsert, columns);
171
+ if (cellTypeEnabled) {
172
+ for (let rowOffset = 0; rowOffset < rows.length; rowOffset++) {
173
+ const row = rows[rowOffset];
174
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
175
+ const cell = row[columnIndex];
176
+ if (insertAt + rowOffset < headingRows || columnIndex < headingColumns) {
177
+ writer.setAttribute('tableCellType', 'header', cell);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return;
183
+ }
184
+ // Iterate over all the rows above the inserted rows in order to check for the row-spanned cells.
185
+ const walkerEndRow = isCopyStructure ? Math.max(insertAt, copyStructureFrom) : insertAt;
186
+ const tableIterator = new TableWalker(table, { endRow: walkerEndRow });
187
+ // Store spans of the reference row to reproduce it's structure. This array is column number indexed.
188
+ const rowColSpansMap = new Array(columns).fill(1);
189
+ for (const { row, column, cellHeight, cellWidth, cell } of tableIterator) {
190
+ const lastCellRow = row + cellHeight - 1;
191
+ const isOverlappingInsertedRow = row < insertAt && insertAt <= lastCellRow;
192
+ const isReferenceRow = row <= copyStructureFrom && copyStructureFrom <= lastCellRow;
193
+ // If the cell is row-spanned and overlaps the inserted row, then reserve space for it in the row map.
194
+ if (isOverlappingInsertedRow) {
195
+ // This cell overlaps the inserted rows so we need to expand it further.
196
+ writer.setAttribute('rowspan', cellHeight + rowsToInsert, cell);
197
+ // Mark this cell with negative number to indicate how many cells should be skipped when adding the new cells.
198
+ rowColSpansMap[column] = -cellWidth;
199
+ }
200
+ // Store the colspan from reference row.
201
+ else if (isCopyStructure && isReferenceRow) {
202
+ rowColSpansMap[column] = cellWidth;
203
+ }
204
+ }
205
+ for (let rowIndex = 0; rowIndex < rowsToInsert; rowIndex++) {
206
+ const tableRow = writer.createElement('tableRow');
207
+ writer.insert(tableRow, table, insertAt);
208
+ for (let cellIndex = 0; cellIndex < rowColSpansMap.length; cellIndex++) {
209
+ const colspan = rowColSpansMap[cellIndex];
210
+ const insertPosition = writer.createPositionAt(tableRow, 'end');
211
+ // Insert the empty cell only if this slot is not row-spanned from any other cell.
212
+ if (colspan > 0) {
213
+ const insertedCells = createEmptyTableCell(writer, insertPosition, colspan > 1 ? { colspan } : undefined);
214
+ // If we insert row in heading section, set proper cell type.
215
+ if (cellTypeEnabled && (insertAt + rowIndex < headingRows || cellIndex < headingColumns)) {
216
+ writer.setAttribute('tableCellType', 'header', insertedCells);
217
+ }
218
+ }
219
+ // Skip the col-spanned slots, there won't be any cells.
220
+ cellIndex += Math.abs(colspan) - 1;
221
+ }
222
+ }
223
+ });
224
+ }
225
+ /**
226
+ * Inserts columns into a table.
227
+ *
228
+ * ```ts
229
+ * editor.plugins.get( 'TableUtils' ).insertColumns( table, { at: 1, columns: 2 } );
230
+ * ```
231
+ *
232
+ * Assuming the table on the left, the above code will transform it to the table on the right:
233
+ *
234
+ * 0 1 2 3 0 1 2 3 4 5
235
+ * +---+---+---+ +---+---+---+---+---+
236
+ * | a | b | | a | b |
237
+ * + +---+ + +---+
238
+ * | | c | | | c |
239
+ * +---+---+---+ will give: +---+---+---+---+---+
240
+ * | d | e | f | | d | | | e | f |
241
+ * +---+ +---+ +---+---+---+ +---+
242
+ * | g | | h | | g | | | | h |
243
+ * +---+---+---+ +---+---+---+---+---+
244
+ * | i | | i |
245
+ * +---+---+---+ +---+---+---+---+---+
246
+ * ^---- insert here, `at` = 1, `columns` = 2
247
+ *
248
+ * @param table The table model element where the columns will be inserted.
249
+ * @param options.at The column index at which the columns will be inserted. Default value is 0.
250
+ * @param options.columns The number of columns to insert. Default value is 1.
251
+ */
252
+ insertColumns(table, options = {}) {
253
+ const model = this.editor.model;
254
+ const insertAt = options.at || 0;
255
+ const columnsToInsert = options.columns || 1;
256
+ const cellTypeEnabled = isTableCellTypeEnabled(this.editor);
257
+ model.change(writer => {
258
+ const headingRows = table.getAttribute('headingRows') || 0;
259
+ let headingColumns = table.getAttribute('headingColumns');
260
+ // Inserting columns inside heading section requires to update `headingColumns` attribute as the heading section will grow.
261
+ if (insertAt < headingColumns) {
262
+ headingColumns += columnsToInsert;
263
+ this.setHeadingColumnsCount(writer, table, headingColumns, {
264
+ shallow: true
265
+ });
266
+ }
267
+ const tableColumns = this.getColumns(table);
268
+ // Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
269
+ if (insertAt === 0 || tableColumns === insertAt) {
270
+ let rowIndex = 0;
271
+ for (const tableRow of table.getChildren()) {
272
+ // Ignore non-row elements inside the table (e.g. caption).
273
+ if (!tableRow.is('element', 'tableRow')) {
274
+ continue;
275
+ }
276
+ const insertedCells = createCells(columnsToInsert, writer, writer.createPositionAt(tableRow, insertAt ? 'end' : 0));
277
+ if (cellTypeEnabled) {
278
+ // If we insert column in heading section, set proper cell type.
279
+ for (let columnOffset = 0; columnOffset < insertedCells.length; columnOffset++) {
280
+ if (insertAt + columnOffset < headingColumns || rowIndex < headingRows) {
281
+ writer.setAttribute('tableCellType', 'header', insertedCells[columnOffset]);
282
+ }
283
+ }
284
+ }
285
+ rowIndex++;
286
+ }
287
+ return;
288
+ }
289
+ const tableWalker = new TableWalker(table, { column: insertAt, includeAllSlots: true });
290
+ for (const tableSlot of tableWalker) {
291
+ const { row, cell, cellAnchorColumn, cellAnchorRow, cellWidth, cellHeight } = tableSlot;
292
+ // When iterating over column the table walker outputs either:
293
+ // - cells at given column index (cell "e" from method docs),
294
+ // - spanned columns (spanned cell from row between cells "g" and "h" - spanned by "e", only if `includeAllSlots: true`),
295
+ // - or a cell from the same row which spans over this column (cell "a").
296
+ if (cellAnchorColumn < insertAt) {
297
+ // If cell is anchored in previous column, it is a cell that spans over an inserted column (cell "a" & "i").
298
+ // For such cells expand them by a number of columns inserted.
299
+ writer.setAttribute('colspan', cellWidth + columnsToInsert, cell);
300
+ // This cell will overlap cells in rows below so skip them (because of `includeAllSlots` option) - (cell "a")
301
+ const lastCellRow = cellAnchorRow + cellHeight - 1;
302
+ for (let i = row; i <= lastCellRow; i++) {
303
+ tableWalker.skipRow(i);
304
+ }
305
+ }
306
+ else {
307
+ // It's either cell at this column index or spanned cell by a row-spanned cell from row above.
308
+ // In table above it's cell "e" and a spanned position from row below (empty cell between cells "g" and "h")
309
+ const insertedCells = createCells(columnsToInsert, writer, tableSlot.getPositionBefore());
310
+ // If we insert column in heading section, set proper cell type.
311
+ if (cellTypeEnabled) {
312
+ for (let columnOffset = 0; columnOffset < insertedCells.length; columnOffset++) {
313
+ if (insertAt + columnOffset < headingColumns || row < headingRows) {
314
+ writer.setAttribute('tableCellType', 'header', insertedCells[columnOffset]);
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+ });
321
+ }
322
+ /**
323
+ * Removes rows from the given `table`.
324
+ *
325
+ * This method re-calculates the table geometry including `rowspan` attribute of table cells overlapping removed rows
326
+ * and table headings values.
327
+ *
328
+ * ```ts
329
+ * editor.plugins.get( 'TableUtils' ).removeRows( table, { at: 1, rows: 2 } );
330
+ * ```
331
+ *
332
+ * Executing the above code in the context of the table on the left will transform its structure as presented on the right:
333
+ *
334
+ * row index
335
+ * ┌───┬───┬───┐ `at` = 1 ┌───┬───┬───┐
336
+ * 0 │ a │ b │ c │ `rows` = 2 │ a │ b │ c │ 0
337
+ * │ ├───┼───┤ │ ├───┼───┤
338
+ * 1 │ │ d │ e │ <-- remove from here │ │ d │ g │ 1
339
+ * │ │ ├───┤ will give: ├───┼───┼───┤
340
+ * 2 │ │ │ f │ │ h │ i │ j │ 2
341
+ * │ │ ├───┤ └───┴───┴───┘
342
+ * 3 │ │ │ g │
343
+ * ├───┼───┼───┤
344
+ * 4 │ h │ i │ j │
345
+ * └───┴───┴───┘
346
+ *
347
+ * @param options.at The row index at which the removing rows will start.
348
+ * @param options.rows The number of rows to remove. Default value is 1.
349
+ */
350
+ removeRows(table, options) {
351
+ const model = this.editor.model;
352
+ const rowsToRemove = options.rows || 1;
353
+ const rowCount = this.getRows(table);
354
+ const first = options.at;
355
+ const last = first + rowsToRemove - 1;
356
+ if (last > rowCount - 1) {
357
+ /**
358
+ * The `options.at` param must point at existing row and `options.rows` must not exceed the rows in the table.
359
+ *
360
+ * @error tableutils-removerows-row-index-out-of-range
361
+ */
362
+ throw new CKEditorError('tableutils-removerows-row-index-out-of-range', this, { table, options });
363
+ }
364
+ model.change(writer => {
365
+ const indexesObject = { first, last };
366
+ // Removing rows from the table require that most calculations to be done prior to changing table structure.
367
+ // Preparations must be done in the same enqueueChange callback to use the current table structure.
368
+ // 1. Preparation - get row-spanned cells that have to be modified after removing rows.
369
+ const { cellsToMove, cellsToTrim } = getCellsToMoveAndTrimOnRemoveRow(table, indexesObject);
370
+ // 2. Execution
371
+ // 2a. Move cells from removed rows that extends over a removed section - must be done before removing rows.
372
+ // This will fill any gaps in a rows below that previously were empty because of row-spanned cells.
373
+ if (cellsToMove.size) {
374
+ const rowAfterRemovedSection = last + 1;
375
+ moveCellsToRow(table, rowAfterRemovedSection, cellsToMove, writer);
376
+ }
377
+ // 2b. Remove all required rows.
378
+ for (let i = last; i >= first; i--) {
379
+ writer.remove(table.getChild(i));
380
+ }
381
+ // 2c. Update cells from rows above that overlap removed section. Similar to step 2 but does not involve moving cells.
382
+ for (const { rowspan, cell } of cellsToTrim) {
383
+ updateNumericAttribute('rowspan', rowspan, cell, writer);
384
+ }
385
+ // 2d. Adjust heading rows if removed rows were in a heading section.
386
+ updateHeadingRows(table, indexesObject, writer);
387
+ // 2e. Remove empty columns (without anchored cells) if there are any.
388
+ if (!removeEmptyColumns(table, this)) {
389
+ // If there wasn't any empty columns then we still need to check if this wasn't called
390
+ // because of cleaning empty rows and we only removed one of them.
391
+ removeEmptyRows(table, this);
392
+ }
393
+ // 3. If next rows are entirely header, adjust heading rows count.
394
+ if (isTableCellTypeEnabled(this.editor)) {
395
+ let headingRows = table.getAttribute('headingRows') || 0;
396
+ const totalRows = this.getRows(table);
397
+ while (headingRows < totalRows && isEntireCellsLineHeader({ table, row: headingRows })) {
398
+ headingRows++;
399
+ }
400
+ this.setHeadingRowsCount(writer, table, headingRows, { shallow: true });
401
+ }
402
+ });
403
+ }
404
+ /**
405
+ * Removes columns from the given `table`.
406
+ *
407
+ * This method re-calculates the table geometry including the `colspan` attribute of table cells overlapping removed columns
408
+ * and table headings values.
409
+ *
410
+ * ```ts
411
+ * editor.plugins.get( 'TableUtils' ).removeColumns( table, { at: 1, columns: 2 } );
412
+ * ```
413
+ *
414
+ * Executing the above code in the context of the table on the left will transform its structure as presented on the right:
415
+ *
416
+ * 0 1 2 3 4 0 1 2
417
+ * ┌───────────────┬───┐ ┌───────┬───┐
418
+ * │ a │ b │ │ a │ b │
419
+ * │ ├───┤ │ ├───┤
420
+ * │ │ c │ │ │ c │
421
+ * ├───┬───┬───┬───┼───┤ will give: ├───┬───┼───┤
422
+ * │ d │ e │ f │ g │ h │ │ d │ g │ h │
423
+ * ├───┼───┼───┤ ├───┤ ├───┤ ├───┤
424
+ * │ i │ j │ k │ │ l │ │ i │ │ l │
425
+ * ├───┴───┴───┴───┴───┤ ├───┴───┴───┤
426
+ * │ m │ │ m │
427
+ * └───────────────────┘ └───────────┘
428
+ * ^---- remove from here, `at` = 1, `columns` = 2
429
+ *
430
+ * @param options.at The row index at which the removing columns will start.
431
+ * @param options.columns The number of columns to remove.
432
+ */
433
+ removeColumns(table, options) {
434
+ const model = this.editor.model;
435
+ const first = options.at;
436
+ const columnsToRemove = options.columns || 1;
437
+ const last = options.at + columnsToRemove - 1;
438
+ model.change(writer => {
439
+ adjustHeadingColumns(table, { first, last }, writer);
440
+ const tableColumns = getTableColumnElements(table);
441
+ for (let removedColumnIndex = last; removedColumnIndex >= first; removedColumnIndex--) {
442
+ for (const { cell, column, cellWidth } of [...new TableWalker(table)]) {
443
+ // If colspaned cell overlaps removed column decrease its span.
444
+ if (column <= removedColumnIndex && cellWidth > 1 && column + cellWidth > removedColumnIndex) {
445
+ updateNumericAttribute('colspan', cellWidth - 1, cell, writer);
446
+ }
447
+ else if (column === removedColumnIndex) {
448
+ // The cell in removed column has colspan of 1.
449
+ writer.remove(cell);
450
+ }
451
+ }
452
+ // If table has `tableColumn` elements, we need to update it manually.
453
+ // See https://github.com/ckeditor/ckeditor5/issues/14521#issuecomment-1662102889 for details.
454
+ if (tableColumns[removedColumnIndex]) {
455
+ // If the removed column is the first one then we need to add its width to the next column.
456
+ // Otherwise we add it to the previous column.
457
+ const adjacentColumn = removedColumnIndex === 0 ? tableColumns[1] : tableColumns[removedColumnIndex - 1];
458
+ const removedColumnWidth = parseFloat(tableColumns[removedColumnIndex].getAttribute('columnWidth'));
459
+ const adjacentColumnWidth = parseFloat(adjacentColumn.getAttribute('columnWidth'));
460
+ writer.remove(tableColumns[removedColumnIndex]);
461
+ // Add the removed column width (in %) to the adjacent column.
462
+ writer.setAttribute('columnWidth', removedColumnWidth + adjacentColumnWidth + '%', adjacentColumn);
463
+ }
464
+ }
465
+ // Remove empty rows that could appear after removing columns.
466
+ if (!removeEmptyRows(table, this)) {
467
+ // If there wasn't any empty rows then we still need to check if this wasn't called
468
+ // because of cleaning empty columns and we only removed one of them.
469
+ removeEmptyColumns(table, this);
470
+ }
471
+ // If next columns are entirely header, adjust heading columns count.
472
+ if (isTableCellTypeEnabled(this.editor)) {
473
+ let headingColumns = table.getAttribute('headingColumns') || 0;
474
+ const totalColumns = this.getColumns(table);
475
+ while (headingColumns < totalColumns && isEntireCellsLineHeader({ table, column: headingColumns })) {
476
+ headingColumns++;
477
+ }
478
+ this.setHeadingColumnsCount(writer, table, headingColumns, { shallow: true });
479
+ }
480
+ });
481
+ }
482
+ /**
483
+ * Divides a table cell vertically into several ones.
484
+ *
485
+ * The cell will be visually split into more cells by updating colspans of other cells in a column
486
+ * and inserting cells (columns) after that cell.
487
+ *
488
+ * In the table below, if cell "a" is split into 3 cells:
489
+ *
490
+ * +---+---+---+
491
+ * | a | b | c |
492
+ * +---+---+---+
493
+ * | d | e | f |
494
+ * +---+---+---+
495
+ *
496
+ * it will result in the table below:
497
+ *
498
+ * +---+---+---+---+---+
499
+ * | a | | | b | c |
500
+ * +---+---+---+---+---+
501
+ * | d | e | f |
502
+ * +---+---+---+---+---+
503
+ *
504
+ * So cell "d" will get its `colspan` updated to `3` and 2 cells will be added (2 columns will be created).
505
+ *
506
+ * Splitting a cell that already has a `colspan` attribute set will distribute the cell `colspan` evenly and the remainder
507
+ * will be left to the original cell:
508
+ *
509
+ * +---+---+---+
510
+ * | a |
511
+ * +---+---+---+
512
+ * | b | c | d |
513
+ * +---+---+---+
514
+ *
515
+ * Splitting cell "a" with `colspan=3` into 2 cells will create 1 cell with a `colspan=a` and cell "a" that will have `colspan=2`:
516
+ *
517
+ * +---+---+---+
518
+ * | a | |
519
+ * +---+---+---+
520
+ * | b | c | d |
521
+ * +---+---+---+
522
+ */
523
+ splitCellVertically(tableCell, numberOfCells = 2) {
524
+ const model = this.editor.model;
525
+ const tableRow = tableCell.parent;
526
+ const table = tableRow.parent;
527
+ const rowspan = parseInt(tableCell.getAttribute('rowspan') || '1');
528
+ const colspan = parseInt(tableCell.getAttribute('colspan') || '1');
529
+ model.change(writer => {
530
+ // First check - the cell spans over multiple rows so before doing anything else just split this cell.
531
+ if (colspan > 1) {
532
+ // Get spans of new (inserted) cells and span to update of split cell.
533
+ const { newCellsSpan, updatedSpan } = breakSpanEvenly(colspan, numberOfCells);
534
+ updateNumericAttribute('colspan', updatedSpan, tableCell, writer);
535
+ // Each inserted cell will have the same attributes:
536
+ const newCellsAttributes = {};
537
+ // Do not store default value in the model.
538
+ if (newCellsSpan > 1) {
539
+ newCellsAttributes.colspan = newCellsSpan;
540
+ }
541
+ // Copy rowspan of split cell.
542
+ if (rowspan > 1) {
543
+ newCellsAttributes.rowspan = rowspan;
544
+ }
545
+ const cellsToInsert = colspan > numberOfCells ? numberOfCells - 1 : colspan - 1;
546
+ createCells(cellsToInsert, writer, writer.createPositionAfter(tableCell), newCellsAttributes);
547
+ }
548
+ // Second check - the cell has colspan of 1 or we need to create more cells then the currently one spans over.
549
+ if (colspan < numberOfCells) {
550
+ const cellsToInsert = numberOfCells - colspan;
551
+ // First step: expand cells on the same column as split cell.
552
+ const tableMap = [...new TableWalker(table)];
553
+ // Get the column index of split cell.
554
+ const { column: splitCellColumn } = tableMap.find(({ cell }) => cell === tableCell);
555
+ // Find cells which needs to be expanded vertically - those on the same column or those that spans over split cell's column.
556
+ const cellsToUpdate = tableMap.filter(({ cell, cellWidth, column }) => {
557
+ const isOnSameColumn = cell !== tableCell && column === splitCellColumn;
558
+ const spansOverColumn = (column < splitCellColumn && column + cellWidth > splitCellColumn);
559
+ return isOnSameColumn || spansOverColumn;
560
+ });
561
+ // Expand cells vertically.
562
+ for (const { cell, cellWidth } of cellsToUpdate) {
563
+ writer.setAttribute('colspan', cellWidth + cellsToInsert, cell);
564
+ }
565
+ // Second step: create columns after split cell.
566
+ // Each inserted cell will have the same attributes:
567
+ const newCellsAttributes = {};
568
+ // Do not store default value in the model.
569
+ // Copy rowspan of split cell.
570
+ if (rowspan > 1) {
571
+ newCellsAttributes.rowspan = rowspan;
572
+ }
573
+ createCells(cellsToInsert, writer, writer.createPositionAfter(tableCell), newCellsAttributes);
574
+ const headingColumns = table.getAttribute('headingColumns') || 0;
575
+ // Update heading section if split cell is in heading section.
576
+ if (headingColumns > splitCellColumn) {
577
+ updateNumericAttribute('headingColumns', headingColumns + cellsToInsert, table, writer);
578
+ }
579
+ }
580
+ });
581
+ }
582
+ /**
583
+ * Divides a table cell horizontally into several ones.
584
+ *
585
+ * The cell will be visually split into more cells by updating rowspans of other cells in the row and inserting rows with a single cell
586
+ * below.
587
+ *
588
+ * If in the table below cell "b" is split into 3 cells:
589
+ *
590
+ * +---+---+---+
591
+ * | a | b | c |
592
+ * +---+---+---+
593
+ * | d | e | f |
594
+ * +---+---+---+
595
+ *
596
+ * It will result in the table below:
597
+ *
598
+ * +---+---+---+
599
+ * | a | b | c |
600
+ * + +---+ +
601
+ * | | | |
602
+ * + +---+ +
603
+ * | | | |
604
+ * +---+---+---+
605
+ * | d | e | f |
606
+ * +---+---+---+
607
+ *
608
+ * So cells "a" and "b" will get their `rowspan` updated to `3` and 2 rows with a single cell will be added.
609
+ *
610
+ * Splitting a cell that already has a `rowspan` attribute set will distribute the cell `rowspan` evenly and the remainder
611
+ * will be left to the original cell:
612
+ *
613
+ * +---+---+---+
614
+ * | a | b | c |
615
+ * + +---+---+
616
+ * | | d | e |
617
+ * + +---+---+
618
+ * | | f | g |
619
+ * + +---+---+
620
+ * | | h | i |
621
+ * +---+---+---+
622
+ *
623
+ * Splitting cell "a" with `rowspan=4` into 3 cells will create 2 cells with a `rowspan=1` and cell "a" will have `rowspan=2`:
624
+ *
625
+ * +---+---+---+
626
+ * | a | b | c |
627
+ * + +---+---+
628
+ * | | d | e |
629
+ * +---+---+---+
630
+ * | | f | g |
631
+ * +---+---+---+
632
+ * | | h | i |
633
+ * +---+---+---+
634
+ */
635
+ splitCellHorizontally(tableCell, numberOfCells = 2) {
636
+ const model = this.editor.model;
637
+ const tableRow = tableCell.parent;
638
+ const table = tableRow.parent;
639
+ const splitCellRow = table.getChildIndex(tableRow);
640
+ const rowspan = parseInt(tableCell.getAttribute('rowspan') || '1');
641
+ const colspan = parseInt(tableCell.getAttribute('colspan') || '1');
642
+ model.change(writer => {
643
+ // First check - the cell spans over multiple rows so before doing anything else just split this cell.
644
+ if (rowspan > 1) {
645
+ // Cache table map before updating table.
646
+ const tableMap = [...new TableWalker(table, {
647
+ startRow: splitCellRow,
648
+ endRow: splitCellRow + rowspan - 1,
649
+ includeAllSlots: true
650
+ })];
651
+ // Get spans of new (inserted) cells and span to update of split cell.
652
+ const { newCellsSpan, updatedSpan } = breakSpanEvenly(rowspan, numberOfCells);
653
+ updateNumericAttribute('rowspan', updatedSpan, tableCell, writer);
654
+ const { column: cellColumn } = tableMap.find(({ cell }) => cell === tableCell);
655
+ // Each inserted cell will have the same attributes:
656
+ const newCellsAttributes = {};
657
+ // Do not store default value in the model.
658
+ if (newCellsSpan > 1) {
659
+ newCellsAttributes.rowspan = newCellsSpan;
660
+ }
661
+ // Copy colspan of split cell.
662
+ if (colspan > 1) {
663
+ newCellsAttributes.colspan = colspan;
664
+ }
665
+ // Accumulator that stores distance from the last inserted cell span.
666
+ // It helps with evenly splitting larger cell spans (for example 10 cells collapsing into 3 cells).
667
+ // We split these cells into 3, 3, 4 cells and we have to call `createCells` only when distance between
668
+ // these cells is equal or greater than the new cells span size.
669
+ let distanceFromLastCellSpan = 0;
670
+ for (const tableSlot of tableMap) {
671
+ const { column, row } = tableSlot;
672
+ // As both newly created cells and the split cell might have rowspan,
673
+ // the insertion of new cells must go to appropriate rows:
674
+ //
675
+ // 1. It's a row after split cell + it's height.
676
+ const isAfterSplitCell = row >= splitCellRow + updatedSpan;
677
+ // 2. Is on the same column.
678
+ const isOnSameColumn = column === cellColumn;
679
+ // Reset distance from the last cell span if we are on the same column and we exceeded the new cells span size.
680
+ if (distanceFromLastCellSpan >= newCellsSpan && isOnSameColumn) {
681
+ distanceFromLastCellSpan = 0;
682
+ }
683
+ if (isAfterSplitCell && isOnSameColumn) {
684
+ // Create new cells only if the distance from the last cell span is equal or greater than the new cells span.
685
+ if (!distanceFromLastCellSpan) {
686
+ createCells(1, writer, tableSlot.getPositionBefore(), newCellsAttributes);
687
+ }
688
+ // Increase the distance from the last cell span.
689
+ distanceFromLastCellSpan++;
690
+ }
691
+ }
692
+ }
693
+ // Second check - the cell has rowspan of 1 or we need to create more cells than the current cell spans over.
694
+ if (rowspan < numberOfCells) {
695
+ // We already split the cell in check one so here we split to the remaining number of cells only.
696
+ const cellsToInsert = numberOfCells - rowspan;
697
+ // This check is needed since we need to check if there are any cells from previous rows than spans over this cell's row.
698
+ const tableMap = [...new TableWalker(table, { startRow: 0, endRow: splitCellRow })];
699
+ // First step: expand cells.
700
+ for (const { cell, cellHeight, row } of tableMap) {
701
+ // Expand rowspan of cells that are either:
702
+ // - on the same row as current cell,
703
+ // - or are below split cell row and overlaps that row.
704
+ if (cell !== tableCell && row + cellHeight > splitCellRow) {
705
+ const rowspanToSet = cellHeight + cellsToInsert;
706
+ writer.setAttribute('rowspan', rowspanToSet, cell);
707
+ }
708
+ }
709
+ // Second step: create rows with single cell below split cell.
710
+ const newCellsAttributes = {};
711
+ // Copy colspan of split cell.
712
+ if (colspan > 1) {
713
+ newCellsAttributes.colspan = colspan;
714
+ }
715
+ createEmptyRows(writer, table, splitCellRow + 1, cellsToInsert, 1, newCellsAttributes);
716
+ // Update heading section if split cell is in heading section.
717
+ const headingRows = table.getAttribute('headingRows') || 0;
718
+ if (headingRows > splitCellRow) {
719
+ updateNumericAttribute('headingRows', headingRows + cellsToInsert, table, writer);
720
+ }
721
+ }
722
+ });
723
+ }
724
+ /**
725
+ * Returns the number of columns for a given table.
726
+ *
727
+ * ```ts
728
+ * editor.plugins.get( 'TableUtils' ).getColumns( table );
729
+ * ```
730
+ *
731
+ * @param table The table to analyze.
732
+ */
733
+ getColumns(table) {
734
+ // Analyze first row only as all the rows should have the same width.
735
+ // Using the first row without checking if it's a tableRow because we expect
736
+ // that table will have only tableRow model elements at the beginning.
737
+ const row = table.getChild(0);
738
+ return [...row.getChildren()]
739
+ // $marker elements can also be children of a row too (when TrackChanges is on). Don't include them in the count.
740
+ .filter(node => node.is('element', 'tableCell'))
741
+ .reduce((columns, row) => {
742
+ const columnWidth = parseInt(row.getAttribute('colspan') || '1');
743
+ return columns + columnWidth;
744
+ }, 0);
745
+ }
746
+ /**
747
+ * Returns the number of rows for a given table. Any other element present in the table model is omitted.
748
+ *
749
+ * ```ts
750
+ * editor.plugins.get( 'TableUtils' ).getRows( table );
751
+ * ```
752
+ *
753
+ * @param table The table to analyze.
754
+ */
755
+ getRows(table) {
756
+ // Rowspan not included due to #6427.
757
+ return Array.from(table.getChildren())
758
+ .reduce((rowCount, child) => child.is('element', 'tableRow') ? rowCount + 1 : rowCount, 0);
759
+ }
760
+ /**
761
+ * Creates an instance of the table walker.
762
+ *
763
+ * The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
764
+ * It walks row by row and column by column in order to output values defined in the options.
765
+ * By default it will output only the locations that are occupied by a cell. To include also spanned rows and columns,
766
+ * pass the `includeAllSlots` option.
767
+ *
768
+ * @internal
769
+ * @param table A table over which the walker iterates.
770
+ * @param options An object with configuration.
771
+ */
772
+ createTableWalker(table, options = {}) {
773
+ return new TableWalker(table, options);
774
+ }
775
+ /**
776
+ * Returns all model table cells that are fully selected (from the outside)
777
+ * within the provided model selection's ranges.
778
+ *
779
+ * To obtain the cells selected from the inside, use
780
+ * {@link #getTableCellsContainingSelection}.
781
+ */
782
+ getSelectedTableCells(selection) {
783
+ const cells = [];
784
+ for (const range of this.sortRanges(selection.getRanges())) {
785
+ const element = range.getContainedElement();
786
+ if (element && element.is('element', 'tableCell')) {
787
+ cells.push(element);
788
+ }
789
+ }
790
+ return cells;
791
+ }
792
+ /**
793
+ * Sets the number of heading rows for the given `table`.
794
+ *
795
+ * @param writer The model writer.
796
+ * @param table The table model element.
797
+ * @param headingRows The number of heading rows to set.
798
+ * @param options Additional options.
799
+ * @param options.shallow If set to `true` it will only update the `headingRows` attribute
800
+ * without updating the cell types in the table. Default is `false`.
801
+ * @param options.resetFormerHeadingCells If set to `true`, it will check if the rows that are no longer in the heading section
802
+ * should be updated to body cells. Default is `true`.
803
+ * @param options.autoExpand If set to `true`, it will check if the following rows look like a header and expand the heading section.
804
+ * Default is `true`.
805
+ */
806
+ setHeadingRowsCount(writer, table, headingRows, options = {}) {
807
+ const { shallow, resetFormerHeadingCells = true, autoExpand = true } = options;
808
+ const oldHeadingRows = table.getAttribute('headingRows') || 0;
809
+ if (headingRows === oldHeadingRows) {
810
+ return;
811
+ }
812
+ updateNumericAttribute('headingRows', headingRows, table, writer, 0);
813
+ if (shallow || !isTableCellTypeEnabled(this.editor)) {
814
+ return;
815
+ }
816
+ // Set header type to all cells in new heading rows.
817
+ for (const { cell, row, column } of new TableWalker(table, { endRow: headingRows - 1 })) {
818
+ updateTableCellType({
819
+ table,
820
+ writer,
821
+ cell,
822
+ row,
823
+ column
824
+ });
825
+ }
826
+ // If heading rows were reduced, set body type to all cells in rows that are no longer in heading section.
827
+ if (resetFormerHeadingCells && headingRows < oldHeadingRows) {
828
+ for (let row = headingRows; row < oldHeadingRows; row++) {
829
+ // Handle edge case when some cells were already changed to body type manually,
830
+ // before changing heading rows count.
831
+ if (!isEntireCellsLineHeader({ table, row })) {
832
+ break;
833
+ }
834
+ for (const { cell, row: cellRow, column } of new TableWalker(table, { row })) {
835
+ updateTableCellType({
836
+ table,
837
+ writer,
838
+ cell,
839
+ row: cellRow,
840
+ column
841
+ });
842
+ }
843
+ }
844
+ }
845
+ // If following rows looks like header, expand heading rows to cover them.
846
+ if (autoExpand && headingRows > oldHeadingRows) {
847
+ const totalRows = this.getRows(table);
848
+ while (headingRows < totalRows && isEntireCellsLineHeader({ table, row: headingRows })) {
849
+ headingRows++;
850
+ }
851
+ updateNumericAttribute('headingRows', headingRows, table, writer, 0);
852
+ }
853
+ }
854
+ /**
855
+ * Sets the number of heading columns for the given `table`.
856
+ *
857
+ * @param writer The model writer to use.
858
+ * @param table The table model element.
859
+ * @param headingColumns The number of heading columns to set.
860
+ * @param options Additional options.
861
+ * @param options.shallow If set to `true` it will only update the `headingColumns` attribute
862
+ * without updating the cell types in the table. Default is `false`.
863
+ * @param options.resetFormerHeadingCells If set to `true`, it will check if the columns that are no longer in the heading section
864
+ * should be updated to body cells. Default is `true`.
865
+ * @param options.autoExpand If set to `true`, it will check if the following columns look like a header and expand the heading section.
866
+ * Default is `true`.
867
+ */
868
+ setHeadingColumnsCount(writer, table, headingColumns, options = {}) {
869
+ const { shallow, resetFormerHeadingCells = true, autoExpand = true } = options;
870
+ const oldHeadingColumns = table.getAttribute('headingColumns') || 0;
871
+ if (headingColumns === oldHeadingColumns) {
872
+ return;
873
+ }
874
+ updateNumericAttribute('headingColumns', headingColumns, table, writer, 0);
875
+ if (shallow || !isTableCellTypeEnabled(this.editor)) {
876
+ return;
877
+ }
878
+ // Set header type to all cells in new heading columns.
879
+ for (const { cell, row, column } of new TableWalker(table, { endColumn: headingColumns - 1 })) {
880
+ updateTableCellType({
881
+ table,
882
+ writer,
883
+ cell,
884
+ row,
885
+ column
886
+ });
887
+ }
888
+ // If heading columns were reduced, set body type to all cells in columns that are no longer in heading section.
889
+ if (resetFormerHeadingCells && headingColumns < oldHeadingColumns) {
890
+ for (let column = headingColumns; column < oldHeadingColumns; column++) {
891
+ // Handle edge case when some cells were already changed to body type manually,
892
+ // before changing heading columns count.
893
+ if (!isEntireCellsLineHeader({ table, column })) {
894
+ break;
895
+ }
896
+ for (const { cell, row, column: cellColumn } of new TableWalker(table, { column })) {
897
+ updateTableCellType({
898
+ table,
899
+ writer,
900
+ cell,
901
+ row,
902
+ column: cellColumn
903
+ });
904
+ }
905
+ }
906
+ }
907
+ // If following columns looks like header, expand heading columns to cover them.
908
+ if (autoExpand && headingColumns > oldHeadingColumns) {
909
+ const totalColumns = this.getColumns(table);
910
+ while (headingColumns < totalColumns && isEntireCellsLineHeader({ table, column: headingColumns })) {
911
+ headingColumns++;
912
+ }
913
+ updateNumericAttribute('headingColumns', headingColumns, table, writer, 0);
914
+ }
915
+ }
916
+ /**
917
+ * Returns all model table cells that the provided model selection's ranges
918
+ * {@link module:engine/model/range~ModelRange#start} inside.
919
+ *
920
+ * To obtain the cells selected from the outside, use
921
+ * {@link #getSelectedTableCells}.
922
+ */
923
+ getTableCellsContainingSelection(selection) {
924
+ const cells = [];
925
+ for (const range of selection.getRanges()) {
926
+ const cellWithSelection = range.start.findAncestor('tableCell');
927
+ if (cellWithSelection) {
928
+ cells.push(cellWithSelection);
929
+ }
930
+ }
931
+ return cells;
932
+ }
933
+ /**
934
+ * Returns all model table cells that are either completely selected
935
+ * by selection ranges or host selection range
936
+ * {@link module:engine/model/range~ModelRange#start start positions} inside them.
937
+ *
938
+ * Combines {@link #getTableCellsContainingSelection} and
939
+ * {@link #getSelectedTableCells}.
940
+ */
941
+ getSelectionAffectedTableCells(selection) {
942
+ const selectedCells = this.getSelectedTableCells(selection);
943
+ if (selectedCells.length) {
944
+ return selectedCells;
945
+ }
946
+ return this.getTableCellsContainingSelection(selection);
947
+ }
948
+ /**
949
+ * Returns an object with the `first` and `last` row index contained in the given `tableCells`.
950
+ *
951
+ * ```ts
952
+ * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
953
+ *
954
+ * const { first, last } = getRowIndexes( selectedTableCells );
955
+ *
956
+ * console.log( `Selected rows: ${ first } to ${ last }` );
957
+ * ```
958
+ *
959
+ * @returns Returns an object with the `first` and `last` table row indexes.
960
+ */
961
+ getRowIndexes(tableCells) {
962
+ const indexes = tableCells.map(cell => cell.parent.index);
963
+ return this._getFirstLastIndexesObject(indexes);
964
+ }
965
+ /**
966
+ * Returns an object with the `first` and `last` column index contained in the given `tableCells`.
967
+ *
968
+ * ```ts
969
+ * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
970
+ *
971
+ * const { first, last } = getColumnIndexes( selectedTableCells );
972
+ *
973
+ * console.log( `Selected columns: ${ first } to ${ last }` );
974
+ * ```
975
+ *
976
+ * @returns Returns an object with the `first` and `last` table column indexes.
977
+ */
978
+ getColumnIndexes(tableCells) {
979
+ const table = tableCells[0].findAncestor('table');
980
+ const tableMap = [...new TableWalker(table)];
981
+ const indexes = tableMap
982
+ .filter(entry => tableCells.includes(entry.cell))
983
+ .map(entry => entry.column);
984
+ return this._getFirstLastIndexesObject(indexes);
985
+ }
986
+ /**
987
+ * Checks if the selection contains cells that do not exceed rectangular selection.
988
+ *
989
+ * In a table below:
990
+ *
991
+ * ┌───┬───┬───┬───┐
992
+ * │ a │ b │ c │ d │
993
+ * ├───┴───┼───┤ │
994
+ * │ e │ f │ │
995
+ * │ ├───┼───┤
996
+ * │ │ g │ h │
997
+ * └───────┴───┴───┘
998
+ *
999
+ * Valid selections are these which create a solid rectangle (without gaps), such as:
1000
+ * - a, b (two horizontal cells)
1001
+ * - c, f (two vertical cells)
1002
+ * - a, b, e (cell "e" spans over four cells)
1003
+ * - c, d, f (cell d spans over a cell in the row below)
1004
+ *
1005
+ * While an invalid selection would be:
1006
+ * - a, c (the unselected cell "b" creates a gap)
1007
+ * - f, g, h (cell "d" spans over a cell from the row of "f" cell - thus creates a gap)
1008
+ */
1009
+ isSelectionRectangular(selectedTableCells) {
1010
+ if (selectedTableCells.length < 2 || !this._areCellInTheSameTableSection(selectedTableCells)) {
1011
+ return false;
1012
+ }
1013
+ // A valid selection is a fully occupied rectangle composed of table cells.
1014
+ // Below we will calculate the area of a selected table cells and the area of valid selection.
1015
+ // The area of a valid selection is defined by top-left and bottom-right cells.
1016
+ const rows = new Set();
1017
+ const columns = new Set();
1018
+ let areaOfSelectedCells = 0;
1019
+ for (const tableCell of selectedTableCells) {
1020
+ const { row, column } = this.getCellLocation(tableCell);
1021
+ const rowspan = parseInt(tableCell.getAttribute('rowspan')) || 1;
1022
+ const colspan = parseInt(tableCell.getAttribute('colspan')) || 1;
1023
+ // Record row & column indexes of current cell.
1024
+ rows.add(row);
1025
+ columns.add(column);
1026
+ // For cells that spans over multiple rows add also the last row that this cell spans over.
1027
+ if (rowspan > 1) {
1028
+ rows.add(row + rowspan - 1);
1029
+ }
1030
+ // For cells that spans over multiple columns add also the last column that this cell spans over.
1031
+ if (colspan > 1) {
1032
+ columns.add(column + colspan - 1);
1033
+ }
1034
+ areaOfSelectedCells += (rowspan * colspan);
1035
+ }
1036
+ // We can only merge table cells that are in adjacent rows...
1037
+ const areaOfValidSelection = getBiggestRectangleArea(rows, columns);
1038
+ return areaOfValidSelection == areaOfSelectedCells;
1039
+ }
1040
+ /**
1041
+ * Returns array of sorted ranges.
1042
+ */
1043
+ sortRanges(ranges) {
1044
+ return Array.from(ranges).sort(compareRangeOrder);
1045
+ }
1046
+ /**
1047
+ * Helper method to get an object with `first` and `last` indexes from an unsorted array of indexes.
1048
+ */
1049
+ _getFirstLastIndexesObject(indexes) {
1050
+ const allIndexesSorted = indexes.sort((indexA, indexB) => indexA - indexB);
1051
+ const first = allIndexesSorted[0];
1052
+ const last = allIndexesSorted[allIndexesSorted.length - 1];
1053
+ return { first, last };
1054
+ }
1055
+ /**
1056
+ * Checks if the selection does not mix a header (column or row) with other cells.
1057
+ *
1058
+ * For instance, in the table below valid selections consist of cells with the same letter only.
1059
+ * So, a-a (same heading row and column) or d-d (body cells) are valid while c-d or a-b are not.
1060
+ *
1061
+ * header columns
1062
+ * ↓ ↓
1063
+ * ┌───┬───┬───┬───┐
1064
+ * │ a │ a │ b │ b │ ← header row
1065
+ * ├───┼───┼───┼───┤
1066
+ * │ c │ c │ d │ d │
1067
+ * ├───┼───┼───┼───┤
1068
+ * │ c │ c │ d │ d │
1069
+ * └───┴───┴───┴───┘
1070
+ */
1071
+ _areCellInTheSameTableSection(tableCells) {
1072
+ const table = tableCells[0].findAncestor('table');
1073
+ const rowIndexes = this.getRowIndexes(tableCells);
1074
+ const headingRows = parseInt(table.getAttribute('headingRows')) || 0;
1075
+ // Calculating row indexes is a bit cheaper so if this check fails we can't merge.
1076
+ if (!this._areIndexesInSameSection(rowIndexes, headingRows)) {
1077
+ return false;
1078
+ }
1079
+ const columnIndexes = this.getColumnIndexes(tableCells);
1080
+ const headingColumns = parseInt(table.getAttribute('headingColumns')) || 0;
1081
+ // Similarly cells must be in same column section.
1082
+ return this._areIndexesInSameSection(columnIndexes, headingColumns);
1083
+ }
1084
+ /**
1085
+ * Unified check if table rows/columns indexes are in the same heading/body section.
1086
+ */
1087
+ _areIndexesInSameSection({ first, last }, headingSectionSize) {
1088
+ const firstCellIsInHeading = first < headingSectionSize;
1089
+ const lastCellIsInHeading = last < headingSectionSize;
1090
+ return firstCellIsInHeading === lastCellIsInHeading;
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Creates empty rows at the given index in an existing table.
1095
+ *
1096
+ * @param insertAt The row index of row insertion.
1097
+ * @param rows The number of rows to create.
1098
+ * @param tableCellToInsert The number of cells to insert in each row.
1099
+ */
1100
+ function createEmptyRows(writer, table, insertAt, rows, tableCellToInsert, attributes = {}) {
1101
+ const insertedRows = [];
1102
+ for (let i = 0; i < rows; i++) {
1103
+ const tableRow = writer.createElement('tableRow');
1104
+ writer.insert(tableRow, table, insertAt);
1105
+ insertedRows.push(createCells(tableCellToInsert, writer, writer.createPositionAt(tableRow, 'end'), attributes));
1106
+ }
1107
+ return insertedRows;
1108
+ }
1109
+ /**
1110
+ * Creates cells at a given position.
1111
+ *
1112
+ * @param cells The number of cells to create
1113
+ */
1114
+ function createCells(cells, writer, insertPosition, attributes = {}) {
1115
+ const createdCells = [];
1116
+ let currentPosition = insertPosition;
1117
+ for (let i = 0; i < cells; i++) {
1118
+ const cell = createEmptyTableCell(writer, currentPosition, attributes);
1119
+ createdCells.push(cell);
1120
+ currentPosition = writer.createPositionAfter(cell);
1121
+ }
1122
+ return createdCells;
1123
+ }
1124
+ /**
1125
+ * Evenly distributes the span of a cell to a number of provided cells.
1126
+ * The resulting spans will always be integer values.
1127
+ *
1128
+ * For instance breaking a span of 7 into 3 cells will return:
1129
+ *
1130
+ * ```ts
1131
+ * { newCellsSpan: 2, updatedSpan: 3 }
1132
+ * ```
1133
+ *
1134
+ * as two cells will have a span of 2 and the remainder will go the first cell so its span will change to 3.
1135
+ *
1136
+ * @param span The span value do break.
1137
+ * @param numberOfCells The number of resulting spans.
1138
+ */
1139
+ function breakSpanEvenly(span, numberOfCells) {
1140
+ if (span < numberOfCells) {
1141
+ return { newCellsSpan: 1, updatedSpan: 1 };
1142
+ }
1143
+ const newCellsSpan = Math.floor(span / numberOfCells);
1144
+ const updatedSpan = (span - newCellsSpan * numberOfCells) + newCellsSpan;
1145
+ return { newCellsSpan, updatedSpan };
1146
+ }
1147
+ /**
1148
+ * Updates heading columns attribute if removing a row from head section.
1149
+ */
1150
+ function adjustHeadingColumns(table, removedColumnIndexes, writer) {
1151
+ const headingColumns = table.getAttribute('headingColumns') || 0;
1152
+ if (headingColumns && removedColumnIndexes.first < headingColumns) {
1153
+ const headingsRemoved = Math.min(headingColumns - 1 /* Other numbers are 0-based */, removedColumnIndexes.last) -
1154
+ removedColumnIndexes.first + 1;
1155
+ writer.setAttribute('headingColumns', headingColumns - headingsRemoved, table);
1156
+ }
1157
+ }
1158
+ /**
1159
+ * Calculates a new heading rows value for removing rows from heading section.
1160
+ */
1161
+ function updateHeadingRows(table, { first, last }, writer) {
1162
+ const headingRows = table.getAttribute('headingRows') || 0;
1163
+ if (first < headingRows) {
1164
+ const newRows = last < headingRows ? headingRows - (last - first + 1) : first;
1165
+ updateNumericAttribute('headingRows', newRows, table, writer, 0);
1166
+ }
1167
+ }
1168
+ /**
1169
+ * Finds cells that will be:
1170
+ * - trimmed - Cells that are "above" removed rows sections and overlap the removed section - their rowspan must be trimmed.
1171
+ * - moved - Cells from removed rows section might stick out of. These cells are moved to the next row after a removed section.
1172
+ *
1173
+ * Sample table with overlapping & sticking out cells:
1174
+ *
1175
+ * +----+----+----+----+----+
1176
+ * | 00 | 01 | 02 | 03 | 04 |
1177
+ * +----+ + + + +
1178
+ * | 10 | | | | |
1179
+ * +----+----+ + + +
1180
+ * | 20 | 21 | | | | <-- removed row
1181
+ * + + +----+ + +
1182
+ * | | | 32 | | | <-- removed row
1183
+ * +----+ + +----+ +
1184
+ * | 40 | | | 43 | |
1185
+ * +----+----+----+----+----+
1186
+ *
1187
+ * In a table above:
1188
+ * - cells to trim: '02', '03' & '04'.
1189
+ * - cells to move: '21' & '32'.
1190
+ */
1191
+ function getCellsToMoveAndTrimOnRemoveRow(table, { first, last }) {
1192
+ const cellsToMove = new Map();
1193
+ const cellsToTrim = [];
1194
+ for (const { row, column, cellHeight, cell } of new TableWalker(table, { endRow: last })) {
1195
+ const lastRowOfCell = row + cellHeight - 1;
1196
+ const isCellStickingOutFromRemovedRows = row >= first && row <= last && lastRowOfCell > last;
1197
+ if (isCellStickingOutFromRemovedRows) {
1198
+ const rowspanInRemovedSection = last - row + 1;
1199
+ const rowSpanToSet = cellHeight - rowspanInRemovedSection;
1200
+ cellsToMove.set(column, {
1201
+ cell,
1202
+ rowspan: rowSpanToSet
1203
+ });
1204
+ }
1205
+ const isCellOverlappingRemovedRows = row < first && lastRowOfCell >= first;
1206
+ if (isCellOverlappingRemovedRows) {
1207
+ let rowspanAdjustment;
1208
+ // Cell fully covers removed section - trim it by removed rows count.
1209
+ if (lastRowOfCell >= last) {
1210
+ rowspanAdjustment = last - first + 1;
1211
+ }
1212
+ // Cell partially overlaps removed section - calculate cell's span that is in removed section.
1213
+ else {
1214
+ rowspanAdjustment = lastRowOfCell - first + 1;
1215
+ }
1216
+ cellsToTrim.push({
1217
+ cell,
1218
+ rowspan: cellHeight - rowspanAdjustment
1219
+ });
1220
+ }
1221
+ }
1222
+ return { cellsToMove, cellsToTrim };
1223
+ }
1224
+ function moveCellsToRow(table, targetRowIndex, cellsToMove, writer) {
1225
+ const tableWalker = new TableWalker(table, {
1226
+ includeAllSlots: true,
1227
+ row: targetRowIndex
1228
+ });
1229
+ const tableRowMap = [...tableWalker];
1230
+ const row = table.getChild(targetRowIndex);
1231
+ let previousCell;
1232
+ for (const { column, cell, isAnchor } of tableRowMap) {
1233
+ if (cellsToMove.has(column)) {
1234
+ const { cell: cellToMove, rowspan } = cellsToMove.get(column);
1235
+ const targetPosition = previousCell ?
1236
+ writer.createPositionAfter(previousCell) :
1237
+ writer.createPositionAt(row, 0);
1238
+ writer.move(writer.createRangeOn(cellToMove), targetPosition);
1239
+ updateNumericAttribute('rowspan', rowspan, cellToMove, writer);
1240
+ previousCell = cellToMove;
1241
+ }
1242
+ else if (isAnchor) {
1243
+ // If cell is spanned then `cell` holds reference to overlapping cell. See ckeditor/ckeditor5#6502.
1244
+ previousCell = cell;
1245
+ }
1246
+ }
1247
+ }
1248
+ function compareRangeOrder(rangeA, rangeB) {
1249
+ // Since table cell ranges are disjoint, it's enough to check their start positions.
1250
+ const posA = rangeA.start;
1251
+ const posB = rangeB.start;
1252
+ // Checking for equal position (returning 0) is not needed because this would be either:
1253
+ // a. Intersecting range (not allowed by model)
1254
+ // b. Collapsed range on the same position (allowed by model but should not happen).
1255
+ return posA.isBefore(posB) ? -1 : 1;
1256
+ }
1257
+ /**
1258
+ * Calculates the area of a maximum rectangle that can span over the provided row & column indexes.
1259
+ */
1260
+ function getBiggestRectangleArea(rows, columns) {
1261
+ const rowsIndexes = Array.from(rows.values());
1262
+ const columnIndexes = Array.from(columns.values());
1263
+ const lastRow = Math.max(...rowsIndexes);
1264
+ const firstRow = Math.min(...rowsIndexes);
1265
+ const lastColumn = Math.max(...columnIndexes);
1266
+ const firstColumn = Math.min(...columnIndexes);
1267
+ return (lastRow - firstRow + 1) * (lastColumn - firstColumn + 1);
1268
+ }
1269
+ /**
1270
+ * Updates the `tableCellType` attribute of a table cell based on its position in the table
1271
+ * and the table's `headingRows` and `headingColumns` attributes.
1272
+ */
1273
+ function updateTableCellType({ writer, table, row, column, cell }) {
1274
+ const headingRows = table.getAttribute('headingRows') || 0;
1275
+ const headingColumns = table.getAttribute('headingColumns') || 0;
1276
+ if (row >= headingRows && column >= headingColumns) {
1277
+ writer.removeAttribute('tableCellType', cell);
1278
+ }
1279
+ else {
1280
+ writer.setAttribute('tableCellType', 'header', cell);
1281
+ }
1282
+ }