@atlaskit/editor-tables 2.4.0 → 2.5.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 (31) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/table-map.js +28 -3
  3. package/dist/cjs/utils/analyse-table.js +38 -0
  4. package/dist/cjs/utils/get-selection-range-in-column.js +6 -5
  5. package/dist/cjs/utils/move-column.js +106 -6
  6. package/dist/cjs/utils/move-row.js +8 -43
  7. package/dist/cjs/utils/normalize-direction.js +15 -0
  8. package/dist/es2019/table-map.js +22 -1
  9. package/dist/es2019/utils/analyse-table.js +32 -0
  10. package/dist/es2019/utils/get-selection-range-in-column.js +5 -5
  11. package/dist/es2019/utils/move-column.js +105 -7
  12. package/dist/es2019/utils/move-row.js +7 -41
  13. package/dist/es2019/utils/normalize-direction.js +8 -0
  14. package/dist/esm/table-map.js +28 -3
  15. package/dist/esm/utils/analyse-table.js +32 -0
  16. package/dist/esm/utils/get-selection-range-in-column.js +6 -5
  17. package/dist/esm/utils/move-column.js +106 -8
  18. package/dist/esm/utils/move-row.js +7 -42
  19. package/dist/esm/utils/normalize-direction.js +9 -0
  20. package/dist/types/table-map.d.ts +3 -0
  21. package/dist/types/utils/analyse-table.d.ts +7 -0
  22. package/dist/types/utils/get-selection-range-in-column.d.ts +3 -3
  23. package/dist/types/utils/move-column.d.ts +2 -2
  24. package/dist/types/utils/normalize-direction.d.ts +4 -0
  25. package/dist/types-ts4.5/table-map.d.ts +3 -0
  26. package/dist/types-ts4.5/utils/analyse-table.d.ts +7 -0
  27. package/dist/types-ts4.5/utils/get-selection-range-in-column.d.ts +3 -3
  28. package/dist/types-ts4.5/utils/move-column.d.ts +2 -2
  29. package/dist/types-ts4.5/utils/normalize-direction.d.ts +4 -0
  30. package/package.json +1 -1
  31. package/tsconfig.json +32 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/editor-tables
2
2
 
3
+ ## 2.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#69232](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/69232) [`93c8f231aa82`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/93c8f231aa82) - Optimized the table move column logic to perform individual insert/deletes per row
8
+
3
9
  ## 2.4.0
4
10
 
5
11
  ### Minor Changes
@@ -143,9 +143,34 @@ var TableMap = exports.TableMap = /*#__PURE__*/function () {
143
143
  }, {
144
144
  key: "isPosMerged",
145
145
  value: function isPosMerged(pos) {
146
- return this.map.filter(function (cellPos) {
147
- return cellPos === pos;
148
- }).length > 1;
146
+ return this.map.includes(pos, this.map.indexOf(pos) + 1);
147
+ }
148
+ }, {
149
+ key: "isCellMerged",
150
+ value: function isCellMerged(row, col) {
151
+ return this.isCellMergedTopLeft(row, col) || this.isCellMergedBottomRight(row, col);
152
+ }
153
+ }, {
154
+ key: "isCellMergedTopLeft",
155
+ value: function isCellMergedTopLeft(row, col) {
156
+ var pos = this.map[row * this.width + col];
157
+ return (
158
+ // top
159
+ row > 0 && pos === this.map[(row - 1) * this.width + col] ||
160
+ // left
161
+ col > 0 && pos === this.map[row * this.width + (col - 1)]
162
+ );
163
+ }
164
+ }, {
165
+ key: "isCellMergedBottomRight",
166
+ value: function isCellMergedBottomRight(row, col) {
167
+ var pos = this.map[row * this.width + col];
168
+ return (
169
+ // bottom
170
+ row < this.height - 1 && pos === this.map[(row + 1) * this.width + col] ||
171
+ // right
172
+ col < this.width - 1 && pos === this.map[row * this.width + (col + 1)]
173
+ );
149
174
  }
150
175
 
151
176
  // :: (number, string, number) → ?number
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.determineTableHeaderStateFromTableNode = determineTableHeaderStateFromTableNode;
7
+ function determineTableHeaderStateFromTableNode(table, tableMap, types) {
8
+ // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
9
+ // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
10
+ // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
11
+ // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
12
+
13
+ // This is a 3 bit mask;
14
+ // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
15
+ // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
16
+ // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
17
+ var mask = 0;
18
+
19
+ // At minimum we should have 1 cell in the table.
20
+ var topLeftCell = table.nodeAt(tableMap.map[0]);
21
+ // If this cell is a header that could indicate
22
+ mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
23
+ if (tableMap.width > 1) {
24
+ var cell = table.nodeAt(tableMap.map[1]);
25
+ // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
26
+ // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
27
+ // was a header cell.
28
+ mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
29
+ }
30
+ if (tableMap.height > 1) {
31
+ var _cell = table.nodeAt(tableMap.map[tableMap.width]);
32
+ mask |= _cell && _cell.type === types.header_cell ? 4 : 2 * (mask & 1);
33
+ }
34
+ return {
35
+ rowHeaderEnabled: mask === 7 || mask === 3,
36
+ columnHeaderEnabled: mask === 7 || mask === 5
37
+ };
38
+ }
@@ -7,10 +7,11 @@ exports.getSelectionRangeInColumn = void 0;
7
7
  var _getCellsInColumn = require("./get-cells-in-column");
8
8
  var _getCellsInRow = require("./get-cells-in-row");
9
9
  // Returns a range of rectangular selection spanning all merged cells around a column at index `columnIndex`.
10
- var getSelectionRangeInColumn = exports.getSelectionRangeInColumn = function getSelectionRangeInColumn(columnIndex) {
10
+ var getSelectionRangeInColumn = exports.getSelectionRangeInColumn = function getSelectionRangeInColumn(startColIndex) {
11
+ var endColIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startColIndex;
11
12
  return function (tr) {
12
- var startIndex = columnIndex;
13
- var endIndex = columnIndex;
13
+ var startIndex = startColIndex;
14
+ var endIndex = endColIndex;
14
15
 
15
16
  // looking for selection start column (startIndex)
16
17
  var _loop = function _loop(i) {
@@ -27,7 +28,7 @@ var getSelectionRangeInColumn = exports.getSelectionRangeInColumn = function get
27
28
  });
28
29
  }
29
30
  };
30
- for (var i = columnIndex; i >= 0; i--) {
31
+ for (var i = startColIndex; i >= 0; i--) {
31
32
  _loop(i);
32
33
  }
33
34
  // looking for selection end column (endIndex)
@@ -42,7 +43,7 @@ var getSelectionRangeInColumn = exports.getSelectionRangeInColumn = function get
42
43
  });
43
44
  }
44
45
  };
45
- for (var _i = columnIndex; _i <= endIndex; _i++) {
46
+ for (var _i = startColIndex; _i <= endIndex; _i++) {
46
47
  _loop2(_i);
47
48
  }
48
49
 
@@ -1,13 +1,19 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
7
  exports.moveColumn = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _tableMap = require("../table-map");
10
+ var _analyseTable = require("./analyse-table");
7
11
  var _cloneTr = require("./clone-tr");
8
12
  var _find = require("./find");
9
13
  var _getSelectionRangeInColumn = require("./get-selection-range-in-column");
14
+ var _normalizeDirection = require("./normalize-direction");
10
15
  var _reorderUtils = require("./reorder-utils");
16
+ var _tableNodeTypes = require("./table-node-types");
11
17
  // :: (originColumnIndex: number, targetColumnIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
12
18
  // Returns a new transaction that moves the origin column to the target index;
13
19
  //
@@ -136,8 +142,8 @@ var _reorderUtils = require("./reorder-utils");
136
142
  // moveColumn(x, y, options)(state.tr)
137
143
  // );
138
144
  // ```
139
- var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, targetColumnIndex) {
140
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
145
+ var moveColumn = exports.moveColumn = function moveColumn(state, originColumnIndex, targetColumnIndex) {
146
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
141
147
  tryToFit: false,
142
148
  direction: 0
143
149
  };
@@ -147,21 +153,115 @@ var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, tar
147
153
  if (!table) {
148
154
  return tr;
149
155
  }
156
+
157
+ // normalize the origin index to an array since this supports moving both a single & multiple cols in a single action.
150
158
  if (!Array.isArray(originColumnIndex)) {
151
159
  originColumnIndex = [originColumnIndex];
152
160
  }
153
- var originalColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(originColumnIndex[0])(tr);
161
+ var tableMap = _tableMap.TableMap.get(table.node);
162
+ var originalColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(Math.min.apply(Math, (0, _toConsumableArray2.default)(originColumnIndex)), Math.max.apply(Math, (0, _toConsumableArray2.default)(originColumnIndex)))(tr);
154
163
  var targetColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(targetColumnIndex)(tr);
155
164
  var indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
156
165
  var indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
166
+ var min = indexesOriginColumn[0];
167
+ var max = indexesOriginColumn[indexesOriginColumn.length - 1];
157
168
  if (indexesOriginColumn.includes(targetColumnIndex)) {
158
169
  return tr;
159
170
  }
160
171
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
161
172
  (0, _reorderUtils.isValidReorder)(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
162
173
  }
163
- var newTable = (0, _reorderUtils.moveTableColumn)(table, indexesOriginColumn, indexesTargetColumn, options.direction);
164
- var newTr = (0, _cloneTr.cloneTr)(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
174
+ var types = (0, _tableNodeTypes.tableNodeTypes)(state.schema);
175
+ var direction = (0, _normalizeDirection.normalizeDirection)(min, targetColumnIndex, options);
176
+ var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, (0, _toConsumableArray2.default)(indexesTargetColumn));
177
+ var _determineTableHeader = (0, _analyseTable.determineTableHeaderStateFromTableNode)(table.node, tableMap, types),
178
+ rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
179
+ columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
180
+ var createContentNode = createContentNodeFactory(table);
181
+ var newTr = (0, _cloneTr.cloneTr)(tr);
182
+ var origins = [];
183
+ for (var y = 0; y < tableMap.height; y++) {
184
+ origins.push([]);
185
+ for (var x = min; x <= max; x++) {
186
+ if (tableMap.isCellMergedTopLeft(y, x)) {
187
+ continue;
188
+ }
189
+ var nodePos = tableMap.map[y * tableMap.width + x];
190
+ origins[y].push(createContentNode(nodePos));
191
+ }
192
+ if (columnHeaderEnabled && (min === 0 || actualTargetIndex === 0)) {
193
+ // This block is handling the situation where a col is moved in/out of the header position. If the header col option
194
+ // is enabled then;
195
+ // When a col is moved out, the col will be converted to a normal col and the col to the right will become the header.
196
+ // When a col is moved in, the old col header needs to be made normal, and the incoming col needs to be made a header.
197
+ // This section only manages what happens to the other col, not the one being moved.
198
+ var nearHeaderCol = min === 0 ? max + 1 : actualTargetIndex;
199
+ var _nodePos = tableMap.map[y * tableMap.width + nearHeaderCol];
200
+ var _createContentNode = createContentNode(_nodePos),
201
+ pos = _createContentNode.pos,
202
+ node = _createContentNode.node;
203
+ newTr.setNodeMarkup(pos, actualTargetIndex !== 0 || rowHeaderEnabled && y === 0 ? types.header_cell : types.cell, node.attrs);
204
+ }
205
+ }
206
+ origins.forEach(function (row, y) {
207
+ if (!row.length) {
208
+ // If the origin has no cells to be moved then we can skip moving for this row. This can occur when a cell above rowspans
209
+ // into the current row.
210
+ return;
211
+ }
212
+
213
+ // The actual target index needs to be translated per row, this is because row/col spans can affect the amount of
214
+ // cells each row contains.
215
+ var rowTargetPosition = translateTargetPosition(y, actualTargetIndex, tableMap);
216
+ var node = table.node.nodeAt(rowTargetPosition);
217
+ var pos = table.start + rowTargetPosition;
218
+ var insertPos = direction === 'end' ? newTr.mapping.map(pos + node.nodeSize, 1) : newTr.mapping.map(pos, -1);
219
+ newTr.insert(insertPos, row.map(function (_ref, x) {
220
+ var node = _ref.node;
221
+ return normalizeCellNode(node, rowHeaderEnabled && y === 0, columnHeaderEnabled && actualTargetIndex === 0 && x === 0, types);
222
+ }));
223
+
224
+ // NOTE: only consecutive cells can be moved, this means we can simplify the delete op into a single step which
225
+ // deletes the range of cells.
226
+ var first = row[0];
227
+ var last = row[row.length - 1];
228
+ return newTr.delete(newTr.mapping.map(first.pos, 1), newTr.mapping.map(last.pos + last.node.nodeSize, -1));
229
+ });
165
230
  return newTr;
166
231
  };
167
- };
232
+ };
233
+ function normalizeCellNode(cellNode, rowHeaderEnabled, columnHeaderEnabled, types) {
234
+ var newTargetType = rowHeaderEnabled || columnHeaderEnabled ? types.header_cell : types.cell;
235
+ return cellNode.type !== newTargetType ? newTargetType.create(cellNode.attrs, cellNode.content, cellNode.marks) : cellNode;
236
+ }
237
+ function createContentNodeFactory(table) {
238
+ return function (nodePos) {
239
+ var node = table.node.nodeAt(nodePos);
240
+ var pos = nodePos + table.start;
241
+ return {
242
+ pos: pos,
243
+ start: pos + 1,
244
+ node: node,
245
+ depth: table.depth + 2
246
+ };
247
+ };
248
+ }
249
+ function translateTargetPosition(row, startIndex, tableMap) {
250
+ if (tableMap.isCellMergedTopLeft(row, startIndex)) {
251
+ // find the closet unmerged position to the left of the target. We scan left first because merged cells will actually
252
+ // reduce the amount of cells in a row.
253
+ for (var x = startIndex - 1; x >= 0; x--) {
254
+ if (!tableMap.isCellMergedTopLeft(row, x)) {
255
+ return tableMap.map[row * tableMap.width + x];
256
+ }
257
+ }
258
+
259
+ // If no index found then we need to look to the right, this can occur when the first cell in the row is merged.
260
+ for (var _x = startIndex + 1; _x < tableMap.width; _x++) {
261
+ if (!tableMap.isCellMergedTopLeft(row, _x)) {
262
+ return tableMap.map[row * tableMap.width + _x];
263
+ }
264
+ }
265
+ }
266
+ return tableMap.map[row * tableMap.width + startIndex];
267
+ }
@@ -8,9 +8,12 @@ exports.moveRow = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
10
  var _tableMap = require("../table-map");
11
+ var _analyseTable = require("./analyse-table");
11
12
  var _cloneTr = require("./clone-tr");
12
13
  var _find = require("./find");
13
14
  var _getSelectionRangeInRow = require("./get-selection-range-in-row");
15
+ var _normalizeDirection = require("./normalize-direction");
16
+ var _reorderUtils = require("./reorder-utils");
14
17
  var _tableNodeTypes = require("./table-node-types");
15
18
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
16
19
  // Returns a new transaction that moves the origin row to the target index;
@@ -192,8 +195,11 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
192
195
  targetRowIndex >= originMin && targetRowIndex <= originMax) {
193
196
  return tr;
194
197
  }
198
+ if (!options.tryToFit && indexesTargetRow.length > 1) {
199
+ (0, _reorderUtils.isValidReorder)(originMin, targetRowIndex, indexesTargetRow, 'row');
200
+ }
195
201
  var types = (0, _tableNodeTypes.tableNodeTypes)(state.schema);
196
- var direction = normalizeDirection(originMin, targetRowIndex, options);
202
+ var direction = (0, _normalizeDirection.normalizeDirection)(originMin, targetRowIndex, options);
197
203
  var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, (0, _toConsumableArray2.default)(indexesTargetRow));
198
204
  var originPositions = indexesOriginRow.map(function (index) {
199
205
  return tableMap.positionAt(index, 0, table.node) + table.pos;
@@ -212,7 +218,7 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
212
218
  var targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
213
219
  if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
214
220
  var newTr = (0, _cloneTr.cloneTr)(tr);
215
- var _determineTableHeader = determineTableHeaderState(table.node, tableMap, types),
221
+ var _determineTableHeader = (0, _analyseTable.determineTableHeaderStateFromTableNode)(table.node, tableMap, types),
216
222
  rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
217
223
  columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
218
224
  if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
@@ -245,47 +251,6 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
245
251
  return tr;
246
252
  };
247
253
  };
248
- function normalizeDirection(origin, target) {
249
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
250
- tryToFit: false,
251
- direction: 0
252
- };
253
- var dir = origin < target ? 'end' : 'start';
254
- var override = options.direction < 0 ? 'start' : 'end';
255
- return options.tryToFit && !!options.direction ? override : dir;
256
- }
257
- function determineTableHeaderState(table, tableMap, types) {
258
- // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
259
- // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
260
- // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
261
- // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
262
-
263
- // This is a 3 bit mask;
264
- // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
265
- // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
266
- // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
267
- var mask = 0;
268
-
269
- // At minimum we should have 1 cell in the table.
270
- var topLeftCell = table.nodeAt(tableMap.map[0]);
271
- // If this cell is a header that could indicate
272
- mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
273
- if (tableMap.width > 1) {
274
- var cell = table.nodeAt(tableMap.map[1]);
275
- // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
276
- // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
277
- // was a header cell.
278
- mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
279
- }
280
- if (tableMap.height > 1) {
281
- var _cell = table.nodeAt(tableMap.map[tableMap.width]);
282
- mask |= _cell && _cell.type === types.header_cell ? 4 : 2 * (mask & 1);
283
- }
284
- return {
285
- rowHeaderEnabled: mask === 7 || mask === 3,
286
- columnHeaderEnabled: mask === 7 || mask === 5
287
- };
288
- }
289
254
 
290
255
  /**
291
256
  * This ensures the row node cell type correctly reflect what they should be.
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.normalizeDirection = normalizeDirection;
7
+ function normalizeDirection(origin, target) {
8
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
9
+ tryToFit: false,
10
+ direction: 0
11
+ };
12
+ var dir = origin < target ? 'end' : 'start';
13
+ var override = options.direction < 0 ? 'start' : 'end';
14
+ return options.tryToFit && !!options.direction ? override : dir;
15
+ }
@@ -124,7 +124,28 @@ export class TableMap {
124
124
  throw new RangeError('No cell with offset ' + pos + ' found');
125
125
  }
126
126
  isPosMerged(pos) {
127
- return this.map.filter(cellPos => cellPos === pos).length > 1;
127
+ return this.map.includes(pos, this.map.indexOf(pos) + 1);
128
+ }
129
+ isCellMerged(row, col) {
130
+ return this.isCellMergedTopLeft(row, col) || this.isCellMergedBottomRight(row, col);
131
+ }
132
+ isCellMergedTopLeft(row, col) {
133
+ const pos = this.map[row * this.width + col];
134
+ return (
135
+ // top
136
+ row > 0 && pos === this.map[(row - 1) * this.width + col] ||
137
+ // left
138
+ col > 0 && pos === this.map[row * this.width + (col - 1)]
139
+ );
140
+ }
141
+ isCellMergedBottomRight(row, col) {
142
+ const pos = this.map[row * this.width + col];
143
+ return (
144
+ // bottom
145
+ row < this.height - 1 && pos === this.map[(row + 1) * this.width + col] ||
146
+ // right
147
+ col < this.width - 1 && pos === this.map[row * this.width + (col + 1)]
148
+ );
128
149
  }
129
150
 
130
151
  // :: (number, string, number) → ?number
@@ -0,0 +1,32 @@
1
+ export function determineTableHeaderStateFromTableNode(table, tableMap, types) {
2
+ // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
3
+ // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
4
+ // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
5
+ // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
6
+
7
+ // This is a 3 bit mask;
8
+ // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
9
+ // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
10
+ // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
11
+ let mask = 0;
12
+
13
+ // At minimum we should have 1 cell in the table.
14
+ const topLeftCell = table.nodeAt(tableMap.map[0]);
15
+ // If this cell is a header that could indicate
16
+ mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
17
+ if (tableMap.width > 1) {
18
+ const cell = table.nodeAt(tableMap.map[1]);
19
+ // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
20
+ // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
21
+ // was a header cell.
22
+ mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
23
+ }
24
+ if (tableMap.height > 1) {
25
+ const cell = table.nodeAt(tableMap.map[tableMap.width]);
26
+ mask |= cell && cell.type === types.header_cell ? 4 : 2 * (mask & 1);
27
+ }
28
+ return {
29
+ rowHeaderEnabled: mask === 7 || mask === 3,
30
+ columnHeaderEnabled: mask === 7 || mask === 5
31
+ };
32
+ }
@@ -2,12 +2,12 @@ import { getCellsInColumn } from './get-cells-in-column';
2
2
  import { getCellsInRow } from './get-cells-in-row';
3
3
 
4
4
  // Returns a range of rectangular selection spanning all merged cells around a column at index `columnIndex`.
5
- export const getSelectionRangeInColumn = columnIndex => tr => {
6
- let startIndex = columnIndex;
7
- let endIndex = columnIndex;
5
+ export const getSelectionRangeInColumn = (startColIndex, endColIndex = startColIndex) => tr => {
6
+ let startIndex = startColIndex;
7
+ let endIndex = endColIndex;
8
8
 
9
9
  // looking for selection start column (startIndex)
10
- for (let i = columnIndex; i >= 0; i--) {
10
+ for (let i = startColIndex; i >= 0; i--) {
11
11
  const cells = getCellsInColumn(i)(tr.selection);
12
12
  if (cells) {
13
13
  cells.forEach(cell => {
@@ -22,7 +22,7 @@ export const getSelectionRangeInColumn = columnIndex => tr => {
22
22
  }
23
23
  }
24
24
  // looking for selection end column (endIndex)
25
- for (let i = columnIndex; i <= endIndex; i++) {
25
+ for (let i = startColIndex; i <= endIndex; i++) {
26
26
  const cells = getCellsInColumn(i)(tr.selection);
27
27
  if (cells) {
28
28
  cells.forEach(cell => {
@@ -1,8 +1,11 @@
1
+ import { TableMap } from '../table-map';
2
+ import { determineTableHeaderStateFromTableNode } from './analyse-table';
1
3
  import { cloneTr } from './clone-tr';
2
4
  import { findTable } from './find';
3
5
  import { getSelectionRangeInColumn } from './get-selection-range-in-column';
4
- import { isValidReorder, moveTableColumn } from './reorder-utils';
5
-
6
+ import { normalizeDirection } from './normalize-direction';
7
+ import { isValidReorder } from './reorder-utils';
8
+ import { tableNodeTypes } from './table-node-types';
6
9
  // :: (originColumnIndex: number, targetColumnIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
7
10
  // Returns a new transaction that moves the origin column to the target index;
8
11
  //
@@ -131,7 +134,7 @@ import { isValidReorder, moveTableColumn } from './reorder-utils';
131
134
  // moveColumn(x, y, options)(state.tr)
132
135
  // );
133
136
  // ```
134
- export const moveColumn = (originColumnIndex, targetColumnIndex, options = {
137
+ export const moveColumn = (state, originColumnIndex, targetColumnIndex, options = {
135
138
  tryToFit: false,
136
139
  direction: 0
137
140
  }) => tr => {
@@ -140,20 +143,115 @@ export const moveColumn = (originColumnIndex, targetColumnIndex, options = {
140
143
  if (!table) {
141
144
  return tr;
142
145
  }
146
+
147
+ // normalize the origin index to an array since this supports moving both a single & multiple cols in a single action.
143
148
  if (!Array.isArray(originColumnIndex)) {
144
149
  originColumnIndex = [originColumnIndex];
145
150
  }
146
- const originalColumnRanges = getSelectionRangeInColumn(originColumnIndex[0])(tr);
151
+ const tableMap = TableMap.get(table.node);
152
+ const originalColumnRanges = getSelectionRangeInColumn(Math.min(...originColumnIndex), Math.max(...originColumnIndex))(tr);
147
153
  const targetColumnRanges = getSelectionRangeInColumn(targetColumnIndex)(tr);
148
154
  const indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
149
155
  const indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
156
+ const min = indexesOriginColumn[0];
157
+ const max = indexesOriginColumn[indexesOriginColumn.length - 1];
150
158
  if (indexesOriginColumn.includes(targetColumnIndex)) {
151
159
  return tr;
152
160
  }
153
161
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
154
162
  isValidReorder(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
155
163
  }
156
- const newTable = moveTableColumn(table, indexesOriginColumn, indexesTargetColumn, options.direction);
157
- const newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
164
+ const types = tableNodeTypes(state.schema);
165
+ const direction = normalizeDirection(min, targetColumnIndex, options);
166
+ const actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'](...indexesTargetColumn);
167
+ const {
168
+ rowHeaderEnabled,
169
+ columnHeaderEnabled
170
+ } = determineTableHeaderStateFromTableNode(table.node, tableMap, types);
171
+ const createContentNode = createContentNodeFactory(table);
172
+ const newTr = cloneTr(tr);
173
+ const origins = [];
174
+ for (let y = 0; y < tableMap.height; y++) {
175
+ origins.push([]);
176
+ for (let x = min; x <= max; x++) {
177
+ if (tableMap.isCellMergedTopLeft(y, x)) {
178
+ continue;
179
+ }
180
+ const nodePos = tableMap.map[y * tableMap.width + x];
181
+ origins[y].push(createContentNode(nodePos));
182
+ }
183
+ if (columnHeaderEnabled && (min === 0 || actualTargetIndex === 0)) {
184
+ // This block is handling the situation where a col is moved in/out of the header position. If the header col option
185
+ // is enabled then;
186
+ // When a col is moved out, the col will be converted to a normal col and the col to the right will become the header.
187
+ // When a col is moved in, the old col header needs to be made normal, and the incoming col needs to be made a header.
188
+ // This section only manages what happens to the other col, not the one being moved.
189
+ const nearHeaderCol = min === 0 ? max + 1 : actualTargetIndex;
190
+ const nodePos = tableMap.map[y * tableMap.width + nearHeaderCol];
191
+ const {
192
+ pos,
193
+ node
194
+ } = createContentNode(nodePos);
195
+ newTr.setNodeMarkup(pos, actualTargetIndex !== 0 || rowHeaderEnabled && y === 0 ? types.header_cell : types.cell, node.attrs);
196
+ }
197
+ }
198
+ origins.forEach((row, y) => {
199
+ if (!row.length) {
200
+ // If the origin has no cells to be moved then we can skip moving for this row. This can occur when a cell above rowspans
201
+ // into the current row.
202
+ return;
203
+ }
204
+
205
+ // The actual target index needs to be translated per row, this is because row/col spans can affect the amount of
206
+ // cells each row contains.
207
+ const rowTargetPosition = translateTargetPosition(y, actualTargetIndex, tableMap);
208
+ const node = table.node.nodeAt(rowTargetPosition);
209
+ const pos = table.start + rowTargetPosition;
210
+ const insertPos = direction === 'end' ? newTr.mapping.map(pos + node.nodeSize, 1) : newTr.mapping.map(pos, -1);
211
+ newTr.insert(insertPos, row.map(({
212
+ node
213
+ }, x) => normalizeCellNode(node, rowHeaderEnabled && y === 0, columnHeaderEnabled && actualTargetIndex === 0 && x === 0, types)));
214
+
215
+ // NOTE: only consecutive cells can be moved, this means we can simplify the delete op into a single step which
216
+ // deletes the range of cells.
217
+ const first = row[0];
218
+ const last = row[row.length - 1];
219
+ return newTr.delete(newTr.mapping.map(first.pos, 1), newTr.mapping.map(last.pos + last.node.nodeSize, -1));
220
+ });
158
221
  return newTr;
159
- };
222
+ };
223
+ function normalizeCellNode(cellNode, rowHeaderEnabled, columnHeaderEnabled, types) {
224
+ const newTargetType = rowHeaderEnabled || columnHeaderEnabled ? types.header_cell : types.cell;
225
+ return cellNode.type !== newTargetType ? newTargetType.create(cellNode.attrs, cellNode.content, cellNode.marks) : cellNode;
226
+ }
227
+ function createContentNodeFactory(table) {
228
+ return nodePos => {
229
+ const node = table.node.nodeAt(nodePos);
230
+ const pos = nodePos + table.start;
231
+ return {
232
+ pos,
233
+ start: pos + 1,
234
+ node,
235
+ depth: table.depth + 2
236
+ };
237
+ };
238
+ }
239
+ function translateTargetPosition(row, startIndex, tableMap) {
240
+ if (tableMap.isCellMergedTopLeft(row, startIndex)) {
241
+ // find the closet unmerged position to the left of the target. We scan left first because merged cells will actually
242
+ // reduce the amount of cells in a row.
243
+ for (let x = startIndex - 1; x >= 0; x--) {
244
+ if (!tableMap.isCellMergedTopLeft(row, x)) {
245
+ return tableMap.map[row * tableMap.width + x];
246
+ }
247
+ }
248
+
249
+ // If no index found then we need to look to the right, this can occur when the first cell in the row is merged.
250
+ for (let x = startIndex + 1; x < tableMap.width; x++) {
251
+ if (!tableMap.isCellMergedTopLeft(row, x)) {
252
+ return tableMap.map[row * tableMap.width + x];
253
+ }
254
+ }
255
+ }
256
+ return tableMap.map[row * tableMap.width + startIndex];
257
+ }
@@ -1,7 +1,10 @@
1
1
  import { TableMap } from '../table-map';
2
+ import { determineTableHeaderStateFromTableNode } from './analyse-table';
2
3
  import { cloneTr } from './clone-tr';
3
4
  import { findTable } from './find';
4
5
  import { getSelectionRangeInRow } from './get-selection-range-in-row';
6
+ import { normalizeDirection } from './normalize-direction';
7
+ import { isValidReorder } from './reorder-utils';
5
8
  import { tableNodeTypes } from './table-node-types';
6
9
 
7
10
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
@@ -174,6 +177,9 @@ export const moveRow = (state, originRowIndex, targetRowIndex, options = {
174
177
  targetRowIndex >= originMin && targetRowIndex <= originMax) {
175
178
  return tr;
176
179
  }
180
+ if (!options.tryToFit && indexesTargetRow.length > 1) {
181
+ isValidReorder(originMin, targetRowIndex, indexesTargetRow, 'row');
182
+ }
177
183
  const types = tableNodeTypes(state.schema);
178
184
  const direction = normalizeDirection(originMin, targetRowIndex, options);
179
185
  const actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'](...indexesTargetRow);
@@ -195,7 +201,7 @@ export const moveRow = (state, originRowIndex, targetRowIndex, options = {
195
201
  const {
196
202
  rowHeaderEnabled,
197
203
  columnHeaderEnabled
198
- } = determineTableHeaderState(table.node, tableMap, types);
204
+ } = determineTableHeaderStateFromTableNode(table.node, tableMap, types);
199
205
  if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
200
206
  // This block is handling the situation where a row is moved in/out of the header position. If the header row option
201
207
  // is enabled then;
@@ -225,46 +231,6 @@ export const moveRow = (state, originRowIndex, targetRowIndex, options = {
225
231
  }
226
232
  return tr;
227
233
  };
228
- function normalizeDirection(origin, target, options = {
229
- tryToFit: false,
230
- direction: 0
231
- }) {
232
- const dir = origin < target ? 'end' : 'start';
233
- const override = options.direction < 0 ? 'start' : 'end';
234
- return options.tryToFit && !!options.direction ? override : dir;
235
- }
236
- function determineTableHeaderState(table, tableMap, types) {
237
- // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
238
- // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
239
- // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
240
- // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
241
-
242
- // This is a 3 bit mask;
243
- // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
244
- // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
245
- // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
246
- let mask = 0;
247
-
248
- // At minimum we should have 1 cell in the table.
249
- const topLeftCell = table.nodeAt(tableMap.map[0]);
250
- // If this cell is a header that could indicate
251
- mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
252
- if (tableMap.width > 1) {
253
- const cell = table.nodeAt(tableMap.map[1]);
254
- // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
255
- // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
256
- // was a header cell.
257
- mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
258
- }
259
- if (tableMap.height > 1) {
260
- const cell = table.nodeAt(tableMap.map[tableMap.width]);
261
- mask |= cell && cell.type === types.header_cell ? 4 : 2 * (mask & 1);
262
- }
263
- return {
264
- rowHeaderEnabled: mask === 7 || mask === 3,
265
- columnHeaderEnabled: mask === 7 || mask === 5
266
- };
267
- }
268
234
 
269
235
  /**
270
236
  * This ensures the row node cell type correctly reflect what they should be.
@@ -0,0 +1,8 @@
1
+ export function normalizeDirection(origin, target, options = {
2
+ tryToFit: false,
3
+ direction: 0
4
+ }) {
5
+ const dir = origin < target ? 'end' : 'start';
6
+ const override = options.direction < 0 ? 'start' : 'end';
7
+ return options.tryToFit && !!options.direction ? override : dir;
8
+ }
@@ -136,9 +136,34 @@ export var TableMap = /*#__PURE__*/function () {
136
136
  }, {
137
137
  key: "isPosMerged",
138
138
  value: function isPosMerged(pos) {
139
- return this.map.filter(function (cellPos) {
140
- return cellPos === pos;
141
- }).length > 1;
139
+ return this.map.includes(pos, this.map.indexOf(pos) + 1);
140
+ }
141
+ }, {
142
+ key: "isCellMerged",
143
+ value: function isCellMerged(row, col) {
144
+ return this.isCellMergedTopLeft(row, col) || this.isCellMergedBottomRight(row, col);
145
+ }
146
+ }, {
147
+ key: "isCellMergedTopLeft",
148
+ value: function isCellMergedTopLeft(row, col) {
149
+ var pos = this.map[row * this.width + col];
150
+ return (
151
+ // top
152
+ row > 0 && pos === this.map[(row - 1) * this.width + col] ||
153
+ // left
154
+ col > 0 && pos === this.map[row * this.width + (col - 1)]
155
+ );
156
+ }
157
+ }, {
158
+ key: "isCellMergedBottomRight",
159
+ value: function isCellMergedBottomRight(row, col) {
160
+ var pos = this.map[row * this.width + col];
161
+ return (
162
+ // bottom
163
+ row < this.height - 1 && pos === this.map[(row + 1) * this.width + col] ||
164
+ // right
165
+ col < this.width - 1 && pos === this.map[row * this.width + (col + 1)]
166
+ );
142
167
  }
143
168
 
144
169
  // :: (number, string, number) → ?number
@@ -0,0 +1,32 @@
1
+ export function determineTableHeaderStateFromTableNode(table, tableMap, types) {
2
+ // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
3
+ // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
4
+ // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
5
+ // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
6
+
7
+ // This is a 3 bit mask;
8
+ // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
9
+ // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
10
+ // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
11
+ var mask = 0;
12
+
13
+ // At minimum we should have 1 cell in the table.
14
+ var topLeftCell = table.nodeAt(tableMap.map[0]);
15
+ // If this cell is a header that could indicate
16
+ mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
17
+ if (tableMap.width > 1) {
18
+ var cell = table.nodeAt(tableMap.map[1]);
19
+ // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
20
+ // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
21
+ // was a header cell.
22
+ mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
23
+ }
24
+ if (tableMap.height > 1) {
25
+ var _cell = table.nodeAt(tableMap.map[tableMap.width]);
26
+ mask |= _cell && _cell.type === types.header_cell ? 4 : 2 * (mask & 1);
27
+ }
28
+ return {
29
+ rowHeaderEnabled: mask === 7 || mask === 3,
30
+ columnHeaderEnabled: mask === 7 || mask === 5
31
+ };
32
+ }
@@ -2,10 +2,11 @@ import { getCellsInColumn } from './get-cells-in-column';
2
2
  import { getCellsInRow } from './get-cells-in-row';
3
3
 
4
4
  // Returns a range of rectangular selection spanning all merged cells around a column at index `columnIndex`.
5
- export var getSelectionRangeInColumn = function getSelectionRangeInColumn(columnIndex) {
5
+ export var getSelectionRangeInColumn = function getSelectionRangeInColumn(startColIndex) {
6
+ var endColIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startColIndex;
6
7
  return function (tr) {
7
- var startIndex = columnIndex;
8
- var endIndex = columnIndex;
8
+ var startIndex = startColIndex;
9
+ var endIndex = endColIndex;
9
10
 
10
11
  // looking for selection start column (startIndex)
11
12
  var _loop = function _loop(i) {
@@ -22,7 +23,7 @@ export var getSelectionRangeInColumn = function getSelectionRangeInColumn(column
22
23
  });
23
24
  }
24
25
  };
25
- for (var i = columnIndex; i >= 0; i--) {
26
+ for (var i = startColIndex; i >= 0; i--) {
26
27
  _loop(i);
27
28
  }
28
29
  // looking for selection end column (endIndex)
@@ -37,7 +38,7 @@ export var getSelectionRangeInColumn = function getSelectionRangeInColumn(column
37
38
  });
38
39
  }
39
40
  };
40
- for (var _i = columnIndex; _i <= endIndex; _i++) {
41
+ for (var _i = startColIndex; _i <= endIndex; _i++) {
41
42
  _loop2(_i);
42
43
  }
43
44
 
@@ -1,8 +1,12 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { TableMap } from '../table-map';
3
+ import { determineTableHeaderStateFromTableNode } from './analyse-table';
1
4
  import { cloneTr } from './clone-tr';
2
5
  import { findTable } from './find';
3
6
  import { getSelectionRangeInColumn } from './get-selection-range-in-column';
4
- import { isValidReorder, moveTableColumn } from './reorder-utils';
5
-
7
+ import { normalizeDirection } from './normalize-direction';
8
+ import { isValidReorder } from './reorder-utils';
9
+ import { tableNodeTypes } from './table-node-types';
6
10
  // :: (originColumnIndex: number, targetColumnIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
7
11
  // Returns a new transaction that moves the origin column to the target index;
8
12
  //
@@ -131,8 +135,8 @@ import { isValidReorder, moveTableColumn } from './reorder-utils';
131
135
  // moveColumn(x, y, options)(state.tr)
132
136
  // );
133
137
  // ```
134
- export var moveColumn = function moveColumn(originColumnIndex, targetColumnIndex) {
135
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
138
+ export var moveColumn = function moveColumn(state, originColumnIndex, targetColumnIndex) {
139
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
136
140
  tryToFit: false,
137
141
  direction: 0
138
142
  };
@@ -142,21 +146,115 @@ export var moveColumn = function moveColumn(originColumnIndex, targetColumnIndex
142
146
  if (!table) {
143
147
  return tr;
144
148
  }
149
+
150
+ // normalize the origin index to an array since this supports moving both a single & multiple cols in a single action.
145
151
  if (!Array.isArray(originColumnIndex)) {
146
152
  originColumnIndex = [originColumnIndex];
147
153
  }
148
- var originalColumnRanges = getSelectionRangeInColumn(originColumnIndex[0])(tr);
154
+ var tableMap = TableMap.get(table.node);
155
+ var originalColumnRanges = getSelectionRangeInColumn(Math.min.apply(Math, _toConsumableArray(originColumnIndex)), Math.max.apply(Math, _toConsumableArray(originColumnIndex)))(tr);
149
156
  var targetColumnRanges = getSelectionRangeInColumn(targetColumnIndex)(tr);
150
157
  var indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
151
158
  var indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
159
+ var min = indexesOriginColumn[0];
160
+ var max = indexesOriginColumn[indexesOriginColumn.length - 1];
152
161
  if (indexesOriginColumn.includes(targetColumnIndex)) {
153
162
  return tr;
154
163
  }
155
164
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
156
165
  isValidReorder(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
157
166
  }
158
- var newTable = moveTableColumn(table, indexesOriginColumn, indexesTargetColumn, options.direction);
159
- var newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
167
+ var types = tableNodeTypes(state.schema);
168
+ var direction = normalizeDirection(min, targetColumnIndex, options);
169
+ var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, _toConsumableArray(indexesTargetColumn));
170
+ var _determineTableHeader = determineTableHeaderStateFromTableNode(table.node, tableMap, types),
171
+ rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
172
+ columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
173
+ var createContentNode = createContentNodeFactory(table);
174
+ var newTr = cloneTr(tr);
175
+ var origins = [];
176
+ for (var y = 0; y < tableMap.height; y++) {
177
+ origins.push([]);
178
+ for (var x = min; x <= max; x++) {
179
+ if (tableMap.isCellMergedTopLeft(y, x)) {
180
+ continue;
181
+ }
182
+ var nodePos = tableMap.map[y * tableMap.width + x];
183
+ origins[y].push(createContentNode(nodePos));
184
+ }
185
+ if (columnHeaderEnabled && (min === 0 || actualTargetIndex === 0)) {
186
+ // This block is handling the situation where a col is moved in/out of the header position. If the header col option
187
+ // is enabled then;
188
+ // When a col is moved out, the col will be converted to a normal col and the col to the right will become the header.
189
+ // When a col is moved in, the old col header needs to be made normal, and the incoming col needs to be made a header.
190
+ // This section only manages what happens to the other col, not the one being moved.
191
+ var nearHeaderCol = min === 0 ? max + 1 : actualTargetIndex;
192
+ var _nodePos = tableMap.map[y * tableMap.width + nearHeaderCol];
193
+ var _createContentNode = createContentNode(_nodePos),
194
+ pos = _createContentNode.pos,
195
+ node = _createContentNode.node;
196
+ newTr.setNodeMarkup(pos, actualTargetIndex !== 0 || rowHeaderEnabled && y === 0 ? types.header_cell : types.cell, node.attrs);
197
+ }
198
+ }
199
+ origins.forEach(function (row, y) {
200
+ if (!row.length) {
201
+ // If the origin has no cells to be moved then we can skip moving for this row. This can occur when a cell above rowspans
202
+ // into the current row.
203
+ return;
204
+ }
205
+
206
+ // The actual target index needs to be translated per row, this is because row/col spans can affect the amount of
207
+ // cells each row contains.
208
+ var rowTargetPosition = translateTargetPosition(y, actualTargetIndex, tableMap);
209
+ var node = table.node.nodeAt(rowTargetPosition);
210
+ var pos = table.start + rowTargetPosition;
211
+ var insertPos = direction === 'end' ? newTr.mapping.map(pos + node.nodeSize, 1) : newTr.mapping.map(pos, -1);
212
+ newTr.insert(insertPos, row.map(function (_ref, x) {
213
+ var node = _ref.node;
214
+ return normalizeCellNode(node, rowHeaderEnabled && y === 0, columnHeaderEnabled && actualTargetIndex === 0 && x === 0, types);
215
+ }));
216
+
217
+ // NOTE: only consecutive cells can be moved, this means we can simplify the delete op into a single step which
218
+ // deletes the range of cells.
219
+ var first = row[0];
220
+ var last = row[row.length - 1];
221
+ return newTr.delete(newTr.mapping.map(first.pos, 1), newTr.mapping.map(last.pos + last.node.nodeSize, -1));
222
+ });
160
223
  return newTr;
161
224
  };
162
- };
225
+ };
226
+ function normalizeCellNode(cellNode, rowHeaderEnabled, columnHeaderEnabled, types) {
227
+ var newTargetType = rowHeaderEnabled || columnHeaderEnabled ? types.header_cell : types.cell;
228
+ return cellNode.type !== newTargetType ? newTargetType.create(cellNode.attrs, cellNode.content, cellNode.marks) : cellNode;
229
+ }
230
+ function createContentNodeFactory(table) {
231
+ return function (nodePos) {
232
+ var node = table.node.nodeAt(nodePos);
233
+ var pos = nodePos + table.start;
234
+ return {
235
+ pos: pos,
236
+ start: pos + 1,
237
+ node: node,
238
+ depth: table.depth + 2
239
+ };
240
+ };
241
+ }
242
+ function translateTargetPosition(row, startIndex, tableMap) {
243
+ if (tableMap.isCellMergedTopLeft(row, startIndex)) {
244
+ // find the closet unmerged position to the left of the target. We scan left first because merged cells will actually
245
+ // reduce the amount of cells in a row.
246
+ for (var x = startIndex - 1; x >= 0; x--) {
247
+ if (!tableMap.isCellMergedTopLeft(row, x)) {
248
+ return tableMap.map[row * tableMap.width + x];
249
+ }
250
+ }
251
+
252
+ // If no index found then we need to look to the right, this can occur when the first cell in the row is merged.
253
+ for (var _x = startIndex + 1; _x < tableMap.width; _x++) {
254
+ if (!tableMap.isCellMergedTopLeft(row, _x)) {
255
+ return tableMap.map[row * tableMap.width + _x];
256
+ }
257
+ }
258
+ }
259
+ return tableMap.map[row * tableMap.width + startIndex];
260
+ }
@@ -1,9 +1,12 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import { TableMap } from '../table-map';
4
+ import { determineTableHeaderStateFromTableNode } from './analyse-table';
4
5
  import { cloneTr } from './clone-tr';
5
6
  import { findTable } from './find';
6
7
  import { getSelectionRangeInRow } from './get-selection-range-in-row';
8
+ import { normalizeDirection } from './normalize-direction';
9
+ import { isValidReorder } from './reorder-utils';
7
10
  import { tableNodeTypes } from './table-node-types';
8
11
 
9
12
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
@@ -186,6 +189,9 @@ export var moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
186
189
  targetRowIndex >= originMin && targetRowIndex <= originMax) {
187
190
  return tr;
188
191
  }
192
+ if (!options.tryToFit && indexesTargetRow.length > 1) {
193
+ isValidReorder(originMin, targetRowIndex, indexesTargetRow, 'row');
194
+ }
189
195
  var types = tableNodeTypes(state.schema);
190
196
  var direction = normalizeDirection(originMin, targetRowIndex, options);
191
197
  var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, _toConsumableArray(indexesTargetRow));
@@ -206,7 +212,7 @@ export var moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
206
212
  var targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
207
213
  if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
208
214
  var newTr = cloneTr(tr);
209
- var _determineTableHeader = determineTableHeaderState(table.node, tableMap, types),
215
+ var _determineTableHeader = determineTableHeaderStateFromTableNode(table.node, tableMap, types),
210
216
  rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
211
217
  columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
212
218
  if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
@@ -239,47 +245,6 @@ export var moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
239
245
  return tr;
240
246
  };
241
247
  };
242
- function normalizeDirection(origin, target) {
243
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
244
- tryToFit: false,
245
- direction: 0
246
- };
247
- var dir = origin < target ? 'end' : 'start';
248
- var override = options.direction < 0 ? 'start' : 'end';
249
- return options.tryToFit && !!options.direction ? override : dir;
250
- }
251
- function determineTableHeaderState(table, tableMap, types) {
252
- // We only really need to check the 2nd cell in the row/col if it's a header, since we only support a single full row/col header on the top & left
253
- // of a table. We can assume that if the 2nd cell is a header then the entire row/col is a header.
254
- // Be carefull though! when checking the 1st cell as it shares its header state with both row/cols.
255
- // This means we wont be able to reliably identify header state on tables smaller the 2x2, however we can do best guess.
256
-
257
- // This is a 3 bit mask;
258
- // bit: 0 = Identifies if the cell at (0, 0) (row, col - 0-based) is a header cell or not
259
- // bit: 1 = Identifies if the cell at (0, 1) is a header cell or not
260
- // bit: 2 = Identifies if the cell at (1, 0) is a header cell or not
261
- var mask = 0;
262
-
263
- // At minimum we should have 1 cell in the table.
264
- var topLeftCell = table.nodeAt(tableMap.map[0]);
265
- // If this cell is a header that could indicate
266
- mask |= topLeftCell && topLeftCell.type === types.header_cell ? 1 : 0;
267
- if (tableMap.width > 1) {
268
- var cell = table.nodeAt(tableMap.map[1]);
269
- // If the cell at (0, 1) is a header then we set the bit flag to indicate row headers are enabled, otherwise if it's
270
- // not then we will set the col headers enabled flag (and vice versa in the branch below) only if the cell at (0,0)
271
- // was a header cell.
272
- mask |= cell && cell.type === types.header_cell ? 2 : 4 * (mask & 1);
273
- }
274
- if (tableMap.height > 1) {
275
- var _cell = table.nodeAt(tableMap.map[tableMap.width]);
276
- mask |= _cell && _cell.type === types.header_cell ? 4 : 2 * (mask & 1);
277
- }
278
- return {
279
- rowHeaderEnabled: mask === 7 || mask === 3,
280
- columnHeaderEnabled: mask === 7 || mask === 5
281
- };
282
- }
283
248
 
284
249
  /**
285
250
  * This ensures the row node cell type correctly reflect what they should be.
@@ -0,0 +1,9 @@
1
+ export function normalizeDirection(origin, target) {
2
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
3
+ tryToFit: false,
4
+ direction: 0
5
+ };
6
+ var dir = origin < target ? 'end' : 'start';
7
+ var override = options.direction < 0 ? 'start' : 'end';
8
+ return options.tryToFit && !!options.direction ? override : dir;
9
+ }
@@ -56,6 +56,9 @@ export declare class TableMap {
56
56
  colCount(pos: number): number;
57
57
  rowCount(pos: number): number;
58
58
  isPosMerged(pos: number): boolean;
59
+ isCellMerged(row: number, col: number): boolean;
60
+ isCellMergedTopLeft(row: number, col: number): boolean;
61
+ isCellMergedBottomRight(row: number, col: number): boolean;
59
62
  nextCell(pos: number, axis: Axis, dir: number): number | null;
60
63
  rectBetween(a: number, b: number): Rect;
61
64
  cellsInRect(rect: Rect): number[];
@@ -0,0 +1,7 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ import type { TableMap } from '../table-map';
3
+ import type { TableNodeCache } from './table-node-types';
4
+ export declare function determineTableHeaderStateFromTableNode(table: PMNode, tableMap: TableMap, types: TableNodeCache): {
5
+ rowHeaderEnabled: boolean;
6
+ columnHeaderEnabled: boolean;
7
+ };
@@ -1,3 +1,3 @@
1
- import { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- import { SelectionRange } from '../types';
3
- export declare const getSelectionRangeInColumn: (columnIndex: number) => (tr: Transaction) => SelectionRange | undefined;
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { SelectionRange } from '../types';
3
+ export declare const getSelectionRangeInColumn: (startColIndex: number, endColIndex?: number) => (tr: Transaction) => SelectionRange | undefined;
@@ -1,5 +1,5 @@
1
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveColumn: (originColumnIndex: number | number[], targetColumnIndex: number, options?: {
1
+ import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare const moveColumn: (state: EditorState, originColumnIndex: number | number[], targetColumnIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
@@ -0,0 +1,4 @@
1
+ export declare function normalizeDirection(origin: number, target: number, options?: {
2
+ tryToFit: boolean;
3
+ direction: number;
4
+ }): 'start' | 'end';
@@ -56,6 +56,9 @@ export declare class TableMap {
56
56
  colCount(pos: number): number;
57
57
  rowCount(pos: number): number;
58
58
  isPosMerged(pos: number): boolean;
59
+ isCellMerged(row: number, col: number): boolean;
60
+ isCellMergedTopLeft(row: number, col: number): boolean;
61
+ isCellMergedBottomRight(row: number, col: number): boolean;
59
62
  nextCell(pos: number, axis: Axis, dir: number): number | null;
60
63
  rectBetween(a: number, b: number): Rect;
61
64
  cellsInRect(rect: Rect): number[];
@@ -0,0 +1,7 @@
1
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ import type { TableMap } from '../table-map';
3
+ import type { TableNodeCache } from './table-node-types';
4
+ export declare function determineTableHeaderStateFromTableNode(table: PMNode, tableMap: TableMap, types: TableNodeCache): {
5
+ rowHeaderEnabled: boolean;
6
+ columnHeaderEnabled: boolean;
7
+ };
@@ -1,3 +1,3 @@
1
- import { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- import { SelectionRange } from '../types';
3
- export declare const getSelectionRangeInColumn: (columnIndex: number) => (tr: Transaction) => SelectionRange | undefined;
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { SelectionRange } from '../types';
3
+ export declare const getSelectionRangeInColumn: (startColIndex: number, endColIndex?: number) => (tr: Transaction) => SelectionRange | undefined;
@@ -1,5 +1,5 @@
1
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveColumn: (originColumnIndex: number | number[], targetColumnIndex: number, options?: {
1
+ import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare const moveColumn: (state: EditorState, originColumnIndex: number | number[], targetColumnIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
@@ -0,0 +1,4 @@
1
+ export declare function normalizeDirection(origin: number, target: number, options?: {
2
+ tryToFit: boolean;
3
+ direction: number;
4
+ }): 'start' | 'end';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-tables",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "A package that contains common classes and utility functions for editor tables",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
package/tsconfig.json CHANGED
@@ -1,15 +1,44 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.json",
3
3
  "compilerOptions": {
4
- "baseUrl": "./"
4
+ "baseUrl": "./",
5
+ "paths": {
6
+ "@atlaskit/platform-feature-flags": [
7
+ "../../platform/feature-flags/src/index.ts"
8
+ ],
9
+ "@atlassian/feature-flags-test-utils": [
10
+ "../../platform/feature-flags-test-utils/src/index.ts"
11
+ ],
12
+ "@atlaskit/editor-tables/cell-bookmark": [
13
+ "src/cell-bookmark.ts"
14
+ ],
15
+ "@atlaskit/editor-tables/cell-selection": [
16
+ "src/cell-selection.ts"
17
+ ],
18
+ "@atlaskit/editor-tables/pm-plugins": [
19
+ "src/pm-plugins.ts"
20
+ ],
21
+ "@atlaskit/editor-tables/table-map": [
22
+ "src/table-map.ts"
23
+ ],
24
+ "@atlaskit/editor-tables/types": [
25
+ "src/types.ts"
26
+ ],
27
+ "@atlaskit/editor-tables/utils": [
28
+ "src/utils.ts"
29
+ ],
30
+ "@atlaskit/editor-tables": [
31
+ "./src"
32
+ ]
33
+ }
5
34
  },
6
35
  "include": [
7
36
  "src/**/*.ts",
8
37
  "src/**/*.tsx",
9
38
  "./docs/**/*.ts",
10
- "./docs/**/*.tsx",
39
+ "./docs/**/*.tsx"
11
40
  ],
12
41
  "exclude": [
13
42
  "node_modules"
14
43
  ]
15
- }
44
+ }