@ckeditor/ckeditor5-table 36.0.0 → 37.0.0-alpha.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 (166) hide show
  1. package/build/table.js +1 -1
  2. package/package.json +34 -29
  3. package/src/commands/insertcolumncommand.d.ts +61 -0
  4. package/src/commands/insertcolumncommand.js +45 -60
  5. package/src/commands/insertrowcommand.d.ts +60 -0
  6. package/src/commands/insertrowcommand.js +44 -59
  7. package/src/commands/inserttablecommand.d.ts +50 -0
  8. package/src/commands/inserttablecommand.js +51 -68
  9. package/src/commands/mergecellcommand.d.ts +76 -0
  10. package/src/commands/mergecellcommand.js +169 -244
  11. package/src/commands/mergecellscommand.d.ts +33 -0
  12. package/src/commands/mergecellscommand.js +72 -101
  13. package/src/commands/removecolumncommand.d.ts +34 -0
  14. package/src/commands/removecolumncommand.js +88 -102
  15. package/src/commands/removerowcommand.d.ts +34 -0
  16. package/src/commands/removerowcommand.js +63 -80
  17. package/src/commands/selectcolumncommand.d.ts +38 -0
  18. package/src/commands/selectcolumncommand.js +41 -54
  19. package/src/commands/selectrowcommand.d.ts +38 -0
  20. package/src/commands/selectrowcommand.js +38 -48
  21. package/src/commands/setheadercolumncommand.d.ts +55 -0
  22. package/src/commands/setheadercolumncommand.js +48 -73
  23. package/src/commands/setheaderrowcommand.d.ts +58 -0
  24. package/src/commands/setheaderrowcommand.js +56 -85
  25. package/src/commands/splitcellcommand.d.ts +49 -0
  26. package/src/commands/splitcellcommand.js +35 -49
  27. package/src/converters/downcast.d.ts +63 -0
  28. package/src/converters/downcast.js +98 -130
  29. package/src/converters/table-caption-post-fixer.d.ts +20 -0
  30. package/src/converters/table-caption-post-fixer.js +36 -52
  31. package/src/converters/table-cell-paragraph-post-fixer.d.ts +32 -0
  32. package/src/converters/table-cell-paragraph-post-fixer.js +88 -119
  33. package/src/converters/table-cell-refresh-handler.d.ts +18 -0
  34. package/src/converters/table-cell-refresh-handler.js +29 -48
  35. package/src/converters/table-headings-refresh-handler.d.ts +17 -0
  36. package/src/converters/table-headings-refresh-handler.js +35 -54
  37. package/src/converters/table-layout-post-fixer.d.ts +226 -0
  38. package/src/converters/table-layout-post-fixer.js +276 -313
  39. package/src/converters/tableproperties.d.ts +54 -0
  40. package/src/converters/tableproperties.js +136 -168
  41. package/src/converters/upcasttable.d.ts +49 -0
  42. package/src/converters/upcasttable.js +196 -251
  43. package/src/index.d.ts +29 -0
  44. package/src/index.js +0 -2
  45. package/src/plaintableoutput.d.ts +30 -0
  46. package/src/plaintableoutput.js +107 -135
  47. package/src/table.d.ts +38 -0
  48. package/src/table.js +12 -88
  49. package/src/tablecaption/tablecaptionediting.d.ts +68 -0
  50. package/src/tablecaption/tablecaptionediting.js +104 -135
  51. package/src/tablecaption/tablecaptionui.d.ts +26 -0
  52. package/src/tablecaption/tablecaptionui.js +42 -58
  53. package/src/tablecaption/toggletablecaptioncommand.d.ts +73 -0
  54. package/src/tablecaption/toggletablecaptioncommand.js +77 -92
  55. package/src/tablecaption/utils.d.ts +42 -0
  56. package/src/tablecaption/utils.js +35 -61
  57. package/src/tablecaption.d.ts +27 -0
  58. package/src/tablecaption.js +12 -19
  59. package/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.d.ts +37 -0
  60. package/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js +14 -20
  61. package/src/tablecellproperties/commands/tablecellbordercolorcommand.d.ts +42 -0
  62. package/src/tablecellproperties/commands/tablecellbordercolorcommand.js +27 -37
  63. package/src/tablecellproperties/commands/tablecellborderstylecommand.d.ts +42 -0
  64. package/src/tablecellproperties/commands/tablecellborderstylecommand.js +27 -37
  65. package/src/tablecellproperties/commands/tablecellborderwidthcommand.d.ts +56 -0
  66. package/src/tablecellproperties/commands/tablecellborderwidthcommand.js +42 -53
  67. package/src/tablecellproperties/commands/tablecellheightcommand.d.ts +51 -0
  68. package/src/tablecellproperties/commands/tablecellheightcommand.js +29 -36
  69. package/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.d.ts +37 -0
  70. package/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js +14 -20
  71. package/src/tablecellproperties/commands/tablecellpaddingcommand.d.ts +56 -0
  72. package/src/tablecellproperties/commands/tablecellpaddingcommand.js +42 -53
  73. package/src/tablecellproperties/commands/tablecellpropertycommand.d.ts +62 -0
  74. package/src/tablecellproperties/commands/tablecellpropertycommand.js +77 -122
  75. package/src/tablecellproperties/commands/tablecellverticalalignmentcommand.d.ts +45 -0
  76. package/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js +14 -20
  77. package/src/tablecellproperties/tablecellpropertiesediting.d.ts +47 -0
  78. package/src/tablecellproperties/tablecellpropertiesediting.js +194 -236
  79. package/src/tablecellproperties/tablecellpropertiesui.d.ts +117 -0
  80. package/src/tablecellproperties/tablecellpropertiesui.js +303 -456
  81. package/src/tablecellproperties/ui/tablecellpropertiesview.d.ts +227 -0
  82. package/src/tablecellproperties/ui/tablecellpropertiesview.js +509 -844
  83. package/src/tablecellproperties.d.ts +33 -0
  84. package/src/tablecellproperties.js +12 -98
  85. package/src/tablecellwidth/commands/tablecellwidthcommand.d.ts +51 -0
  86. package/src/tablecellwidth/commands/tablecellwidthcommand.js +29 -35
  87. package/src/tablecellwidth/tablecellwidthediting.d.ts +34 -0
  88. package/src/tablecellwidth/tablecellwidthediting.js +26 -38
  89. package/src/tableclipboard.d.ts +68 -0
  90. package/src/tableclipboard.js +429 -568
  91. package/src/tablecolumnresize/constants.d.ts +20 -0
  92. package/src/tablecolumnresize/constants.js +0 -10
  93. package/src/tablecolumnresize/converters.d.ts +18 -0
  94. package/src/tablecolumnresize/converters.js +35 -119
  95. package/src/tablecolumnresize/tablecolumnresizeediting.d.ts +142 -0
  96. package/src/tablecolumnresize/tablecolumnresizeediting.js +545 -711
  97. package/src/tablecolumnresize/tablewidthscommand.d.ts +38 -0
  98. package/src/tablecolumnresize/tablewidthscommand.js +61 -0
  99. package/src/tablecolumnresize/utils.d.ts +141 -0
  100. package/src/tablecolumnresize/utils.js +256 -233
  101. package/src/tablecolumnresize.d.ts +29 -0
  102. package/src/tablecolumnresize.js +12 -19
  103. package/src/tableconfig.d.ts +341 -0
  104. package/src/tableconfig.js +5 -0
  105. package/src/tableediting.d.ts +102 -0
  106. package/src/tableediting.js +157 -176
  107. package/src/tablekeyboard.d.ts +68 -0
  108. package/src/tablekeyboard.js +261 -344
  109. package/src/tablemouse/mouseeventsobserver.d.ts +62 -0
  110. package/src/tablemouse/mouseeventsobserver.js +12 -49
  111. package/src/tablemouse.d.ts +51 -0
  112. package/src/tablemouse.js +154 -202
  113. package/src/tableproperties/commands/tablealignmentcommand.d.ts +37 -0
  114. package/src/tableproperties/commands/tablealignmentcommand.js +14 -20
  115. package/src/tableproperties/commands/tablebackgroundcolorcommand.d.ts +37 -0
  116. package/src/tableproperties/commands/tablebackgroundcolorcommand.js +14 -20
  117. package/src/tableproperties/commands/tablebordercolorcommand.d.ts +42 -0
  118. package/src/tableproperties/commands/tablebordercolorcommand.js +27 -37
  119. package/src/tableproperties/commands/tableborderstylecommand.d.ts +42 -0
  120. package/src/tableproperties/commands/tableborderstylecommand.js +27 -37
  121. package/src/tableproperties/commands/tableborderwidthcommand.d.ts +56 -0
  122. package/src/tableproperties/commands/tableborderwidthcommand.js +42 -53
  123. package/src/tableproperties/commands/tableheightcommand.d.ts +51 -0
  124. package/src/tableproperties/commands/tableheightcommand.js +29 -33
  125. package/src/tableproperties/commands/tablepropertycommand.d.ts +61 -0
  126. package/src/tableproperties/commands/tablepropertycommand.js +68 -112
  127. package/src/tableproperties/commands/tablewidthcommand.d.ts +51 -0
  128. package/src/tableproperties/commands/tablewidthcommand.js +29 -33
  129. package/src/tableproperties/tablepropertiesediting.d.ts +45 -0
  130. package/src/tableproperties/tablepropertiesediting.js +164 -210
  131. package/src/tableproperties/tablepropertiesui.d.ts +119 -0
  132. package/src/tableproperties/tablepropertiesui.js +294 -439
  133. package/src/tableproperties/ui/tablepropertiesview.d.ts +203 -0
  134. package/src/tableproperties/ui/tablepropertiesview.js +427 -718
  135. package/src/tableproperties.d.ts +33 -0
  136. package/src/tableproperties.js +12 -95
  137. package/src/tableselection.d.ts +111 -0
  138. package/src/tableselection.js +279 -376
  139. package/src/tabletoolbar.d.ts +37 -0
  140. package/src/tabletoolbar.js +39 -92
  141. package/src/tableui.d.ts +58 -0
  142. package/src/tableui.js +281 -338
  143. package/src/tableutils.d.ts +453 -0
  144. package/src/tableutils.js +1015 -1229
  145. package/src/tablewalker.d.ts +323 -0
  146. package/src/tablewalker.js +308 -548
  147. package/src/ui/colorinputview.d.ts +143 -0
  148. package/src/ui/colorinputview.js +229 -366
  149. package/src/ui/formrowview.d.ts +61 -0
  150. package/src/ui/formrowview.js +38 -84
  151. package/src/ui/inserttableview.d.ts +77 -0
  152. package/src/ui/inserttableview.js +152 -242
  153. package/src/utils/common.d.ts +42 -0
  154. package/src/utils/common.js +33 -57
  155. package/src/utils/structure.d.ts +245 -0
  156. package/src/utils/structure.js +261 -379
  157. package/src/utils/table-properties.d.ts +67 -0
  158. package/src/utils/table-properties.js +60 -81
  159. package/src/utils/ui/contextualballoon.d.ts +34 -0
  160. package/src/utils/ui/contextualballoon.js +70 -89
  161. package/src/utils/ui/table-properties.d.ts +193 -0
  162. package/src/utils/ui/table-properties.js +259 -319
  163. package/src/utils/ui/widget.d.ts +16 -0
  164. package/src/utils/ui/widget.js +24 -46
  165. package/src/tablecolumnresize/tablecolumnwidthscommand.js +0 -55
  166. package/src/tablecolumnresize/tablewidthresizecommand.js +0 -65
@@ -2,729 +2,563 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module table/tablecolumnresize/tablecolumnresizeediting
8
7
  */
9
-
10
- import { throttle } from 'lodash-es';
8
+ import { throttle, isEqual } from 'lodash-es';
11
9
  import { global, DomEmitterMixin } from 'ckeditor5/src/utils';
12
10
  import { Plugin } from 'ckeditor5/src/core';
13
-
14
11
  import MouseEventsObserver from '../../src/tablemouse/mouseeventsobserver';
15
12
  import TableEditing from '../tableediting';
16
13
  import TableUtils from '../tableutils';
17
14
  import TableWalker from '../tablewalker';
18
-
19
- import TableWidthResizeCommand from './tablewidthresizecommand';
20
- import TableColumnWidthsCommand from './tablecolumnwidthscommand';
21
-
22
- import {
23
- upcastColgroupElement,
24
- downcastTableColumnWidthsAttribute
25
- } from './converters';
26
-
27
- import {
28
- clamp,
29
- createFilledArray,
30
- sumArray,
31
- getColumnEdgesIndexes,
32
- getChangedResizedTables,
33
- getColumnMinWidthAsPercentage,
34
- getElementWidthInPixels,
35
- getTableWidthInPixels,
36
- normalizeColumnWidths,
37
- toPrecision,
38
- getDomCellOuterWidth
39
- } from './utils';
40
-
15
+ import TableWidthsCommand from './tablewidthscommand';
16
+ import { downcastTableResizedClass, upcastColgroupElement } from './converters';
17
+ import { clamp, createFilledArray, sumArray, getColumnEdgesIndexes, getChangedResizedTables, getColumnMinWidthAsPercentage, getElementWidthInPixels, getTableWidthInPixels, normalizeColumnWidths, toPrecision, getDomCellOuterWidth, updateColumnElements, getColumnGroupElement, getTableColumnElements, getTableColumnsWidths } from './utils';
41
18
  import { COLUMN_MIN_WIDTH_IN_PIXELS } from './constants';
42
-
43
19
  /**
44
20
  * The table column resize editing plugin.
45
- *
46
- * @extends module:core/plugin~Plugin
47
21
  */
48
22
  export default class TableColumnResizeEditing extends Plugin {
49
- /**
50
- * @inheritDoc
51
- */
52
- static get requires() {
53
- return [ TableEditing, TableUtils ];
54
- }
55
-
56
- /**
57
- * @inheritDoc
58
- */
59
- static get pluginName() {
60
- return 'TableColumnResizeEditing';
61
- }
62
-
63
- /**
64
- * @inheritDoc
65
- */
66
- constructor( editor ) {
67
- super( editor );
68
-
69
- /**
70
- * A flag indicating if the column resizing is in progress.
71
- *
72
- * @private
73
- * @member {Boolean}
74
- */
75
- this._isResizingActive = false;
76
-
77
- /**
78
- * A flag indicating if the column resizing is allowed. It is not allowed if the editor is in read-only
79
- * or comments-only mode or the `TableColumnResize` plugin is disabled.
80
- *
81
- * @private
82
- * @observable
83
- * @member {Boolean}
84
- */
85
- this.set( '_isResizingAllowed', true );
86
-
87
- /**
88
- * A temporary storage for the required data needed to correctly calculate the widths of the resized columns. This storage is
89
- * initialized when column resizing begins, and is purged upon completion.
90
- *
91
- * @private
92
- * @member {Object|null}
93
- */
94
- this._resizingData = null;
95
-
96
- /**
97
- * DOM emitter.
98
- *
99
- * @private
100
- * @member {DomEmitterMixin}
101
- */
102
- this._domEmitter = Object.create( DomEmitterMixin );
103
-
104
- /**
105
- * A local reference to the {@link module:table/tableutils~TableUtils} plugin.
106
- *
107
- * @private
108
- * @member {module:table/tableutils~TableUtils}
109
- */
110
- this._tableUtilsPlugin = editor.plugins.get( 'TableUtils' );
111
-
112
- this.on( 'change:_isResizingAllowed', ( evt, name, value ) => {
113
- // Toggling the `ck-column-resize_disabled` class shows and hides the resizers through CSS.
114
- editor.editing.view.change( writer => {
115
- writer[ value ? 'removeClass' : 'addClass' ]( 'ck-column-resize_disabled', editor.editing.view.document.getRoot() );
116
- } );
117
- } );
118
- }
119
-
120
- /**
121
- * @inheritDoc
122
- */
123
- init() {
124
- this._extendSchema();
125
- this._registerPostFixer();
126
- this._registerConverters();
127
- this._registerResizingListeners();
128
- this._registerColgroupFixer();
129
- this._registerResizerInserter();
130
-
131
- const editor = this.editor;
132
- const columnResizePlugin = editor.plugins.get( 'TableColumnResize' );
133
-
134
- editor.commands.add( 'resizeTableWidth', new TableWidthResizeCommand( editor ) );
135
- editor.commands.add( 'resizeColumnWidths', new TableColumnWidthsCommand( editor ) );
136
-
137
- const resizeTableWidthCommand = editor.commands.get( 'resizeTableWidth' );
138
- const resizeColumnWidthsCommand = editor.commands.get( 'resizeColumnWidths' );
139
-
140
- // Currently the states of column resize and table resize (which is actually the last column resize) features
141
- // are bound together. They can be separated in the future by adding distinct listeners and applying
142
- // different CSS classes (e.g. `ck-column-resize_disabled` and `ck-table-resize_disabled`) to the editor root.
143
- // See #12148 for the details.
144
- this.bind( '_isResizingAllowed' ).to(
145
- editor, 'isReadOnly',
146
- columnResizePlugin, 'isEnabled',
147
- resizeTableWidthCommand, 'isEnabled',
148
- resizeColumnWidthsCommand, 'isEnabled',
149
- ( isEditorReadOnly, isPluginEnabled, isResizeTableWidthCommandEnabled, isResizeColumnWidthsCommandEnabled ) =>
150
- !isEditorReadOnly && isPluginEnabled && isResizeTableWidthCommandEnabled && isResizeColumnWidthsCommandEnabled
151
- );
152
- }
153
-
154
- /**
155
- * @inheritDoc
156
- */
157
- destroy() {
158
- this._domEmitter.stopListening();
159
- super.destroy();
160
- }
161
-
162
- /**
163
- * Registers new attributes for a table model element.
164
- *
165
- * @private
166
- */
167
- _extendSchema() {
168
- this.editor.model.schema.extend( 'table', {
169
- allowAttributes: [ 'tableWidth', 'columnWidths' ]
170
- } );
171
- }
172
-
173
- /**
174
- * Registers table column resize post-fixer.
175
- *
176
- * It checks if the change from the differ concerns a table-related element or attribute. For detected changes it:
177
- * * Adjusts the `columnWidths` attribute to guarantee that the sum of the widths from all columns is 100%.
178
- * * Checks if the `columnWidths` attribute gets updated accordingly after columns have been added or removed.
179
- *
180
- * @private
181
- */
182
- _registerPostFixer() {
183
- const editor = this.editor;
184
- const model = editor.model;
185
-
186
- model.document.registerPostFixer( writer => {
187
- let changed = false;
188
-
189
- for ( const table of getChangedResizedTables( model ) ) {
190
- // (1) Adjust the `columnWidths` attribute to guarantee that the sum of the widths from all columns is 100%.
191
- const columnWidths = normalizeColumnWidths( table.getAttribute( 'columnWidths' ).split( ',' ) );
192
-
193
- // (2) If the number of columns has changed, then we need to adjust the widths of the affected columns.
194
- adjustColumnWidths( columnWidths, table, this );
195
-
196
- const columnWidthsAttribute = columnWidths.map( width => `${ width }%` ).join( ',' );
197
-
198
- if ( table.getAttribute( 'columnWidths' ) === columnWidthsAttribute ) {
199
- continue;
200
- }
201
-
202
- writer.setAttribute( 'columnWidths', columnWidthsAttribute, table );
203
-
204
- changed = true;
205
- }
206
-
207
- return changed;
208
- } );
209
-
210
- // Adjusts if necessary the `columnWidths` in case if the number of column has changed.
211
- //
212
- // @private
213
- // @param {Array.<Number>} columnWidths Note: this array **may be modified** by the function.
214
- // @param {module:engine/model/element~Element} table Table to be checked.
215
- // @param {module:table/tablecolumnresize/tablecolumnresizeediting~TableColumnResizeEditing} plugin
216
- function adjustColumnWidths( columnWidths, table, plugin ) {
217
- const newTableColumnsCount = plugin._tableUtilsPlugin.getColumns( table );
218
- const columnsCountDelta = newTableColumnsCount - columnWidths.length;
219
-
220
- if ( columnsCountDelta === 0 ) {
221
- return;
222
- }
223
-
224
- // Collect all cells that are affected by the change.
225
- const cellSet = getAffectedCells( plugin.editor.model.document.differ, table );
226
-
227
- for ( const cell of cellSet ) {
228
- const currentColumnsDelta = newTableColumnsCount - columnWidths.length;
229
-
230
- if ( currentColumnsDelta === 0 ) {
231
- continue;
232
- }
233
-
234
- // If the column count in the table changed, adjust the widths of the affected columns.
235
- const hasMoreColumns = currentColumnsDelta > 0;
236
- const currentColumnIndex = plugin._tableUtilsPlugin.getCellLocation( cell ).column;
237
-
238
- if ( hasMoreColumns ) {
239
- const columnMinWidthAsPercentage = getColumnMinWidthAsPercentage( table, plugin.editor );
240
- const columnWidthsToInsert = createFilledArray( currentColumnsDelta, columnMinWidthAsPercentage );
241
-
242
- columnWidths.splice( currentColumnIndex, 0, ...columnWidthsToInsert );
243
- } else {
244
- // Moves the widths of the removed columns to the preceding one.
245
- // Other editors either reduce the width of the whole table or adjust the widths
246
- // proportionally, so change of this behavior can be considered in the future.
247
- const removedColumnWidths = columnWidths.splice( currentColumnIndex, Math.abs( currentColumnsDelta ) );
248
-
249
- columnWidths[ currentColumnIndex ] += sumArray( removedColumnWidths );
250
- }
251
- }
252
- }
253
-
254
- // Returns a set of cells that have been changed in a given table.
255
- //
256
- // @private
257
- // @param {module:engine/model/differ~Differ} differ
258
- // @param {module:engine/model/element~Element} table
259
- // @returns {Set.<module:engine/model/element~Element>}
260
- function getAffectedCells( differ, table ) {
261
- const cellSet = new Set();
262
-
263
- for ( const change of differ.getChanges() ) {
264
- if (
265
- change.type == 'insert' &&
266
- change.position.nodeAfter &&
267
- change.position.nodeAfter.name == 'tableCell' &&
268
- change.position.nodeAfter.getAncestors().includes( table )
269
- ) {
270
- cellSet.add( change.position.nodeAfter );
271
- } else if ( change.type == 'remove' ) {
272
- // If the first cell was removed, use the node after the change position instead.
273
- const referenceNode = change.position.nodeBefore || change.position.nodeAfter;
274
-
275
- if ( referenceNode.name == 'tableCell' && referenceNode.getAncestors().includes( table ) ) {
276
- cellSet.add( referenceNode );
277
- }
278
- }
279
- }
280
-
281
- return cellSet;
282
- }
283
- }
284
-
285
- /**
286
- * Registers table column resize converters.
287
- *
288
- * @private
289
- */
290
- _registerConverters() {
291
- const editor = this.editor;
292
- const conversion = editor.conversion;
293
- const widthStyleToTableWidthDefinition = {
294
- view: {
295
- name: 'figure',
296
- key: 'style',
297
- value: {
298
- width: /[\s\S]+/
299
- }
300
- },
301
- model: {
302
- name: 'table',
303
- key: 'tableWidth',
304
- value: viewElement => viewElement.getStyle( 'width' )
305
- }
306
- };
307
- const tableWidthToWidthStyleDefinition = {
308
- model: {
309
- name: 'table',
310
- key: 'tableWidth'
311
- },
312
- view: width => ( {
313
- name: 'figure',
314
- key: 'style',
315
- value: {
316
- width
317
- }
318
- } )
319
- };
320
-
321
- conversion.for( 'upcast' ).attributeToAttribute( widthStyleToTableWidthDefinition );
322
- conversion.for( 'upcast' ).add( upcastColgroupElement( this._tableUtilsPlugin ) );
323
-
324
- conversion.for( 'downcast' ).attributeToAttribute( tableWidthToWidthStyleDefinition );
325
- conversion.for( 'downcast' ).add( downcastTableColumnWidthsAttribute() );
326
- }
327
-
328
- /**
329
- * Registers listeners to handle resizing process.
330
- *
331
- * @private
332
- */
333
- _registerResizingListeners() {
334
- const editingView = this.editor.editing.view;
335
-
336
- editingView.addObserver( MouseEventsObserver );
337
- editingView.document.on( 'mousedown', this._onMouseDownHandler.bind( this ), { priority: 'high' } );
338
-
339
- this._domEmitter.listenTo( global.window.document, 'mousemove', throttle( this._onMouseMoveHandler.bind( this ), 50 ) );
340
- this._domEmitter.listenTo( global.window.document, 'mouseup', this._onMouseUpHandler.bind( this ) );
341
- }
342
-
343
- /**
344
- * Handles the `mousedown` event on column resizer element:
345
- * * calculates the initial column pixel widths,
346
- * * inserts the `<colgroup>` element if it is not present in the `<table>`,
347
- * * puts the necessary data in the temporary storage,
348
- * * applies the attributes to the `<table>` view element.
349
- *
350
- * @private
351
- * @param {module:utils/eventinfo~EventInfo} eventInfo An object containing information about the fired event.
352
- * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData The data related to the DOM event.
353
- */
354
- _onMouseDownHandler( eventInfo, domEventData ) {
355
- const target = domEventData.target;
356
-
357
- if ( !target.hasClass( 'ck-table-column-resizer' ) ) {
358
- return;
359
- }
360
-
361
- if ( !this._isResizingAllowed ) {
362
- return;
363
- }
364
-
365
- domEventData.preventDefault();
366
- eventInfo.stop();
367
-
368
- const editor = this.editor;
369
- const modelTable = editor.editing.mapper.toModelElement( target.findAncestor( 'figure' ) );
370
-
371
- // The column widths are calculated upon mousedown to allow lazy applying the `columnWidths` attribute on the table.
372
- const columnWidthsInPx = _calculateDomColumnWidths( modelTable, this._tableUtilsPlugin, editor );
373
- const viewTable = target.findAncestor( 'table' );
374
- const editingView = editor.editing.view;
375
-
376
- // Insert colgroup for the table that is resized for the first time.
377
- if ( ![ ...viewTable.getChildren() ].find( viewCol => viewCol.is( 'element', 'colgroup' ) ) ) {
378
- editingView.change( viewWriter => {
379
- _insertColgroupElement( viewWriter, columnWidthsInPx, viewTable );
380
- } );
381
- }
382
-
383
- this._isResizingActive = true;
384
- this._resizingData = this._getResizingData( domEventData, columnWidthsInPx );
385
-
386
- // At this point we change only the editor view - we don't want other users to see our changes yet,
387
- // so we can't apply them in the model.
388
- editingView.change( writer => _applyResizingAttributesToTable( writer, viewTable, this._resizingData ) );
389
-
390
- // Calculates the DOM columns' widths. It is done by taking the width of the widest cell
391
- // from each table column (we rely on the {@link module:table/tablewalker~TableWalker}
392
- // to determine which column the cell belongs to).
393
- //
394
- // @private
395
- // @param {module:engine/model/element~Element} modelTable A table which columns should be measured.
396
- // @param {module:table/tableutils~TableUtils} tableUtils The Table Utils plugin instance.
397
- // @param {module:core/editor/editor~Editor} editor The editor instance.
398
- // @returns {Array.<Number>} Columns' widths expressed in pixels (without unit).
399
- function _calculateDomColumnWidths( modelTable, tableUtilsPlugin, editor ) {
400
- const columnWidthsInPx = Array( tableUtilsPlugin.getColumns( modelTable ) );
401
- const tableWalker = new TableWalker( modelTable );
402
-
403
- for ( const cellSlot of tableWalker ) {
404
- const viewCell = editor.editing.mapper.toViewElement( cellSlot.cell );
405
- const domCell = editor.editing.view.domConverter.mapViewToDom( viewCell );
406
- const domCellWidth = getDomCellOuterWidth( domCell );
407
-
408
- if ( !columnWidthsInPx[ cellSlot.column ] || domCellWidth < columnWidthsInPx[ cellSlot.column ] ) {
409
- columnWidthsInPx[ cellSlot.column ] = toPrecision( domCellWidth );
410
- }
411
- }
412
-
413
- return columnWidthsInPx;
414
- }
415
-
416
- // Creates a `<colgroup>` element with `<col>`s and inserts it into a given view table.
417
- //
418
- // @private
419
- // @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter A writer instance.
420
- // @param {Array.<Number>} columnWidthsInPx Column widths.
421
- // @param {module:engine/view/element~Element} viewTable A table view element.
422
- function _insertColgroupElement( viewWriter, columnWidthsInPx, viewTable ) {
423
- const colgroup = viewWriter.createContainerElement( 'colgroup' );
424
-
425
- for ( let i = 0; i < columnWidthsInPx.length; i++ ) {
426
- const viewColElement = viewWriter.createEmptyElement( 'col' );
427
- const columnWidthInPc = `${ toPrecision( columnWidthsInPx[ i ] / sumArray( columnWidthsInPx ) * 100 ) }%`;
428
-
429
- viewWriter.setStyle( 'width', columnWidthInPc, viewColElement );
430
- viewWriter.insert( viewWriter.createPositionAt( colgroup, 'end' ), viewColElement );
431
- }
432
-
433
- viewWriter.insert( viewWriter.createPositionAt( viewTable, 'start' ), colgroup );
434
- }
435
-
436
- // Applies the style and classes to the view table as the resizing begun.
437
- //
438
- // @private
439
- // @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter A writer instance.
440
- // @param {module:engine/view/element~Element} viewTable A table containing the clicked resizer.
441
- // @param {Object} resizingData Data related to the resizing.
442
- function _applyResizingAttributesToTable( viewWriter, viewTable, resizingData ) {
443
- const figureInitialPcWidth = resizingData.widths.viewFigureWidth / resizingData.widths.viewFigureParentWidth;
444
-
445
- viewWriter.addClass( 'ck-table-resized', viewTable );
446
- viewWriter.addClass( 'ck-table-column-resizer__active', resizingData.elements.viewResizer );
447
- viewWriter.setStyle( 'width', `${ toPrecision( figureInitialPcWidth * 100 ) }%`, viewTable.findAncestor( 'figure' ) );
448
- }
449
- }
450
-
451
- /**
452
- * Handles the `mousemove` event.
453
- * * If resizing process is not in progress, it does nothing.
454
- * * If resizing is active but not allowed, it stops the resizing process instantly calling the `mousedown` event handler.
455
- * * Otherwise it dynamically updates the widths of the resized columns.
456
- *
457
- * @private
458
- * @param {module:utils/eventinfo~EventInfo} eventInfo An object containing information about the fired event.
459
- * @param {Event} mouseEventData The native DOM event.
460
- */
461
- _onMouseMoveHandler( eventInfo, mouseEventData ) {
462
- if ( !this._isResizingActive ) {
463
- return;
464
- }
465
-
466
- if ( !this._isResizingAllowed ) {
467
- this._onMouseUpHandler();
468
-
469
- return;
470
- }
471
-
472
- const {
473
- columnPosition,
474
- flags: {
475
- isRightEdge,
476
- isTableCentered,
477
- isLtrContent
478
- },
479
- elements: {
480
- viewFigure,
481
- viewLeftColumn,
482
- viewRightColumn
483
- },
484
- widths: {
485
- viewFigureParentWidth,
486
- tableWidth,
487
- leftColumnWidth,
488
- rightColumnWidth
489
- }
490
- } = this._resizingData;
491
-
492
- const dxLowerBound = -leftColumnWidth + COLUMN_MIN_WIDTH_IN_PIXELS;
493
-
494
- const dxUpperBound = isRightEdge ?
495
- viewFigureParentWidth - tableWidth :
496
- rightColumnWidth - COLUMN_MIN_WIDTH_IN_PIXELS;
497
-
498
- // The multiplier is needed for calculating the proper movement offset:
499
- // - it should negate the sign if content language direction is right-to-left,
500
- // - it should double the offset if the table edge is resized and table is centered.
501
- const multiplier = ( isLtrContent ? 1 : -1 ) * ( isRightEdge && isTableCentered ? 2 : 1 );
502
-
503
- const dx = clamp(
504
- ( mouseEventData.clientX - columnPosition ) * multiplier,
505
- Math.min( dxLowerBound, 0 ),
506
- Math.max( dxUpperBound, 0 )
507
- );
508
-
509
- if ( dx === 0 ) {
510
- return;
511
- }
512
-
513
- this.editor.editing.view.change( writer => {
514
- const leftColumnWidthAsPercentage = toPrecision( ( leftColumnWidth + dx ) * 100 / tableWidth );
515
-
516
- writer.setStyle( 'width', `${ leftColumnWidthAsPercentage }%`, viewLeftColumn );
517
-
518
- if ( isRightEdge ) {
519
- const tableWidthAsPercentage = toPrecision( ( tableWidth + dx ) * 100 / viewFigureParentWidth );
520
-
521
- writer.setStyle( 'width', `${ tableWidthAsPercentage }%`, viewFigure );
522
- } else {
523
- const rightColumnWidthAsPercentage = toPrecision( ( rightColumnWidth - dx ) * 100 / tableWidth );
524
-
525
- writer.setStyle( 'width', `${ rightColumnWidthAsPercentage }%`, viewRightColumn );
526
- }
527
- } );
528
- }
529
-
530
- /**
531
- * Handles the `mouseup` event.
532
- * * If resizing process is not in progress, it does nothing.
533
- * * If resizing is active but not allowed, it cancels the resizing process restoring the original widths.
534
- * * Otherwise it propagates the changes from view to the model by executing the adequate commands.
535
- *
536
- * @private
537
- */
538
- _onMouseUpHandler() {
539
- if ( !this._isResizingActive ) {
540
- return;
541
- }
542
-
543
- const {
544
- viewResizer,
545
- modelTable,
546
- viewFigure,
547
- viewColgroup
548
- } = this._resizingData.elements;
549
-
550
- const editor = this.editor;
551
- const editingView = editor.editing.view;
552
-
553
- const columnWidthsAttributeOld = modelTable.getAttribute( 'columnWidths' );
554
- const columnWidthsAttributeNew = [ ...viewColgroup.getChildren() ]
555
- .map( viewCol => viewCol.getStyle( 'width' ) )
556
- .join( ',' );
557
-
558
- const isColumnWidthsAttributeChanged = columnWidthsAttributeOld !== columnWidthsAttributeNew;
559
-
560
- const tableWidthAttributeOld = modelTable.getAttribute( 'tableWidth' );
561
- const tableWidthAttributeNew = viewFigure.getStyle( 'width' );
562
-
563
- const isTableWidthAttributeChanged = tableWidthAttributeOld !== tableWidthAttributeNew;
564
-
565
- if ( isColumnWidthsAttributeChanged || isTableWidthAttributeChanged ) {
566
- if ( this._isResizingAllowed ) {
567
- // Commit all changes to the model.
568
- if ( isTableWidthAttributeChanged ) {
569
- editor.execute(
570
- 'resizeTableWidth',
571
- {
572
- table: modelTable,
573
- tableWidth: `${ toPrecision( tableWidthAttributeNew ) }%`,
574
- columnWidths: columnWidthsAttributeNew
575
- }
576
- );
577
- } else {
578
- editor.execute( 'resizeColumnWidths', { columnWidths: columnWidthsAttributeNew, table: modelTable } );
579
- }
580
- } else {
581
- // In read-only mode revert all changes in the editing view. The model is not touched so it does not need to be restored.
582
- // This case can occur if the read-only mode kicks in during the resizing process.
583
- editingView.change( writer => {
584
- // If table had resized columns before, restore the previous column widths.
585
- // Otherwise clean up the view from the temporary column resizing markup.
586
- if ( columnWidthsAttributeOld ) {
587
- const columnWidths = columnWidthsAttributeOld.split( ',' );
588
-
589
- for ( const viewCol of viewColgroup.getChildren() ) {
590
- writer.setStyle( 'width', columnWidths.shift(), viewCol );
591
- }
592
- } else {
593
- writer.remove( viewColgroup );
594
- }
595
-
596
- if ( isTableWidthAttributeChanged ) {
597
- // If the whole table was already resized before, restore the previous table width.
598
- // Otherwise clean up the view from the temporary table resizing markup.
599
- if ( tableWidthAttributeOld ) {
600
- writer.setStyle( 'width', tableWidthAttributeOld, viewFigure );
601
- } else {
602
- writer.removeStyle( 'width', viewFigure );
603
- }
604
- }
605
-
606
- // If a table and its columns weren't resized before,
607
- // prune the remaining common resizing markup.
608
- if ( !columnWidthsAttributeOld && !tableWidthAttributeOld ) {
609
- writer.removeClass(
610
- 'ck-table-resized',
611
- [ ...viewFigure.getChildren() ].find( element => element.name === 'table' )
612
- );
613
- }
614
- } );
615
- }
616
- }
617
-
618
- editingView.change( writer => {
619
- writer.removeClass( 'ck-table-column-resizer__active', viewResizer );
620
- } );
621
-
622
- this._isResizingActive = false;
623
- this._resizingData = null;
624
- }
625
-
626
- /**
627
- * Retrieves and returns required data needed for the resizing process.
628
- *
629
- * @private
630
- * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData The data of the `mousedown` event.
631
- * @param {Array.<Number>} columnWidths The current widths of the columns.
632
- * @returns {Object} The data needed for the resizing process.
633
- */
634
- _getResizingData( domEventData, columnWidths ) {
635
- const editor = this.editor;
636
-
637
- const columnPosition = domEventData.domEvent.clientX;
638
-
639
- const viewResizer = domEventData.target;
640
- const viewLeftCell = viewResizer.findAncestor( 'td' ) || viewResizer.findAncestor( 'th' );
641
- const modelLeftCell = editor.editing.mapper.toModelElement( viewLeftCell );
642
- const modelTable = modelLeftCell.findAncestor( 'table' );
643
-
644
- const leftColumnIndex = getColumnEdgesIndexes( modelLeftCell, this._tableUtilsPlugin ).rightEdge;
645
- const lastColumnIndex = this._tableUtilsPlugin.getColumns( modelTable ) - 1;
646
-
647
- const isRightEdge = leftColumnIndex === lastColumnIndex;
648
- const isTableCentered = !modelTable.hasAttribute( 'tableAlignment' );
649
- const isLtrContent = editor.locale.contentLanguageDirection !== 'rtl';
650
-
651
- const viewTable = viewLeftCell.findAncestor( 'table' );
652
- const viewFigure = viewTable.findAncestor( 'figure' );
653
- const viewColgroup = [ ...viewTable.getChildren() ].find( viewCol => viewCol.is( 'element', 'colgroup' ) );
654
- const viewLeftColumn = viewColgroup.getChild( leftColumnIndex );
655
- const viewRightColumn = isRightEdge ? undefined : viewColgroup.getChild( leftColumnIndex + 1 );
656
-
657
- const viewFigureParentWidth = getElementWidthInPixels( editor.editing.view.domConverter.mapViewToDom( viewFigure.parent ) );
658
- const viewFigureWidth = getElementWidthInPixels( editor.editing.view.domConverter.mapViewToDom( viewFigure ) );
659
- const tableWidth = getTableWidthInPixels( modelTable, editor );
660
- const leftColumnWidth = columnWidths[ leftColumnIndex ];
661
- const rightColumnWidth = isRightEdge ? undefined : columnWidths[ leftColumnIndex + 1 ];
662
-
663
- return {
664
- columnPosition,
665
- flags: {
666
- isRightEdge,
667
- isTableCentered,
668
- isLtrContent
669
- },
670
- elements: {
671
- viewResizer,
672
- modelTable,
673
- viewFigure,
674
- viewColgroup,
675
- viewLeftColumn,
676
- viewRightColumn
677
- },
678
- widths: {
679
- viewFigureParentWidth,
680
- viewFigureWidth,
681
- tableWidth,
682
- leftColumnWidth,
683
- rightColumnWidth
684
- }
685
- };
686
- }
687
-
688
- /**
689
- * Inserts the `<colgroup>` element if it is missing in the view table (e.g. after table insertion into table).
690
- *
691
- * @private
692
- */
693
- _registerColgroupFixer() {
694
- const editor = this.editor;
695
-
696
- this.listenTo( editor.editing.view.document, 'layoutChanged', () => {
697
- const viewTable = editor.editing.view.document.selection.getFirstPosition().getAncestors().reverse().find(
698
- viewElement => viewElement.name === 'table'
699
- );
700
- const viewTableContainsColgroup = viewTable && [ ...viewTable.getChildren() ].find(
701
- viewElement => viewElement.is( 'element', 'colgroup' )
702
- );
703
- const modelTable = editor.model.document.selection.getFirstPosition().findAncestor( 'table' );
704
-
705
- if ( modelTable && modelTable.hasAttribute( 'columnWidths' ) && viewTable && !viewTableContainsColgroup ) {
706
- editor.editing.reconvertItem( modelTable );
707
- }
708
- }, { priority: 'low' } );
709
- }
710
-
711
- /**
712
- * Registers a listener ensuring that each resizable cell have a resizer handle.
713
- *
714
- * @private
715
- */
716
- _registerResizerInserter() {
717
- this.editor.conversion.for( 'editingDowncast' ).add( dispatcher => {
718
- dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => {
719
- const modelElement = data.item;
720
- const viewElement = conversionApi.mapper.toViewElement( modelElement );
721
- const viewWriter = conversionApi.writer;
722
-
723
- viewWriter.insert(
724
- viewWriter.createPositionAt( viewElement, 'end' ),
725
- viewWriter.createUIElement( 'div', { class: 'ck-table-column-resizer' } )
726
- );
727
- }, { priority: 'lowest' } );
728
- } );
729
- }
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ static get requires() {
27
+ return [TableEditing, TableUtils];
28
+ }
29
+ /**
30
+ * @inheritDoc
31
+ */
32
+ static get pluginName() {
33
+ return 'TableColumnResizeEditing';
34
+ }
35
+ /**
36
+ * @inheritDoc
37
+ */
38
+ constructor(editor) {
39
+ super(editor);
40
+ this._isResizingActive = false;
41
+ this.set('_isResizingAllowed', true);
42
+ this._resizingData = null;
43
+ this._domEmitter = new (DomEmitterMixin())();
44
+ this._tableUtilsPlugin = editor.plugins.get('TableUtils');
45
+ this.on('change:_isResizingAllowed', (evt, name, value) => {
46
+ // Toggling the `ck-column-resize_disabled` class shows and hides the resizers through CSS.
47
+ editor.editing.view.change(writer => {
48
+ writer[value ? 'removeClass' : 'addClass']('ck-column-resize_disabled', editor.editing.view.document.getRoot());
49
+ });
50
+ });
51
+ }
52
+ /**
53
+ * @inheritDoc
54
+ */
55
+ init() {
56
+ this._extendSchema();
57
+ this._registerPostFixer();
58
+ this._registerConverters();
59
+ this._registerResizingListeners();
60
+ this._registerResizerInserter();
61
+ const editor = this.editor;
62
+ const columnResizePlugin = editor.plugins.get('TableColumnResize');
63
+ const tableEditing = editor.plugins.get('TableEditing');
64
+ tableEditing.registerAdditionalSlot({
65
+ filter: element => element.is('element', 'tableColumnGroup'),
66
+ positionOffset: 0
67
+ });
68
+ const tableWidthsCommand = new TableWidthsCommand(editor);
69
+ // For backwards compatibility we have two commands that perform exactly the same operation.
70
+ editor.commands.add('resizeTableWidth', tableWidthsCommand);
71
+ editor.commands.add('resizeColumnWidths', tableWidthsCommand);
72
+ // Currently the states of column resize and table resize (which is actually the last column resize) features
73
+ // are bound together. They can be separated in the future by adding distinct listeners and applying
74
+ // different CSS classes (e.g. `ck-column-resize_disabled` and `ck-table-resize_disabled`) to the editor root.
75
+ // See #12148 for the details.
76
+ this.bind('_isResizingAllowed').to(editor, 'isReadOnly', columnResizePlugin, 'isEnabled', tableWidthsCommand, 'isEnabled', (isEditorReadOnly, isPluginEnabled, isTableWidthsCommandCommandEnabled) => !isEditorReadOnly && isPluginEnabled && isTableWidthsCommandCommandEnabled);
77
+ }
78
+ /**
79
+ * @inheritDoc
80
+ */
81
+ destroy() {
82
+ this._domEmitter.stopListening();
83
+ super.destroy();
84
+ }
85
+ /**
86
+ * Returns a 'tableColumnGroup' element from the 'table'.
87
+ *
88
+ * @param element A 'table' or 'tableColumnGroup' element.
89
+ * @returns A 'tableColumnGroup' element.
90
+ */
91
+ getColumnGroupElement(element) {
92
+ return getColumnGroupElement(element);
93
+ }
94
+ /**
95
+ * Returns an array of 'tableColumn' elements.
96
+ *
97
+ * @param element A 'table' or 'tableColumnGroup' element.
98
+ * @returns An array of 'tableColumn' elements.
99
+ */
100
+ getTableColumnElements(element) {
101
+ return getTableColumnElements(element);
102
+ }
103
+ /**
104
+ * Returns an array of table column widths.
105
+ *
106
+ * @param element A 'table' or 'tableColumnGroup' element.
107
+ * @returns An array of table column widths.
108
+ */
109
+ getTableColumnsWidths(element) {
110
+ return getTableColumnsWidths(element);
111
+ }
112
+ /**
113
+ * Registers new attributes for a table model element.
114
+ */
115
+ _extendSchema() {
116
+ this.editor.model.schema.extend('table', {
117
+ allowAttributes: ['tableWidth']
118
+ });
119
+ this.editor.model.schema.register('tableColumnGroup', {
120
+ allowIn: 'table',
121
+ isLimit: true
122
+ });
123
+ this.editor.model.schema.register('tableColumn', {
124
+ allowIn: 'tableColumnGroup',
125
+ allowAttributes: ['columnWidth'],
126
+ isLimit: true
127
+ });
128
+ }
129
+ /**
130
+ * Registers table column resize post-fixer.
131
+ *
132
+ * It checks if the change from the differ concerns a table-related element or attribute. For detected changes it:
133
+ * * Adjusts the `columnWidths` attribute to guarantee that the sum of the widths from all columns is 100%.
134
+ * * Checks if the `columnWidths` attribute gets updated accordingly after columns have been added or removed.
135
+ */
136
+ _registerPostFixer() {
137
+ const editor = this.editor;
138
+ const model = editor.model;
139
+ model.document.registerPostFixer(writer => {
140
+ let changed = false;
141
+ for (const table of getChangedResizedTables(model)) {
142
+ const tableColumnGroup = this.getColumnGroupElement(table);
143
+ const columns = this.getTableColumnElements(tableColumnGroup);
144
+ const columnWidths = this.getTableColumnsWidths(tableColumnGroup);
145
+ // Adjust the `columnWidths` attribute to guarantee that the sum of the widths from all columns is 100%.
146
+ let normalizedWidths = normalizeColumnWidths(columnWidths);
147
+ // If the number of columns has changed, then we need to adjust the widths of the affected columns.
148
+ normalizedWidths = adjustColumnWidths(normalizedWidths, table, this);
149
+ if (isEqual(columnWidths, normalizedWidths)) {
150
+ continue;
151
+ }
152
+ updateColumnElements(columns, tableColumnGroup, normalizedWidths, writer);
153
+ changed = true;
154
+ }
155
+ return changed;
156
+ });
157
+ /**
158
+ * Adjusts if necessary the `columnWidths` in case if the number of column has changed.
159
+ *
160
+ * @param columnWidths Note: this array **may be modified** by the function.
161
+ * @param table Table to be checked.
162
+ */
163
+ function adjustColumnWidths(columnWidths, table, plugin) {
164
+ const newTableColumnsCount = plugin._tableUtilsPlugin.getColumns(table);
165
+ const columnsCountDelta = newTableColumnsCount - columnWidths.length;
166
+ if (columnsCountDelta === 0) {
167
+ return columnWidths;
168
+ }
169
+ const widths = columnWidths.map(width => Number(width.replace('%', '')));
170
+ // Collect all cells that are affected by the change.
171
+ const cellSet = getAffectedCells(plugin.editor.model.document.differ, table);
172
+ for (const cell of cellSet) {
173
+ const currentColumnsDelta = newTableColumnsCount - widths.length;
174
+ if (currentColumnsDelta === 0) {
175
+ continue;
176
+ }
177
+ // If the column count in the table changed, adjust the widths of the affected columns.
178
+ const hasMoreColumns = currentColumnsDelta > 0;
179
+ const currentColumnIndex = plugin._tableUtilsPlugin.getCellLocation(cell).column;
180
+ if (hasMoreColumns) {
181
+ const columnMinWidthAsPercentage = getColumnMinWidthAsPercentage(table, plugin.editor);
182
+ const columnWidthsToInsert = createFilledArray(currentColumnsDelta, columnMinWidthAsPercentage);
183
+ widths.splice(currentColumnIndex, 0, ...columnWidthsToInsert);
184
+ }
185
+ else {
186
+ // Moves the widths of the removed columns to the preceding one.
187
+ // Other editors either reduce the width of the whole table or adjust the widths
188
+ // proportionally, so change of this behavior can be considered in the future.
189
+ const removedColumnWidths = widths.splice(currentColumnIndex, Math.abs(currentColumnsDelta));
190
+ widths[currentColumnIndex] += sumArray(removedColumnWidths);
191
+ }
192
+ }
193
+ return widths.map(width => width + '%');
194
+ }
195
+ /**
196
+ * Returns a set of cells that have been changed in a given table.
197
+ */
198
+ function getAffectedCells(differ, table) {
199
+ const cellSet = new Set();
200
+ for (const change of differ.getChanges()) {
201
+ if (change.type == 'insert' &&
202
+ change.position.nodeAfter &&
203
+ change.position.nodeAfter.name == 'tableCell' &&
204
+ change.position.nodeAfter.getAncestors().includes(table)) {
205
+ cellSet.add(change.position.nodeAfter);
206
+ }
207
+ else if (change.type == 'remove') {
208
+ // If the first cell was removed, use the node after the change position instead.
209
+ const referenceNode = (change.position.nodeBefore || change.position.nodeAfter);
210
+ if (referenceNode.name == 'tableCell' && referenceNode.getAncestors().includes(table)) {
211
+ cellSet.add(referenceNode);
212
+ }
213
+ }
214
+ }
215
+ return cellSet;
216
+ }
217
+ }
218
+ /**
219
+ * Registers table column resize converters.
220
+ */
221
+ _registerConverters() {
222
+ const editor = this.editor;
223
+ const conversion = editor.conversion;
224
+ // Table width style
225
+ conversion.for('upcast').attributeToAttribute({
226
+ view: {
227
+ name: 'figure',
228
+ key: 'style',
229
+ value: {
230
+ width: /[\s\S]+/
231
+ }
232
+ },
233
+ model: {
234
+ name: 'table',
235
+ key: 'tableWidth',
236
+ value: (viewElement) => viewElement.getStyle('width')
237
+ }
238
+ });
239
+ conversion.for('downcast').attributeToAttribute({
240
+ model: {
241
+ name: 'table',
242
+ key: 'tableWidth'
243
+ },
244
+ view: (width) => ({
245
+ name: 'figure',
246
+ key: 'style',
247
+ value: {
248
+ width
249
+ }
250
+ })
251
+ });
252
+ conversion.elementToElement({ model: 'tableColumnGroup', view: 'colgroup' });
253
+ conversion.elementToElement({ model: 'tableColumn', view: 'col' });
254
+ conversion.for('downcast').add(downcastTableResizedClass());
255
+ conversion.for('upcast').add(upcastColgroupElement(this._tableUtilsPlugin));
256
+ conversion.for('upcast').attributeToAttribute({
257
+ view: {
258
+ name: 'col',
259
+ styles: {
260
+ width: /.*/
261
+ }
262
+ },
263
+ model: {
264
+ key: 'columnWidth',
265
+ value: (viewElement) => {
266
+ const viewColWidth = viewElement.getStyle('width');
267
+ if (!viewColWidth || !viewColWidth.endsWith('%')) {
268
+ return 'auto';
269
+ }
270
+ return viewColWidth;
271
+ }
272
+ }
273
+ });
274
+ conversion.for('downcast').attributeToAttribute({
275
+ model: {
276
+ name: 'tableColumn',
277
+ key: 'columnWidth'
278
+ },
279
+ view: width => ({ key: 'style', value: { width } })
280
+ });
281
+ }
282
+ /**
283
+ * Registers listeners to handle resizing process.
284
+ */
285
+ _registerResizingListeners() {
286
+ const editingView = this.editor.editing.view;
287
+ editingView.addObserver(MouseEventsObserver);
288
+ editingView.document.on('mousedown', this._onMouseDownHandler.bind(this), { priority: 'high' });
289
+ this._domEmitter.listenTo(global.window.document, 'mousemove', throttle(this._onMouseMoveHandler.bind(this), 50));
290
+ this._domEmitter.listenTo(global.window.document, 'mouseup', this._onMouseUpHandler.bind(this));
291
+ }
292
+ /**
293
+ * Handles the `mousedown` event on column resizer element:
294
+ * * calculates the initial column pixel widths,
295
+ * * inserts the `<colgroup>` element if it is not present in the `<table>`,
296
+ * * puts the necessary data in the temporary storage,
297
+ * * applies the attributes to the `<table>` view element.
298
+ *
299
+ * @param eventInfo An object containing information about the fired event.
300
+ * @param domEventData The data related to the DOM event.
301
+ */
302
+ _onMouseDownHandler(eventInfo, domEventData) {
303
+ const target = domEventData.target;
304
+ if (!target.hasClass('ck-table-column-resizer')) {
305
+ return;
306
+ }
307
+ if (!this._isResizingAllowed) {
308
+ return;
309
+ }
310
+ domEventData.preventDefault();
311
+ eventInfo.stop();
312
+ const editor = this.editor;
313
+ const modelTable = editor.editing.mapper.toModelElement(target.findAncestor('figure'));
314
+ // The column widths are calculated upon mousedown to allow lazy applying the `columnWidths` attribute on the table.
315
+ const columnWidthsInPx = _calculateDomColumnWidths(modelTable, this._tableUtilsPlugin, editor);
316
+ const viewTable = target.findAncestor('table');
317
+ const editingView = editor.editing.view;
318
+ // Insert colgroup for the table that is resized for the first time.
319
+ if (!Array.from(viewTable.getChildren()).find(viewCol => viewCol.is('element', 'colgroup'))) {
320
+ editingView.change(viewWriter => {
321
+ _insertColgroupElement(viewWriter, columnWidthsInPx, viewTable);
322
+ });
323
+ }
324
+ this._isResizingActive = true;
325
+ this._resizingData = this._getResizingData(domEventData, columnWidthsInPx);
326
+ // At this point we change only the editor view - we don't want other users to see our changes yet,
327
+ // so we can't apply them in the model.
328
+ editingView.change(writer => _applyResizingAttributesToTable(writer, viewTable, this._resizingData));
329
+ /**
330
+ * Calculates the DOM columns' widths. It is done by taking the width of the widest cell
331
+ * from each table column (we rely on the {@link module:table/tablewalker~TableWalker}
332
+ * to determine which column the cell belongs to).
333
+ *
334
+ * @param modelTable A table which columns should be measured.
335
+ * @param tableUtils The Table Utils plugin instance.
336
+ * @param editor The editor instance.
337
+ * @returns Columns' widths expressed in pixels (without unit).
338
+ */
339
+ function _calculateDomColumnWidths(modelTable, tableUtilsPlugin, editor) {
340
+ const columnWidthsInPx = Array(tableUtilsPlugin.getColumns(modelTable));
341
+ const tableWalker = new TableWalker(modelTable);
342
+ for (const cellSlot of tableWalker) {
343
+ const viewCell = editor.editing.mapper.toViewElement(cellSlot.cell);
344
+ const domCell = editor.editing.view.domConverter.mapViewToDom(viewCell);
345
+ const domCellWidth = getDomCellOuterWidth(domCell);
346
+ if (!columnWidthsInPx[cellSlot.column] || domCellWidth < columnWidthsInPx[cellSlot.column]) {
347
+ columnWidthsInPx[cellSlot.column] = toPrecision(domCellWidth);
348
+ }
349
+ }
350
+ return columnWidthsInPx;
351
+ }
352
+ /**
353
+ * Creates a `<colgroup>` element with `<col>`s and inserts it into a given view table.
354
+ *
355
+ * @param viewWriter A writer instance.
356
+ * @param columnWidthsInPx Column widths.
357
+ * @param viewTable A table view element.
358
+ */
359
+ function _insertColgroupElement(viewWriter, columnWidthsInPx, viewTable) {
360
+ const colgroup = viewWriter.createContainerElement('colgroup');
361
+ for (let i = 0; i < columnWidthsInPx.length; i++) {
362
+ const viewColElement = viewWriter.createEmptyElement('col');
363
+ const columnWidthInPc = `${toPrecision(columnWidthsInPx[i] / sumArray(columnWidthsInPx) * 100)}%`;
364
+ viewWriter.setStyle('width', columnWidthInPc, viewColElement);
365
+ viewWriter.insert(viewWriter.createPositionAt(colgroup, 'end'), viewColElement);
366
+ }
367
+ viewWriter.insert(viewWriter.createPositionAt(viewTable, 0), colgroup);
368
+ }
369
+ /**
370
+ * Applies the style and classes to the view table as the resizing begun.
371
+ *
372
+ * @param viewWriter A writer instance.
373
+ * @param viewTable A table containing the clicked resizer.
374
+ * @param resizingData Data related to the resizing.
375
+ */
376
+ function _applyResizingAttributesToTable(viewWriter, viewTable, resizingData) {
377
+ const figureInitialPcWidth = resizingData.widths.viewFigureWidth / resizingData.widths.viewFigureParentWidth;
378
+ viewWriter.addClass('ck-table-resized', viewTable);
379
+ viewWriter.addClass('ck-table-column-resizer__active', resizingData.elements.viewResizer);
380
+ viewWriter.setStyle('width', `${toPrecision(figureInitialPcWidth * 100)}%`, viewTable.findAncestor('figure'));
381
+ }
382
+ }
383
+ /**
384
+ * Handles the `mousemove` event.
385
+ * * If resizing process is not in progress, it does nothing.
386
+ * * If resizing is active but not allowed, it stops the resizing process instantly calling the `mousedown` event handler.
387
+ * * Otherwise it dynamically updates the widths of the resized columns.
388
+ *
389
+ * @param eventInfo An object containing information about the fired event.
390
+ * @param mouseEventData The native DOM event.
391
+ */
392
+ _onMouseMoveHandler(eventInfo, mouseEventData) {
393
+ if (!this._isResizingActive) {
394
+ return;
395
+ }
396
+ if (!this._isResizingAllowed) {
397
+ this._onMouseUpHandler();
398
+ return;
399
+ }
400
+ const { columnPosition, flags: { isRightEdge, isTableCentered, isLtrContent }, elements: { viewFigure, viewLeftColumn, viewRightColumn }, widths: { viewFigureParentWidth, tableWidth, leftColumnWidth, rightColumnWidth } } = this._resizingData;
401
+ const dxLowerBound = -leftColumnWidth + COLUMN_MIN_WIDTH_IN_PIXELS;
402
+ const dxUpperBound = isRightEdge ?
403
+ viewFigureParentWidth - tableWidth :
404
+ rightColumnWidth - COLUMN_MIN_WIDTH_IN_PIXELS;
405
+ // The multiplier is needed for calculating the proper movement offset:
406
+ // - it should negate the sign if content language direction is right-to-left,
407
+ // - it should double the offset if the table edge is resized and table is centered.
408
+ const multiplier = (isLtrContent ? 1 : -1) * (isRightEdge && isTableCentered ? 2 : 1);
409
+ const dx = clamp((mouseEventData.clientX - columnPosition) * multiplier, Math.min(dxLowerBound, 0), Math.max(dxUpperBound, 0));
410
+ if (dx === 0) {
411
+ return;
412
+ }
413
+ this.editor.editing.view.change(writer => {
414
+ const leftColumnWidthAsPercentage = toPrecision((leftColumnWidth + dx) * 100 / tableWidth);
415
+ writer.setStyle('width', `${leftColumnWidthAsPercentage}%`, viewLeftColumn);
416
+ if (isRightEdge) {
417
+ const tableWidthAsPercentage = toPrecision((tableWidth + dx) * 100 / viewFigureParentWidth);
418
+ writer.setStyle('width', `${tableWidthAsPercentage}%`, viewFigure);
419
+ }
420
+ else {
421
+ const rightColumnWidthAsPercentage = toPrecision((rightColumnWidth - dx) * 100 / tableWidth);
422
+ writer.setStyle('width', `${rightColumnWidthAsPercentage}%`, viewRightColumn);
423
+ }
424
+ });
425
+ }
426
+ /**
427
+ * Handles the `mouseup` event.
428
+ * * If resizing process is not in progress, it does nothing.
429
+ * * If resizing is active but not allowed, it cancels the resizing process restoring the original widths.
430
+ * * Otherwise it propagates the changes from view to the model by executing the adequate commands.
431
+ */
432
+ _onMouseUpHandler() {
433
+ if (!this._isResizingActive) {
434
+ return;
435
+ }
436
+ const { viewResizer, modelTable, viewFigure, viewColgroup } = this._resizingData.elements;
437
+ const editor = this.editor;
438
+ const editingView = editor.editing.view;
439
+ const tableColumnGroup = this.getColumnGroupElement(modelTable);
440
+ const viewColumns = Array
441
+ .from(viewColgroup.getChildren())
442
+ .filter((column) => column.is('view:element'));
443
+ const columnWidthsAttributeOld = tableColumnGroup ?
444
+ this.getTableColumnsWidths(tableColumnGroup) :
445
+ null;
446
+ const columnWidthsAttributeNew = viewColumns.map(column => column.getStyle('width'));
447
+ const isColumnWidthsAttributeChanged = !isEqual(columnWidthsAttributeOld, columnWidthsAttributeNew);
448
+ const tableWidthAttributeOld = modelTable.getAttribute('tableWidth');
449
+ const tableWidthAttributeNew = viewFigure.getStyle('width');
450
+ const isTableWidthAttributeChanged = tableWidthAttributeOld !== tableWidthAttributeNew;
451
+ if (isColumnWidthsAttributeChanged || isTableWidthAttributeChanged) {
452
+ if (this._isResizingAllowed) {
453
+ editor.execute('resizeTableWidth', {
454
+ table: modelTable,
455
+ tableWidth: `${toPrecision(tableWidthAttributeNew)}%`,
456
+ columnWidths: columnWidthsAttributeNew
457
+ });
458
+ }
459
+ else {
460
+ // In read-only mode revert all changes in the editing view. The model is not touched so it does not need to be restored.
461
+ // This case can occur if the read-only mode kicks in during the resizing process.
462
+ editingView.change(writer => {
463
+ // If table had resized columns before, restore the previous column widths.
464
+ // Otherwise clean up the view from the temporary column resizing markup.
465
+ if (columnWidthsAttributeOld) {
466
+ for (const viewCol of viewColumns) {
467
+ writer.setStyle('width', columnWidthsAttributeOld.shift(), viewCol);
468
+ }
469
+ }
470
+ else {
471
+ writer.remove(viewColgroup);
472
+ }
473
+ if (isTableWidthAttributeChanged) {
474
+ // If the whole table was already resized before, restore the previous table width.
475
+ // Otherwise clean up the view from the temporary table resizing markup.
476
+ if (tableWidthAttributeOld) {
477
+ writer.setStyle('width', tableWidthAttributeOld, viewFigure);
478
+ }
479
+ else {
480
+ writer.removeStyle('width', viewFigure);
481
+ }
482
+ }
483
+ // If a table and its columns weren't resized before,
484
+ // prune the remaining common resizing markup.
485
+ if (!columnWidthsAttributeOld && !tableWidthAttributeOld) {
486
+ writer.removeClass('ck-table-resized', [...viewFigure.getChildren()].find(element => element.name === 'table'));
487
+ }
488
+ });
489
+ }
490
+ }
491
+ editingView.change(writer => {
492
+ writer.removeClass('ck-table-column-resizer__active', viewResizer);
493
+ });
494
+ this._isResizingActive = false;
495
+ this._resizingData = null;
496
+ }
497
+ /**
498
+ * Retrieves and returns required data needed for the resizing process.
499
+ *
500
+ * @param domEventData The data of the `mousedown` event.
501
+ * @param columnWidths The current widths of the columns.
502
+ * @returns The data needed for the resizing process.
503
+ */
504
+ _getResizingData(domEventData, columnWidths) {
505
+ const editor = this.editor;
506
+ const columnPosition = domEventData.domEvent.clientX;
507
+ const viewResizer = domEventData.target;
508
+ const viewLeftCell = viewResizer.findAncestor('td') || viewResizer.findAncestor('th');
509
+ const modelLeftCell = editor.editing.mapper.toModelElement(viewLeftCell);
510
+ const modelTable = modelLeftCell.findAncestor('table');
511
+ const leftColumnIndex = getColumnEdgesIndexes(modelLeftCell, this._tableUtilsPlugin).rightEdge;
512
+ const lastColumnIndex = this._tableUtilsPlugin.getColumns(modelTable) - 1;
513
+ const isRightEdge = leftColumnIndex === lastColumnIndex;
514
+ const isTableCentered = !modelTable.hasAttribute('tableAlignment');
515
+ const isLtrContent = editor.locale.contentLanguageDirection !== 'rtl';
516
+ const viewTable = viewLeftCell.findAncestor('table');
517
+ const viewFigure = viewTable.findAncestor('figure');
518
+ const viewColgroup = [...viewTable.getChildren()]
519
+ .find(viewCol => viewCol.is('element', 'colgroup'));
520
+ const viewLeftColumn = viewColgroup.getChild(leftColumnIndex);
521
+ const viewRightColumn = isRightEdge ? undefined : viewColgroup.getChild(leftColumnIndex + 1);
522
+ const viewFigureParentWidth = getElementWidthInPixels(editor.editing.view.domConverter.mapViewToDom(viewFigure.parent));
523
+ const viewFigureWidth = getElementWidthInPixels(editor.editing.view.domConverter.mapViewToDom(viewFigure));
524
+ const tableWidth = getTableWidthInPixels(modelTable, editor);
525
+ const leftColumnWidth = columnWidths[leftColumnIndex];
526
+ const rightColumnWidth = isRightEdge ? undefined : columnWidths[leftColumnIndex + 1];
527
+ return {
528
+ columnPosition,
529
+ flags: {
530
+ isRightEdge,
531
+ isTableCentered,
532
+ isLtrContent
533
+ },
534
+ elements: {
535
+ viewResizer,
536
+ modelTable,
537
+ viewFigure,
538
+ viewColgroup,
539
+ viewLeftColumn,
540
+ viewRightColumn
541
+ },
542
+ widths: {
543
+ viewFigureParentWidth,
544
+ viewFigureWidth,
545
+ tableWidth,
546
+ leftColumnWidth,
547
+ rightColumnWidth
548
+ }
549
+ };
550
+ }
551
+ /**
552
+ * Registers a listener ensuring that each resizable cell have a resizer handle.
553
+ */
554
+ _registerResizerInserter() {
555
+ this.editor.conversion.for('editingDowncast').add(dispatcher => {
556
+ dispatcher.on('insert:tableCell', (evt, data, conversionApi) => {
557
+ const modelElement = data.item;
558
+ const viewElement = conversionApi.mapper.toViewElement(modelElement);
559
+ const viewWriter = conversionApi.writer;
560
+ viewWriter.insert(viewWriter.createPositionAt(viewElement, 'end'), viewWriter.createUIElement('div', { class: 'ck-table-column-resizer' }));
561
+ }, { priority: 'lowest' });
562
+ });
563
+ }
730
564
  }