@atlaskit/editor-tables 2.3.18 → 2.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/editor-tables
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#67400](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/67400) [`191436e36f93`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/191436e36f93) - Optimised the table move row logic to perform insert/delete steps rather than an entire table replacement
8
+
3
9
  ## 2.3.18
4
10
 
5
11
  ### Patch Changes
@@ -7,10 +7,11 @@ exports.getSelectionRangeInRow = 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 row at index `rowIndex`.
10
- var getSelectionRangeInRow = exports.getSelectionRangeInRow = function getSelectionRangeInRow(rowIndex) {
10
+ var getSelectionRangeInRow = exports.getSelectionRangeInRow = function getSelectionRangeInRow(startRowIndex) {
11
+ var endRowIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startRowIndex;
11
12
  return function (tr) {
12
- var startIndex = rowIndex;
13
- var endIndex = rowIndex;
13
+ var startIndex = startRowIndex;
14
+ var endIndex = endRowIndex;
14
15
 
15
16
  // looking for selection start row (startIndex)
16
17
  var _loop = function _loop(i) {
@@ -27,7 +28,7 @@ var getSelectionRangeInRow = exports.getSelectionRangeInRow = function getSelect
27
28
  });
28
29
  }
29
30
  };
30
- for (var i = rowIndex; i >= 0; i--) {
31
+ for (var i = startRowIndex; i >= 0; i--) {
31
32
  _loop(i);
32
33
  }
33
34
  // looking for selection end row (endIndex)
@@ -42,7 +43,7 @@ var getSelectionRangeInRow = exports.getSelectionRangeInRow = function getSelect
42
43
  });
43
44
  }
44
45
  };
45
- for (var _i = rowIndex; _i <= endIndex; _i++) {
46
+ for (var _i = startRowIndex; _i <= endIndex; _i++) {
46
47
  _loop2(_i);
47
48
  }
48
49
 
@@ -147,7 +147,10 @@ var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, tar
147
147
  if (!table) {
148
148
  return tr;
149
149
  }
150
- var originalColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(originColumnIndex)(tr);
150
+ if (!Array.isArray(originColumnIndex)) {
151
+ originColumnIndex = [originColumnIndex];
152
+ }
153
+ var originalColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(originColumnIndex[0])(tr);
151
154
  var targetColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(targetColumnIndex)(tr);
152
155
  var indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
153
156
  var indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
@@ -155,7 +158,7 @@ var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, tar
155
158
  return tr;
156
159
  }
157
160
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
158
- (0, _reorderUtils.isValidReorder)(originColumnIndex, targetColumnIndex, indexesTargetColumn, 'column');
161
+ (0, _reorderUtils.isValidReorder)(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
159
162
  }
160
163
  var newTable = (0, _reorderUtils.moveTableColumn)(table, indexesOriginColumn, indexesTargetColumn, options.direction);
161
164
  var newTr = (0, _cloneTr.cloneTr)(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
@@ -1,13 +1,17 @@
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.moveRow = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
+ var _tableMap = require("../table-map");
7
11
  var _cloneTr = require("./clone-tr");
8
12
  var _find = require("./find");
9
13
  var _getSelectionRangeInRow = require("./get-selection-range-in-row");
10
- var _reorderUtils = require("./reorder-utils");
14
+ var _tableNodeTypes = require("./table-node-types");
11
15
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
12
16
  // Returns a new transaction that moves the origin row to the target index;
13
17
  //
@@ -151,8 +155,8 @@ var _reorderUtils = require("./reorder-utils");
151
155
  // moveRow(x, y, options)(state.tr)
152
156
  // );
153
157
  // ```
154
- var moveRow = exports.moveRow = function moveRow(originRowIndex, targetRowIndex) {
155
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
158
+ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
159
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
156
160
  tryToFit: false,
157
161
  direction: 0
158
162
  };
@@ -161,18 +165,137 @@ var moveRow = exports.moveRow = function moveRow(originRowIndex, targetRowIndex)
161
165
  if (!table) {
162
166
  return tr;
163
167
  }
164
- var originalRowRanges = (0, _getSelectionRangeInRow.getSelectionRangeInRow)(originRowIndex)(tr);
168
+
169
+ // normalize the origin index to an array since move row support moving both a single & multiple rows in a single action.
170
+ if (!Array.isArray(originRowIndex)) {
171
+ originRowIndex = [originRowIndex];
172
+ }
173
+ if (originRowIndex.includes(targetRowIndex)) {
174
+ return tr;
175
+ }
176
+ var tableMap = _tableMap.TableMap.get(table.node);
177
+ var _originRowIndex$reduc = originRowIndex.reduce(function (_ref, cur) {
178
+ var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
179
+ min = _ref2[0],
180
+ max = _ref2[1];
181
+ return [Math.min(min, cur), Math.max(max, cur)];
182
+ }, [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]),
183
+ _originRowIndex$reduc2 = (0, _slicedToArray2.default)(_originRowIndex$reduc, 2),
184
+ originMin = _originRowIndex$reduc2[0],
185
+ originMax = _originRowIndex$reduc2[1];
186
+ var originalRowRanges = (0, _getSelectionRangeInRow.getSelectionRangeInRow)(originMin, originMax)(tr);
165
187
  var targetRowRanges = (0, _getSelectionRangeInRow.getSelectionRangeInRow)(targetRowIndex)(tr);
166
188
  var indexesOriginRow = (originalRowRanges === null || originalRowRanges === void 0 ? void 0 : originalRowRanges.indexes) || [];
167
189
  var indexesTargetRow = (targetRowRanges === null || targetRowRanges === void 0 ? void 0 : targetRowRanges.indexes) || [];
168
- if (indexesOriginRow.includes(targetRowIndex)) {
190
+ if (originMin < 0 || originMin === Number.MAX_SAFE_INTEGER || originMax >= tableMap.height || originMax === Number.MIN_SAFE_INTEGER ||
191
+ // The target index cannot be within the origin bounds
192
+ targetRowIndex >= originMin && targetRowIndex <= originMax) {
169
193
  return tr;
170
194
  }
171
- if (!options.tryToFit && indexesTargetRow.length > 1) {
172
- (0, _reorderUtils.isValidReorder)(originRowIndex, targetRowIndex, indexesTargetRow, 'row');
195
+ var types = (0, _tableNodeTypes.tableNodeTypes)(state.schema);
196
+ var direction = normalizeDirection(originMin, targetRowIndex, options);
197
+ var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, (0, _toConsumableArray2.default)(indexesTargetRow));
198
+ var originPositions = indexesOriginRow.map(function (index) {
199
+ return tableMap.positionAt(index, 0, table.node) + table.pos;
200
+ });
201
+ var originNodes = originPositions.reduce(function (acc, pos) {
202
+ var node = tr.doc.nodeAt(tr.mapping.map(pos));
203
+ if (node) {
204
+ return [].concat((0, _toConsumableArray2.default)(acc), [{
205
+ pos: pos,
206
+ node: node
207
+ }]);
208
+ }
209
+ return acc;
210
+ }, []);
211
+ var targetPos = tableMap.positionAt(actualTargetIndex, 0, table.node) + table.pos;
212
+ var targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
213
+ if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
214
+ var newTr = (0, _cloneTr.cloneTr)(tr);
215
+ var _determineTableHeader = determineTableHeaderState(table.node, tableMap, types),
216
+ rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
217
+ columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
218
+ if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
219
+ // This block is handling the situation where a row is moved in/out of the header position. If the header row option
220
+ // is enabled then;
221
+ // When a row is moved out, the row will be converted to a normal row and the row below it will become the header.
222
+ // When a row is moved in, the old row header needs to be made normal, and the incoming row needs to be made a header.
223
+ // This section only manages what happens to the other row, no the one being moved.
224
+ var nearHeaderPos = tableMap.positionAt(originMin === 0 ? originMax + 1 : actualTargetIndex, 0, table.node) + table.pos;
225
+ var nearHeaderNode = newTr.doc.nodeAt(newTr.mapping.map(nearHeaderPos));
226
+ if (nearHeaderNode) {
227
+ nearHeaderNode.forEach(function (node, offset, index) {
228
+ var start = newTr.mapping.map(nearHeaderPos + 1 + offset);
229
+ newTr.setNodeMarkup(start, actualTargetIndex !== 0 || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell, node.attrs);
230
+ });
231
+ }
232
+ }
233
+ var insertPos = direction === 'end' ? newTr.mapping.map(targetPos + targetNode.nodeSize, 1) : newTr.mapping.map(targetPos, -1);
234
+ newTr.insert(insertPos, originNodes.map(function (_ref3, index) {
235
+ var node = _ref3.node;
236
+ return normalizeRowNode(node, rowHeaderEnabled && actualTargetIndex === 0 && index === 0, columnHeaderEnabled, types);
237
+ }));
238
+ originNodes.forEach(function (_ref4) {
239
+ var pos = _ref4.pos,
240
+ node = _ref4.node;
241
+ newTr.delete(newTr.mapping.map(pos, 1), newTr.mapping.map(pos + node.nodeSize, -1));
242
+ });
243
+ return newTr;
173
244
  }
174
- var newTable = (0, _reorderUtils.moveTableRow)(table, indexesOriginRow, indexesTargetRow, options.direction);
175
- var newTr = (0, _cloneTr.cloneTr)(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
176
- return newTr;
245
+ return tr;
177
246
  };
178
- };
247
+ };
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
+
290
+ /**
291
+ * This ensures the row node cell type correctly reflect what they should be.
292
+ * @returns A copy of the rowNode
293
+ */
294
+ function normalizeRowNode(rowNode, rowHeaderEnabled, columnHeaderEnabled, types) {
295
+ var content = [];
296
+ rowNode.forEach(function (node, offset, index) {
297
+ var newTargetType = rowHeaderEnabled || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell;
298
+ content.push(node.type !== newTargetType ? newTargetType.create(node.attrs, node.content, node.marks) : node);
299
+ });
300
+ return rowNode.type.create(rowNode.attrs, content, rowNode.marks);
301
+ }
@@ -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 row at index `rowIndex`.
5
- export const getSelectionRangeInRow = rowIndex => tr => {
6
- let startIndex = rowIndex;
7
- let endIndex = rowIndex;
5
+ export const getSelectionRangeInRow = (startRowIndex, endRowIndex = startRowIndex) => tr => {
6
+ let startIndex = startRowIndex;
7
+ let endIndex = endRowIndex;
8
8
 
9
9
  // looking for selection start row (startIndex)
10
- for (let i = rowIndex; i >= 0; i--) {
10
+ for (let i = startRowIndex; i >= 0; i--) {
11
11
  const cells = getCellsInRow(i)(tr.selection);
12
12
  if (cells) {
13
13
  cells.forEach(cell => {
@@ -22,7 +22,7 @@ export const getSelectionRangeInRow = rowIndex => tr => {
22
22
  }
23
23
  }
24
24
  // looking for selection end row (endIndex)
25
- for (let i = rowIndex; i <= endIndex; i++) {
25
+ for (let i = startRowIndex; i <= endIndex; i++) {
26
26
  const cells = getCellsInRow(i)(tr.selection);
27
27
  if (cells) {
28
28
  cells.forEach(cell => {
@@ -140,7 +140,10 @@ export const moveColumn = (originColumnIndex, targetColumnIndex, options = {
140
140
  if (!table) {
141
141
  return tr;
142
142
  }
143
- const originalColumnRanges = getSelectionRangeInColumn(originColumnIndex)(tr);
143
+ if (!Array.isArray(originColumnIndex)) {
144
+ originColumnIndex = [originColumnIndex];
145
+ }
146
+ const originalColumnRanges = getSelectionRangeInColumn(originColumnIndex[0])(tr);
144
147
  const targetColumnRanges = getSelectionRangeInColumn(targetColumnIndex)(tr);
145
148
  const indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
146
149
  const indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
@@ -148,7 +151,7 @@ export const moveColumn = (originColumnIndex, targetColumnIndex, options = {
148
151
  return tr;
149
152
  }
150
153
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
151
- isValidReorder(originColumnIndex, targetColumnIndex, indexesTargetColumn, 'column');
154
+ isValidReorder(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
152
155
  }
153
156
  const newTable = moveTableColumn(table, indexesOriginColumn, indexesTargetColumn, options.direction);
154
157
  const newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
@@ -1,7 +1,8 @@
1
+ import { TableMap } from '../table-map';
1
2
  import { cloneTr } from './clone-tr';
2
3
  import { findTable } from './find';
3
4
  import { getSelectionRangeInRow } from './get-selection-range-in-row';
4
- import { isValidReorder, moveTableRow } from './reorder-utils';
5
+ import { tableNodeTypes } from './table-node-types';
5
6
 
6
7
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
7
8
  // Returns a new transaction that moves the origin row to the target index;
@@ -146,7 +147,7 @@ import { isValidReorder, moveTableRow } from './reorder-utils';
146
147
  // moveRow(x, y, options)(state.tr)
147
148
  // );
148
149
  // ```
149
- export const moveRow = (originRowIndex, targetRowIndex, options = {
150
+ export const moveRow = (state, originRowIndex, targetRowIndex, options = {
150
151
  tryToFit: false,
151
152
  direction: 0
152
153
  }) => tr => {
@@ -154,17 +155,126 @@ export const moveRow = (originRowIndex, targetRowIndex, options = {
154
155
  if (!table) {
155
156
  return tr;
156
157
  }
157
- const originalRowRanges = getSelectionRangeInRow(originRowIndex)(tr);
158
+
159
+ // normalize the origin index to an array since move row support moving both a single & multiple rows in a single action.
160
+ if (!Array.isArray(originRowIndex)) {
161
+ originRowIndex = [originRowIndex];
162
+ }
163
+ if (originRowIndex.includes(targetRowIndex)) {
164
+ return tr;
165
+ }
166
+ const tableMap = TableMap.get(table.node);
167
+ const [originMin, originMax] = originRowIndex.reduce(([min, max], cur) => [Math.min(min, cur), Math.max(max, cur)], [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]);
168
+ const originalRowRanges = getSelectionRangeInRow(originMin, originMax)(tr);
158
169
  const targetRowRanges = getSelectionRangeInRow(targetRowIndex)(tr);
159
170
  const indexesOriginRow = (originalRowRanges === null || originalRowRanges === void 0 ? void 0 : originalRowRanges.indexes) || [];
160
171
  const indexesTargetRow = (targetRowRanges === null || targetRowRanges === void 0 ? void 0 : targetRowRanges.indexes) || [];
161
- if (indexesOriginRow.includes(targetRowIndex)) {
172
+ if (originMin < 0 || originMin === Number.MAX_SAFE_INTEGER || originMax >= tableMap.height || originMax === Number.MIN_SAFE_INTEGER ||
173
+ // The target index cannot be within the origin bounds
174
+ targetRowIndex >= originMin && targetRowIndex <= originMax) {
162
175
  return tr;
163
176
  }
164
- if (!options.tryToFit && indexesTargetRow.length > 1) {
165
- isValidReorder(originRowIndex, targetRowIndex, indexesTargetRow, 'row');
177
+ const types = tableNodeTypes(state.schema);
178
+ const direction = normalizeDirection(originMin, targetRowIndex, options);
179
+ const actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'](...indexesTargetRow);
180
+ const originPositions = indexesOriginRow.map(index => tableMap.positionAt(index, 0, table.node) + table.pos);
181
+ const originNodes = originPositions.reduce((acc, pos) => {
182
+ const node = tr.doc.nodeAt(tr.mapping.map(pos));
183
+ if (node) {
184
+ return [...acc, {
185
+ pos,
186
+ node
187
+ }];
188
+ }
189
+ return acc;
190
+ }, []);
191
+ const targetPos = tableMap.positionAt(actualTargetIndex, 0, table.node) + table.pos;
192
+ const targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
193
+ if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
194
+ const newTr = cloneTr(tr);
195
+ const {
196
+ rowHeaderEnabled,
197
+ columnHeaderEnabled
198
+ } = determineTableHeaderState(table.node, tableMap, types);
199
+ if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
200
+ // This block is handling the situation where a row is moved in/out of the header position. If the header row option
201
+ // is enabled then;
202
+ // When a row is moved out, the row will be converted to a normal row and the row below it will become the header.
203
+ // When a row is moved in, the old row header needs to be made normal, and the incoming row needs to be made a header.
204
+ // This section only manages what happens to the other row, no the one being moved.
205
+ const nearHeaderPos = tableMap.positionAt(originMin === 0 ? originMax + 1 : actualTargetIndex, 0, table.node) + table.pos;
206
+ const nearHeaderNode = newTr.doc.nodeAt(newTr.mapping.map(nearHeaderPos));
207
+ if (nearHeaderNode) {
208
+ nearHeaderNode.forEach((node, offset, index) => {
209
+ const start = newTr.mapping.map(nearHeaderPos + 1 + offset);
210
+ newTr.setNodeMarkup(start, actualTargetIndex !== 0 || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell, node.attrs);
211
+ });
212
+ }
213
+ }
214
+ const insertPos = direction === 'end' ? newTr.mapping.map(targetPos + targetNode.nodeSize, 1) : newTr.mapping.map(targetPos, -1);
215
+ newTr.insert(insertPos, originNodes.map(({
216
+ node
217
+ }, index) => normalizeRowNode(node, rowHeaderEnabled && actualTargetIndex === 0 && index === 0, columnHeaderEnabled, types)));
218
+ originNodes.forEach(({
219
+ pos,
220
+ node
221
+ }) => {
222
+ newTr.delete(newTr.mapping.map(pos, 1), newTr.mapping.map(pos + node.nodeSize, -1));
223
+ });
224
+ return newTr;
166
225
  }
167
- const newTable = moveTableRow(table, indexesOriginRow, indexesTargetRow, options.direction);
168
- const newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
169
- return newTr;
170
- };
226
+ return tr;
227
+ };
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
+
269
+ /**
270
+ * This ensures the row node cell type correctly reflect what they should be.
271
+ * @returns A copy of the rowNode
272
+ */
273
+ function normalizeRowNode(rowNode, rowHeaderEnabled, columnHeaderEnabled, types) {
274
+ let content = [];
275
+ rowNode.forEach((node, offset, index) => {
276
+ const newTargetType = rowHeaderEnabled || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell;
277
+ content.push(node.type !== newTargetType ? newTargetType.create(node.attrs, node.content, node.marks) : node);
278
+ });
279
+ return rowNode.type.create(rowNode.attrs, content, rowNode.marks);
280
+ }
@@ -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 row at index `rowIndex`.
5
- export var getSelectionRangeInRow = function getSelectionRangeInRow(rowIndex) {
5
+ export var getSelectionRangeInRow = function getSelectionRangeInRow(startRowIndex) {
6
+ var endRowIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startRowIndex;
6
7
  return function (tr) {
7
- var startIndex = rowIndex;
8
- var endIndex = rowIndex;
8
+ var startIndex = startRowIndex;
9
+ var endIndex = endRowIndex;
9
10
 
10
11
  // looking for selection start row (startIndex)
11
12
  var _loop = function _loop(i) {
@@ -22,7 +23,7 @@ export var getSelectionRangeInRow = function getSelectionRangeInRow(rowIndex) {
22
23
  });
23
24
  }
24
25
  };
25
- for (var i = rowIndex; i >= 0; i--) {
26
+ for (var i = startRowIndex; i >= 0; i--) {
26
27
  _loop(i);
27
28
  }
28
29
  // looking for selection end row (endIndex)
@@ -37,7 +38,7 @@ export var getSelectionRangeInRow = function getSelectionRangeInRow(rowIndex) {
37
38
  });
38
39
  }
39
40
  };
40
- for (var _i = rowIndex; _i <= endIndex; _i++) {
41
+ for (var _i = startRowIndex; _i <= endIndex; _i++) {
41
42
  _loop2(_i);
42
43
  }
43
44
 
@@ -142,7 +142,10 @@ export var moveColumn = function moveColumn(originColumnIndex, targetColumnIndex
142
142
  if (!table) {
143
143
  return tr;
144
144
  }
145
- var originalColumnRanges = getSelectionRangeInColumn(originColumnIndex)(tr);
145
+ if (!Array.isArray(originColumnIndex)) {
146
+ originColumnIndex = [originColumnIndex];
147
+ }
148
+ var originalColumnRanges = getSelectionRangeInColumn(originColumnIndex[0])(tr);
146
149
  var targetColumnRanges = getSelectionRangeInColumn(targetColumnIndex)(tr);
147
150
  var indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
148
151
  var indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
@@ -150,7 +153,7 @@ export var moveColumn = function moveColumn(originColumnIndex, targetColumnIndex
150
153
  return tr;
151
154
  }
152
155
  if (!options.tryToFit && indexesTargetColumn.length > 1) {
153
- isValidReorder(originColumnIndex, targetColumnIndex, indexesTargetColumn, 'column');
156
+ isValidReorder(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
154
157
  }
155
158
  var newTable = moveTableColumn(table, indexesOriginColumn, indexesTargetColumn, options.direction);
156
159
  var newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
@@ -1,7 +1,10 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ import { TableMap } from '../table-map';
1
4
  import { cloneTr } from './clone-tr';
2
5
  import { findTable } from './find';
3
6
  import { getSelectionRangeInRow } from './get-selection-range-in-row';
4
- import { isValidReorder, moveTableRow } from './reorder-utils';
7
+ import { tableNodeTypes } from './table-node-types';
5
8
 
6
9
  // :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
7
10
  // Returns a new transaction that moves the origin row to the target index;
@@ -146,8 +149,8 @@ import { isValidReorder, moveTableRow } from './reorder-utils';
146
149
  // moveRow(x, y, options)(state.tr)
147
150
  // );
148
151
  // ```
149
- export var moveRow = function moveRow(originRowIndex, targetRowIndex) {
150
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
152
+ export var moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
153
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
151
154
  tryToFit: false,
152
155
  direction: 0
153
156
  };
@@ -156,18 +159,137 @@ export var moveRow = function moveRow(originRowIndex, targetRowIndex) {
156
159
  if (!table) {
157
160
  return tr;
158
161
  }
159
- var originalRowRanges = getSelectionRangeInRow(originRowIndex)(tr);
162
+
163
+ // normalize the origin index to an array since move row support moving both a single & multiple rows in a single action.
164
+ if (!Array.isArray(originRowIndex)) {
165
+ originRowIndex = [originRowIndex];
166
+ }
167
+ if (originRowIndex.includes(targetRowIndex)) {
168
+ return tr;
169
+ }
170
+ var tableMap = TableMap.get(table.node);
171
+ var _originRowIndex$reduc = originRowIndex.reduce(function (_ref, cur) {
172
+ var _ref2 = _slicedToArray(_ref, 2),
173
+ min = _ref2[0],
174
+ max = _ref2[1];
175
+ return [Math.min(min, cur), Math.max(max, cur)];
176
+ }, [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]),
177
+ _originRowIndex$reduc2 = _slicedToArray(_originRowIndex$reduc, 2),
178
+ originMin = _originRowIndex$reduc2[0],
179
+ originMax = _originRowIndex$reduc2[1];
180
+ var originalRowRanges = getSelectionRangeInRow(originMin, originMax)(tr);
160
181
  var targetRowRanges = getSelectionRangeInRow(targetRowIndex)(tr);
161
182
  var indexesOriginRow = (originalRowRanges === null || originalRowRanges === void 0 ? void 0 : originalRowRanges.indexes) || [];
162
183
  var indexesTargetRow = (targetRowRanges === null || targetRowRanges === void 0 ? void 0 : targetRowRanges.indexes) || [];
163
- if (indexesOriginRow.includes(targetRowIndex)) {
184
+ if (originMin < 0 || originMin === Number.MAX_SAFE_INTEGER || originMax >= tableMap.height || originMax === Number.MIN_SAFE_INTEGER ||
185
+ // The target index cannot be within the origin bounds
186
+ targetRowIndex >= originMin && targetRowIndex <= originMax) {
164
187
  return tr;
165
188
  }
166
- if (!options.tryToFit && indexesTargetRow.length > 1) {
167
- isValidReorder(originRowIndex, targetRowIndex, indexesTargetRow, 'row');
189
+ var types = tableNodeTypes(state.schema);
190
+ var direction = normalizeDirection(originMin, targetRowIndex, options);
191
+ var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, _toConsumableArray(indexesTargetRow));
192
+ var originPositions = indexesOriginRow.map(function (index) {
193
+ return tableMap.positionAt(index, 0, table.node) + table.pos;
194
+ });
195
+ var originNodes = originPositions.reduce(function (acc, pos) {
196
+ var node = tr.doc.nodeAt(tr.mapping.map(pos));
197
+ if (node) {
198
+ return [].concat(_toConsumableArray(acc), [{
199
+ pos: pos,
200
+ node: node
201
+ }]);
202
+ }
203
+ return acc;
204
+ }, []);
205
+ var targetPos = tableMap.positionAt(actualTargetIndex, 0, table.node) + table.pos;
206
+ var targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
207
+ if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
208
+ var newTr = cloneTr(tr);
209
+ var _determineTableHeader = determineTableHeaderState(table.node, tableMap, types),
210
+ rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
211
+ columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
212
+ if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
213
+ // This block is handling the situation where a row is moved in/out of the header position. If the header row option
214
+ // is enabled then;
215
+ // When a row is moved out, the row will be converted to a normal row and the row below it will become the header.
216
+ // When a row is moved in, the old row header needs to be made normal, and the incoming row needs to be made a header.
217
+ // This section only manages what happens to the other row, no the one being moved.
218
+ var nearHeaderPos = tableMap.positionAt(originMin === 0 ? originMax + 1 : actualTargetIndex, 0, table.node) + table.pos;
219
+ var nearHeaderNode = newTr.doc.nodeAt(newTr.mapping.map(nearHeaderPos));
220
+ if (nearHeaderNode) {
221
+ nearHeaderNode.forEach(function (node, offset, index) {
222
+ var start = newTr.mapping.map(nearHeaderPos + 1 + offset);
223
+ newTr.setNodeMarkup(start, actualTargetIndex !== 0 || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell, node.attrs);
224
+ });
225
+ }
226
+ }
227
+ var insertPos = direction === 'end' ? newTr.mapping.map(targetPos + targetNode.nodeSize, 1) : newTr.mapping.map(targetPos, -1);
228
+ newTr.insert(insertPos, originNodes.map(function (_ref3, index) {
229
+ var node = _ref3.node;
230
+ return normalizeRowNode(node, rowHeaderEnabled && actualTargetIndex === 0 && index === 0, columnHeaderEnabled, types);
231
+ }));
232
+ originNodes.forEach(function (_ref4) {
233
+ var pos = _ref4.pos,
234
+ node = _ref4.node;
235
+ newTr.delete(newTr.mapping.map(pos, 1), newTr.mapping.map(pos + node.nodeSize, -1));
236
+ });
237
+ return newTr;
168
238
  }
169
- var newTable = moveTableRow(table, indexesOriginRow, indexesTargetRow, options.direction);
170
- var newTr = cloneTr(tr).replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
171
- return newTr;
239
+ return tr;
172
240
  };
173
- };
241
+ };
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
+
284
+ /**
285
+ * This ensures the row node cell type correctly reflect what they should be.
286
+ * @returns A copy of the rowNode
287
+ */
288
+ function normalizeRowNode(rowNode, rowHeaderEnabled, columnHeaderEnabled, types) {
289
+ var content = [];
290
+ rowNode.forEach(function (node, offset, index) {
291
+ var newTargetType = rowHeaderEnabled || columnHeaderEnabled && index === 0 ? types.header_cell : types.cell;
292
+ content.push(node.type !== newTargetType ? newTargetType.create(node.attrs, node.content, node.marks) : node);
293
+ });
294
+ return rowNode.type.create(rowNode.attrs, content, rowNode.marks);
295
+ }
@@ -1,3 +1,3 @@
1
- import { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- import { SelectionRange } from '../types';
3
- export declare const getSelectionRangeInRow: (rowIndex: number) => (tr: Transaction) => SelectionRange | undefined;
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { SelectionRange } from '../types';
3
+ export declare const getSelectionRangeInRow: (startRowIndex: number, endRowIndex?: number) => (tr: Transaction) => SelectionRange | undefined;
@@ -1,5 +1,5 @@
1
1
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveColumn: (originColumnIndex: number, targetColumnIndex: number, options?: {
2
+ export declare const moveColumn: (originColumnIndex: number | number[], targetColumnIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
@@ -1,5 +1,5 @@
1
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveRow: (originRowIndex: number, targetRowIndex: number, options?: {
1
+ import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare const moveRow: (state: EditorState, originRowIndex: number | number[], targetRowIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
@@ -1,3 +1,3 @@
1
- import { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- import { SelectionRange } from '../types';
3
- export declare const getSelectionRangeInRow: (rowIndex: number) => (tr: Transaction) => SelectionRange | undefined;
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ import type { SelectionRange } from '../types';
3
+ export declare const getSelectionRangeInRow: (startRowIndex: number, endRowIndex?: number) => (tr: Transaction) => SelectionRange | undefined;
@@ -1,5 +1,5 @@
1
1
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveColumn: (originColumnIndex: number, targetColumnIndex: number, options?: {
2
+ export declare const moveColumn: (originColumnIndex: number | number[], targetColumnIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
@@ -1,5 +1,5 @@
1
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
- export declare const moveRow: (originRowIndex: number, targetRowIndex: number, options?: {
1
+ import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare const moveRow: (state: EditorState, originRowIndex: number | number[], targetRowIndex: number, options?: {
3
3
  tryToFit: boolean;
4
4
  direction: number;
5
5
  }) => (tr: Transaction) => Transaction;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-tables",
3
- "version": "2.3.18",
3
+ "version": "2.4.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/"
7
7
  },
8
- "repository": "https://bitbucket.org/atlassian/atlassian-frontend",
8
+ "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
9
9
  "author": "Atlassian Pty Ltd",
10
10
  "license": "Apache-2.0",
11
11
  "main": "dist/cjs/index.js",