@atlaskit/editor-tables 2.4.0 → 2.5.1
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 +12 -0
- package/dist/cjs/table-map.js +28 -3
- package/dist/cjs/utils/analyse-table.js +38 -0
- package/dist/cjs/utils/get-selection-range-in-column.js +6 -5
- package/dist/cjs/utils/move-column.js +116 -7
- package/dist/cjs/utils/move-row.js +21 -44
- package/dist/cjs/utils/normalize-direction.js +12 -0
- package/dist/es2019/table-map.js +22 -1
- package/dist/es2019/utils/analyse-table.js +32 -0
- package/dist/es2019/utils/get-selection-range-in-column.js +5 -5
- package/dist/es2019/utils/move-column.js +115 -8
- package/dist/es2019/utils/move-row.js +20 -42
- package/dist/es2019/utils/normalize-direction.js +6 -0
- package/dist/esm/table-map.js +28 -3
- package/dist/esm/utils/analyse-table.js +32 -0
- package/dist/esm/utils/get-selection-range-in-column.js +6 -5
- package/dist/esm/utils/move-column.js +116 -9
- package/dist/esm/utils/move-row.js +20 -43
- package/dist/esm/utils/normalize-direction.js +6 -0
- package/dist/types/table-map.d.ts +3 -0
- package/dist/types/types.d.ts +8 -3
- package/dist/types/utils/analyse-table.d.ts +7 -0
- package/dist/types/utils/get-selection-range-in-column.d.ts +3 -3
- package/dist/types/utils/move-column.d.ts +3 -5
- package/dist/types/utils/move-row.d.ts +2 -4
- package/dist/types/utils/normalize-direction.d.ts +2 -0
- package/dist/types-ts4.5/table-map.d.ts +3 -0
- package/dist/types-ts4.5/types.d.ts +8 -3
- package/dist/types-ts4.5/utils/analyse-table.d.ts +7 -0
- package/dist/types-ts4.5/utils/get-selection-range-in-column.d.ts +3 -3
- package/dist/types-ts4.5/utils/move-column.d.ts +3 -5
- package/dist/types-ts4.5/utils/move-row.d.ts +2 -4
- package/dist/types-ts4.5/utils/normalize-direction.d.ts +2 -0
- package/package.json +1 -1
- package/tsconfig.json +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/editor-tables
|
|
2
2
|
|
|
3
|
+
## 2.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#69625](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/69625) [`67d7971c6ddf`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/67d7971c6ddf) - [ux] Tables DnD now supports dragging multiple rows/columns in a single drag using multi-select
|
|
8
|
+
|
|
9
|
+
## 2.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#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
|
|
14
|
+
|
|
3
15
|
## 2.4.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/cjs/table-map.js
CHANGED
|
@@ -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.
|
|
147
|
-
|
|
148
|
-
|
|
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(
|
|
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 =
|
|
13
|
-
var endIndex =
|
|
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 =
|
|
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 =
|
|
46
|
+
for (var _i = startColIndex; _i <= endIndex; _i++) {
|
|
46
47
|
_loop2(_i);
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -1,13 +1,20 @@
|
|
|
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 _cellSelection = require("../cell-selection");
|
|
10
|
+
var _tableMap = require("../table-map");
|
|
11
|
+
var _analyseTable = require("./analyse-table");
|
|
7
12
|
var _cloneTr = require("./clone-tr");
|
|
8
13
|
var _find = require("./find");
|
|
9
14
|
var _getSelectionRangeInColumn = require("./get-selection-range-in-column");
|
|
15
|
+
var _normalizeDirection = require("./normalize-direction");
|
|
10
16
|
var _reorderUtils = require("./reorder-utils");
|
|
17
|
+
var _tableNodeTypes = require("./table-node-types");
|
|
11
18
|
// :: (originColumnIndex: number, targetColumnIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
|
|
12
19
|
// Returns a new transaction that moves the origin column to the target index;
|
|
13
20
|
//
|
|
@@ -136,10 +143,11 @@ var _reorderUtils = require("./reorder-utils");
|
|
|
136
143
|
// moveColumn(x, y, options)(state.tr)
|
|
137
144
|
// );
|
|
138
145
|
// ```
|
|
139
|
-
var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, targetColumnIndex) {
|
|
140
|
-
var options = arguments.length >
|
|
146
|
+
var moveColumn = exports.moveColumn = function moveColumn(state, originColumnIndex, targetColumnIndex) {
|
|
147
|
+
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
|
|
141
148
|
tryToFit: false,
|
|
142
|
-
direction: 0
|
|
149
|
+
direction: 0,
|
|
150
|
+
selectAfterMove: false
|
|
143
151
|
};
|
|
144
152
|
return function (tr) {
|
|
145
153
|
var _originalColumnRanges, _targetColumnRanges$i;
|
|
@@ -147,21 +155,122 @@ var moveColumn = exports.moveColumn = function moveColumn(originColumnIndex, tar
|
|
|
147
155
|
if (!table) {
|
|
148
156
|
return tr;
|
|
149
157
|
}
|
|
158
|
+
|
|
159
|
+
// normalize the origin index to an array since this supports moving both a single & multiple cols in a single action.
|
|
150
160
|
if (!Array.isArray(originColumnIndex)) {
|
|
151
161
|
originColumnIndex = [originColumnIndex];
|
|
152
162
|
}
|
|
153
|
-
var
|
|
163
|
+
var tableMap = _tableMap.TableMap.get(table.node);
|
|
164
|
+
var originalColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(Math.min.apply(Math, (0, _toConsumableArray2.default)(originColumnIndex)), Math.max.apply(Math, (0, _toConsumableArray2.default)(originColumnIndex)))(tr);
|
|
154
165
|
var targetColumnRanges = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(targetColumnIndex)(tr);
|
|
155
166
|
var indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
|
|
156
167
|
var indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
|
|
168
|
+
var min = indexesOriginColumn[0];
|
|
169
|
+
var max = indexesOriginColumn[indexesOriginColumn.length - 1];
|
|
157
170
|
if (indexesOriginColumn.includes(targetColumnIndex)) {
|
|
158
171
|
return tr;
|
|
159
172
|
}
|
|
160
173
|
if (!options.tryToFit && indexesTargetColumn.length > 1) {
|
|
161
174
|
(0, _reorderUtils.isValidReorder)(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
|
|
162
175
|
}
|
|
163
|
-
var
|
|
164
|
-
var
|
|
176
|
+
var types = (0, _tableNodeTypes.tableNodeTypes)(state.schema);
|
|
177
|
+
var direction = (0, _normalizeDirection.normalizeDirection)(min, targetColumnIndex, options);
|
|
178
|
+
var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, (0, _toConsumableArray2.default)(indexesTargetColumn));
|
|
179
|
+
var _determineTableHeader = (0, _analyseTable.determineTableHeaderStateFromTableNode)(table.node, tableMap, types),
|
|
180
|
+
rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
|
|
181
|
+
columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
|
|
182
|
+
var createContentNode = createContentNodeFactory(table);
|
|
183
|
+
var newTr = (0, _cloneTr.cloneTr)(tr);
|
|
184
|
+
var origins = [];
|
|
185
|
+
for (var y = 0; y < tableMap.height; y++) {
|
|
186
|
+
origins.push([]);
|
|
187
|
+
for (var x = min; x <= max; x++) {
|
|
188
|
+
if (tableMap.isCellMergedTopLeft(y, x)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
var nodePos = tableMap.map[y * tableMap.width + x];
|
|
192
|
+
origins[y].push(createContentNode(nodePos));
|
|
193
|
+
}
|
|
194
|
+
if (columnHeaderEnabled && (min === 0 || actualTargetIndex === 0)) {
|
|
195
|
+
// This block is handling the situation where a col is moved in/out of the header position. If the header col option
|
|
196
|
+
// is enabled then;
|
|
197
|
+
// 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.
|
|
198
|
+
// 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.
|
|
199
|
+
// This section only manages what happens to the other col, not the one being moved.
|
|
200
|
+
var nearHeaderCol = min === 0 ? max + 1 : actualTargetIndex;
|
|
201
|
+
var _nodePos = tableMap.map[y * tableMap.width + nearHeaderCol];
|
|
202
|
+
var _createContentNode = createContentNode(_nodePos),
|
|
203
|
+
pos = _createContentNode.pos,
|
|
204
|
+
node = _createContentNode.node;
|
|
205
|
+
newTr.setNodeMarkup(pos, actualTargetIndex !== 0 || rowHeaderEnabled && y === 0 ? types.header_cell : types.cell, node.attrs);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
origins.forEach(function (row, y) {
|
|
209
|
+
if (!row.length) {
|
|
210
|
+
// 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
|
|
211
|
+
// into the current row.
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The actual target index needs to be translated per row, this is because row/col spans can affect the amount of
|
|
216
|
+
// cells each row contains.
|
|
217
|
+
var rowTargetPosition = translateTargetPosition(y, actualTargetIndex, tableMap);
|
|
218
|
+
var node = table.node.nodeAt(rowTargetPosition);
|
|
219
|
+
var pos = table.start + rowTargetPosition;
|
|
220
|
+
var insertPos = direction === 'end' ? newTr.mapping.map(pos + node.nodeSize, 1) : newTr.mapping.map(pos, -1);
|
|
221
|
+
newTr.insert(insertPos, row.map(function (_ref, x) {
|
|
222
|
+
var node = _ref.node;
|
|
223
|
+
return normalizeCellNode(node, rowHeaderEnabled && y === 0, columnHeaderEnabled && actualTargetIndex === 0 && x === 0, types);
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
// NOTE: only consecutive cells can be moved, this means we can simplify the delete op into a single step which
|
|
227
|
+
// deletes the range of cells.
|
|
228
|
+
var first = row[0];
|
|
229
|
+
var last = row[row.length - 1];
|
|
230
|
+
return newTr.delete(newTr.mapping.map(first.pos, 1), newTr.mapping.map(last.pos + last.node.nodeSize, -1));
|
|
231
|
+
});
|
|
232
|
+
if (options.selectAfterMove) {
|
|
233
|
+
var n = indexesOriginColumn.length - 1;
|
|
234
|
+
var selectionRange = (0, _getSelectionRangeInColumn.getSelectionRangeInColumn)(actualTargetIndex - (direction === 'end' ? n : 0), actualTargetIndex + (direction === 'start' ? n : 0))(newTr);
|
|
235
|
+
if (selectionRange) {
|
|
236
|
+
newTr.setSelection(new _cellSelection.CellSelection(selectionRange.$anchor, selectionRange.$head));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
165
239
|
return newTr;
|
|
166
240
|
};
|
|
167
|
-
};
|
|
241
|
+
};
|
|
242
|
+
function normalizeCellNode(cellNode, rowHeaderEnabled, columnHeaderEnabled, types) {
|
|
243
|
+
var newTargetType = rowHeaderEnabled || columnHeaderEnabled ? types.header_cell : types.cell;
|
|
244
|
+
return cellNode.type !== newTargetType ? newTargetType.create(cellNode.attrs, cellNode.content, cellNode.marks) : cellNode;
|
|
245
|
+
}
|
|
246
|
+
function createContentNodeFactory(table) {
|
|
247
|
+
return function (nodePos) {
|
|
248
|
+
var node = table.node.nodeAt(nodePos);
|
|
249
|
+
var pos = nodePos + table.start;
|
|
250
|
+
return {
|
|
251
|
+
pos: pos,
|
|
252
|
+
start: pos + 1,
|
|
253
|
+
node: node,
|
|
254
|
+
depth: table.depth + 2
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function translateTargetPosition(row, startIndex, tableMap) {
|
|
259
|
+
if (tableMap.isCellMergedTopLeft(row, startIndex)) {
|
|
260
|
+
// find the closet unmerged position to the left of the target. We scan left first because merged cells will actually
|
|
261
|
+
// reduce the amount of cells in a row.
|
|
262
|
+
for (var x = startIndex - 1; x >= 0; x--) {
|
|
263
|
+
if (!tableMap.isCellMergedTopLeft(row, x)) {
|
|
264
|
+
return tableMap.map[row * tableMap.width + x];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// If no index found then we need to look to the right, this can occur when the first cell in the row is merged.
|
|
269
|
+
for (var _x = startIndex + 1; _x < tableMap.width; _x++) {
|
|
270
|
+
if (!tableMap.isCellMergedTopLeft(row, _x)) {
|
|
271
|
+
return tableMap.map[row * tableMap.width + _x];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return tableMap.map[row * tableMap.width + startIndex];
|
|
276
|
+
}
|
|
@@ -7,10 +7,14 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
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
|
+
var _cellSelection = require("../cell-selection");
|
|
10
11
|
var _tableMap = require("../table-map");
|
|
12
|
+
var _analyseTable = require("./analyse-table");
|
|
11
13
|
var _cloneTr = require("./clone-tr");
|
|
12
14
|
var _find = require("./find");
|
|
13
15
|
var _getSelectionRangeInRow = require("./get-selection-range-in-row");
|
|
16
|
+
var _normalizeDirection = require("./normalize-direction");
|
|
17
|
+
var _reorderUtils = require("./reorder-utils");
|
|
14
18
|
var _tableNodeTypes = require("./table-node-types");
|
|
15
19
|
// :: (originRowIndex: number, targetRowIndex: targetColumnIndex, options?: MovementOptions) → (tr: Transaction) → Transaction
|
|
16
20
|
// Returns a new transaction that moves the origin row to the target index;
|
|
@@ -158,7 +162,8 @@ var _tableNodeTypes = require("./table-node-types");
|
|
|
158
162
|
var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRowIndex) {
|
|
159
163
|
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
|
|
160
164
|
tryToFit: false,
|
|
161
|
-
direction: 0
|
|
165
|
+
direction: 0,
|
|
166
|
+
selectAfterMove: false
|
|
162
167
|
};
|
|
163
168
|
return function (tr) {
|
|
164
169
|
var table = (0, _find.findTable)(tr.selection);
|
|
@@ -192,8 +197,11 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
|
|
|
192
197
|
targetRowIndex >= originMin && targetRowIndex <= originMax) {
|
|
193
198
|
return tr;
|
|
194
199
|
}
|
|
200
|
+
if (!options.tryToFit && indexesTargetRow.length > 1) {
|
|
201
|
+
(0, _reorderUtils.isValidReorder)(originMin, targetRowIndex, indexesTargetRow, 'row');
|
|
202
|
+
}
|
|
195
203
|
var types = (0, _tableNodeTypes.tableNodeTypes)(state.schema);
|
|
196
|
-
var direction = normalizeDirection(originMin, targetRowIndex, options);
|
|
204
|
+
var direction = (0, _normalizeDirection.normalizeDirection)(originMin, targetRowIndex, options);
|
|
197
205
|
var actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'].apply(Math, (0, _toConsumableArray2.default)(indexesTargetRow));
|
|
198
206
|
var originPositions = indexesOriginRow.map(function (index) {
|
|
199
207
|
return tableMap.positionAt(index, 0, table.node) + table.pos;
|
|
@@ -212,7 +220,7 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
|
|
|
212
220
|
var targetNode = tr.doc.nodeAt(tr.mapping.map(targetPos));
|
|
213
221
|
if (originNodes !== null && originNodes !== void 0 && originNodes.length && targetNode) {
|
|
214
222
|
var newTr = (0, _cloneTr.cloneTr)(tr);
|
|
215
|
-
var _determineTableHeader =
|
|
223
|
+
var _determineTableHeader = (0, _analyseTable.determineTableHeaderStateFromTableNode)(table.node, tableMap, types),
|
|
216
224
|
rowHeaderEnabled = _determineTableHeader.rowHeaderEnabled,
|
|
217
225
|
columnHeaderEnabled = _determineTableHeader.columnHeaderEnabled;
|
|
218
226
|
if (rowHeaderEnabled && (originMin === 0 || actualTargetIndex === 0)) {
|
|
@@ -235,6 +243,16 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
|
|
|
235
243
|
var node = _ref3.node;
|
|
236
244
|
return normalizeRowNode(node, rowHeaderEnabled && actualTargetIndex === 0 && index === 0, columnHeaderEnabled, types);
|
|
237
245
|
}));
|
|
246
|
+
|
|
247
|
+
// IMPORTANT: We need to perform the selection between the insert/delete. This is because the delete could cause
|
|
248
|
+
// the current selection to be pushed out of the table resulting in an invalid selection range outside of the table
|
|
249
|
+
if (options.selectAfterMove) {
|
|
250
|
+
var offset = direction === 'end' ? 1 : 0;
|
|
251
|
+
var selectionRange = (0, _getSelectionRangeInRow.getSelectionRangeInRow)(actualTargetIndex + offset, actualTargetIndex + offset + originNodes.length - 1)(newTr);
|
|
252
|
+
if (selectionRange) {
|
|
253
|
+
newTr.setSelection(new _cellSelection.CellSelection(selectionRange.$anchor, selectionRange.$head));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
238
256
|
originNodes.forEach(function (_ref4) {
|
|
239
257
|
var pos = _ref4.pos,
|
|
240
258
|
node = _ref4.node;
|
|
@@ -245,47 +263,6 @@ var moveRow = exports.moveRow = function moveRow(state, originRowIndex, targetRo
|
|
|
245
263
|
return tr;
|
|
246
264
|
};
|
|
247
265
|
};
|
|
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
266
|
|
|
290
267
|
/**
|
|
291
268
|
* This ensures the row node cell type correctly reflect what they should be.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.normalizeDirection = normalizeDirection;
|
|
7
|
+
function normalizeDirection(origin, target, options) {
|
|
8
|
+
var _options$direction;
|
|
9
|
+
var dir = origin < target ? 'end' : 'start';
|
|
10
|
+
var override = ((_options$direction = options === null || options === void 0 ? void 0 : options.direction) !== null && _options$direction !== void 0 ? _options$direction : 0) < 0 ? 'start' : 'end';
|
|
11
|
+
return options !== null && options !== void 0 && options.tryToFit && !!(options !== null && options !== void 0 && options.direction) ? override : dir;
|
|
12
|
+
}
|
package/dist/es2019/table-map.js
CHANGED
|
@@ -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.
|
|
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 =
|
|
6
|
-
let startIndex =
|
|
7
|
-
let endIndex =
|
|
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 =
|
|
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 =
|
|
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,12 @@
|
|
|
1
|
+
import { CellSelection } from '../cell-selection';
|
|
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 {
|
|
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,29 +135,132 @@ import { isValidReorder, moveTableColumn } from './reorder-utils';
|
|
|
131
135
|
// moveColumn(x, y, options)(state.tr)
|
|
132
136
|
// );
|
|
133
137
|
// ```
|
|
134
|
-
export const moveColumn = (originColumnIndex, targetColumnIndex, options = {
|
|
138
|
+
export const moveColumn = (state, originColumnIndex, targetColumnIndex, options = {
|
|
135
139
|
tryToFit: false,
|
|
136
|
-
direction: 0
|
|
140
|
+
direction: 0,
|
|
141
|
+
selectAfterMove: false
|
|
137
142
|
}) => tr => {
|
|
138
143
|
var _originalColumnRanges, _targetColumnRanges$i;
|
|
139
144
|
const table = findTable(tr.selection);
|
|
140
145
|
if (!table) {
|
|
141
146
|
return tr;
|
|
142
147
|
}
|
|
148
|
+
|
|
149
|
+
// normalize the origin index to an array since this supports moving both a single & multiple cols in a single action.
|
|
143
150
|
if (!Array.isArray(originColumnIndex)) {
|
|
144
151
|
originColumnIndex = [originColumnIndex];
|
|
145
152
|
}
|
|
146
|
-
const
|
|
153
|
+
const tableMap = TableMap.get(table.node);
|
|
154
|
+
const originalColumnRanges = getSelectionRangeInColumn(Math.min(...originColumnIndex), Math.max(...originColumnIndex))(tr);
|
|
147
155
|
const targetColumnRanges = getSelectionRangeInColumn(targetColumnIndex)(tr);
|
|
148
156
|
const indexesOriginColumn = (_originalColumnRanges = originalColumnRanges === null || originalColumnRanges === void 0 ? void 0 : originalColumnRanges.indexes) !== null && _originalColumnRanges !== void 0 ? _originalColumnRanges : [];
|
|
149
157
|
const indexesTargetColumn = (_targetColumnRanges$i = targetColumnRanges === null || targetColumnRanges === void 0 ? void 0 : targetColumnRanges.indexes) !== null && _targetColumnRanges$i !== void 0 ? _targetColumnRanges$i : [];
|
|
158
|
+
const min = indexesOriginColumn[0];
|
|
159
|
+
const max = indexesOriginColumn[indexesOriginColumn.length - 1];
|
|
150
160
|
if (indexesOriginColumn.includes(targetColumnIndex)) {
|
|
151
161
|
return tr;
|
|
152
162
|
}
|
|
153
163
|
if (!options.tryToFit && indexesTargetColumn.length > 1) {
|
|
154
164
|
isValidReorder(originColumnIndex[0], targetColumnIndex, indexesTargetColumn, 'column');
|
|
155
165
|
}
|
|
156
|
-
const
|
|
157
|
-
const
|
|
166
|
+
const types = tableNodeTypes(state.schema);
|
|
167
|
+
const direction = normalizeDirection(min, targetColumnIndex, options);
|
|
168
|
+
const actualTargetIndex = Math[direction === 'start' ? 'min' : 'max'](...indexesTargetColumn);
|
|
169
|
+
const {
|
|
170
|
+
rowHeaderEnabled,
|
|
171
|
+
columnHeaderEnabled
|
|
172
|
+
} = determineTableHeaderStateFromTableNode(table.node, tableMap, types);
|
|
173
|
+
const createContentNode = createContentNodeFactory(table);
|
|
174
|
+
const newTr = cloneTr(tr);
|
|
175
|
+
const origins = [];
|
|
176
|
+
for (let y = 0; y < tableMap.height; y++) {
|
|
177
|
+
origins.push([]);
|
|
178
|
+
for (let x = min; x <= max; x++) {
|
|
179
|
+
if (tableMap.isCellMergedTopLeft(y, x)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const 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
|
+
const nearHeaderCol = min === 0 ? max + 1 : actualTargetIndex;
|
|
192
|
+
const nodePos = tableMap.map[y * tableMap.width + nearHeaderCol];
|
|
193
|
+
const {
|
|
194
|
+
pos,
|
|
195
|
+
node
|
|
196
|
+
} = createContentNode(nodePos);
|
|
197
|
+
newTr.setNodeMarkup(pos, actualTargetIndex !== 0 || rowHeaderEnabled && y === 0 ? types.header_cell : types.cell, node.attrs);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
origins.forEach((row, y) => {
|
|
201
|
+
if (!row.length) {
|
|
202
|
+
// 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
|
|
203
|
+
// into the current row.
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The actual target index needs to be translated per row, this is because row/col spans can affect the amount of
|
|
208
|
+
// cells each row contains.
|
|
209
|
+
const rowTargetPosition = translateTargetPosition(y, actualTargetIndex, tableMap);
|
|
210
|
+
const node = table.node.nodeAt(rowTargetPosition);
|
|
211
|
+
const pos = table.start + rowTargetPosition;
|
|
212
|
+
const insertPos = direction === 'end' ? newTr.mapping.map(pos + node.nodeSize, 1) : newTr.mapping.map(pos, -1);
|
|
213
|
+
newTr.insert(insertPos, row.map(({
|
|
214
|
+
node
|
|
215
|
+
}, x) => normalizeCellNode(node, rowHeaderEnabled && y === 0, columnHeaderEnabled && actualTargetIndex === 0 && x === 0, types)));
|
|
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
|
+
const first = row[0];
|
|
220
|
+
const 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
|
+
});
|
|
223
|
+
if (options.selectAfterMove) {
|
|
224
|
+
const n = indexesOriginColumn.length - 1;
|
|
225
|
+
const selectionRange = getSelectionRangeInColumn(actualTargetIndex - (direction === 'end' ? n : 0), actualTargetIndex + (direction === 'start' ? n : 0))(newTr);
|
|
226
|
+
if (selectionRange) {
|
|
227
|
+
newTr.setSelection(new CellSelection(selectionRange.$anchor, selectionRange.$head));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
158
230
|
return newTr;
|
|
159
|
-
};
|
|
231
|
+
};
|
|
232
|
+
function normalizeCellNode(cellNode, rowHeaderEnabled, columnHeaderEnabled, types) {
|
|
233
|
+
const newTargetType = rowHeaderEnabled || columnHeaderEnabled ? types.header_cell : types.cell;
|
|
234
|
+
return cellNode.type !== newTargetType ? newTargetType.create(cellNode.attrs, cellNode.content, cellNode.marks) : cellNode;
|
|
235
|
+
}
|
|
236
|
+
function createContentNodeFactory(table) {
|
|
237
|
+
return nodePos => {
|
|
238
|
+
const node = table.node.nodeAt(nodePos);
|
|
239
|
+
const pos = nodePos + table.start;
|
|
240
|
+
return {
|
|
241
|
+
pos,
|
|
242
|
+
start: pos + 1,
|
|
243
|
+
node,
|
|
244
|
+
depth: table.depth + 2
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function translateTargetPosition(row, startIndex, tableMap) {
|
|
249
|
+
if (tableMap.isCellMergedTopLeft(row, startIndex)) {
|
|
250
|
+
// find the closet unmerged position to the left of the target. We scan left first because merged cells will actually
|
|
251
|
+
// reduce the amount of cells in a row.
|
|
252
|
+
for (let x = startIndex - 1; x >= 0; x--) {
|
|
253
|
+
if (!tableMap.isCellMergedTopLeft(row, x)) {
|
|
254
|
+
return tableMap.map[row * tableMap.width + x];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If no index found then we need to look to the right, this can occur when the first cell in the row is merged.
|
|
259
|
+
for (let x = startIndex + 1; x < tableMap.width; x++) {
|
|
260
|
+
if (!tableMap.isCellMergedTopLeft(row, x)) {
|
|
261
|
+
return tableMap.map[row * tableMap.width + x];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return tableMap.map[row * tableMap.width + startIndex];
|
|
266
|
+
}
|