@atlaskit/editor-plugin-table 24.2.2 → 24.2.3

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 (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/pm-plugins/utils/column-controls.js +119 -2
  3. package/dist/cjs/pm-plugins/utils/dom.js +20 -1
  4. package/dist/cjs/pm-plugins/utils/row-controls.js +43 -1
  5. package/dist/cjs/pm-plugins/utils/selection.js +61 -1
  6. package/dist/cjs/ui/DragHandle/index.js +10 -2
  7. package/dist/cjs/ui/FloatingInsertButton/getPopupOptions.js +19 -5
  8. package/dist/cjs/ui/FloatingInsertButton/index.js +37 -3
  9. package/dist/cjs/ui/TableFloatingColumnControls/ColumnControls/index.js +25 -15
  10. package/dist/cjs/ui/TableFloatingColumnControls/index.js +1 -1
  11. package/dist/cjs/ui/TableFloatingControls/RowControls/DragControls.js +28 -12
  12. package/dist/cjs/ui/common-styles.js +6 -1
  13. package/dist/cjs/ui/event-handlers.js +47 -7
  14. package/dist/es2019/pm-plugins/utils/column-controls.js +114 -2
  15. package/dist/es2019/pm-plugins/utils/dom.js +19 -0
  16. package/dist/es2019/pm-plugins/utils/row-controls.js +42 -0
  17. package/dist/es2019/pm-plugins/utils/selection.js +60 -0
  18. package/dist/es2019/ui/DragHandle/index.js +10 -2
  19. package/dist/es2019/ui/FloatingInsertButton/getPopupOptions.js +24 -5
  20. package/dist/es2019/ui/FloatingInsertButton/index.js +35 -3
  21. package/dist/es2019/ui/TableFloatingColumnControls/ColumnControls/index.js +26 -16
  22. package/dist/es2019/ui/TableFloatingColumnControls/index.js +2 -2
  23. package/dist/es2019/ui/TableFloatingControls/RowControls/DragControls.js +29 -12
  24. package/dist/es2019/ui/common-styles.js +26 -1
  25. package/dist/es2019/ui/event-handlers.js +47 -7
  26. package/dist/esm/pm-plugins/utils/column-controls.js +118 -1
  27. package/dist/esm/pm-plugins/utils/dom.js +19 -0
  28. package/dist/esm/pm-plugins/utils/row-controls.js +42 -0
  29. package/dist/esm/pm-plugins/utils/selection.js +60 -0
  30. package/dist/esm/ui/DragHandle/index.js +10 -2
  31. package/dist/esm/ui/FloatingInsertButton/getPopupOptions.js +19 -5
  32. package/dist/esm/ui/FloatingInsertButton/index.js +37 -3
  33. package/dist/esm/ui/TableFloatingColumnControls/ColumnControls/index.js +27 -17
  34. package/dist/esm/ui/TableFloatingColumnControls/index.js +2 -2
  35. package/dist/esm/ui/TableFloatingControls/RowControls/DragControls.js +30 -14
  36. package/dist/esm/ui/common-styles.js +7 -2
  37. package/dist/esm/ui/event-handlers.js +49 -9
  38. package/dist/types/pm-plugins/utils/column-controls.d.ts +35 -1
  39. package/dist/types/pm-plugins/utils/dom.d.ts +10 -0
  40. package/dist/types/pm-plugins/utils/row-controls.d.ts +16 -0
  41. package/dist/types/pm-plugins/utils/selection.d.ts +13 -0
  42. package/dist/types/ui/DragHandle/index.d.ts +1 -1
  43. package/dist/types/ui/FloatingInsertButton/getPopupOptions.d.ts +1 -1
  44. package/dist/types/ui/FloatingInsertButton/index.d.ts +1 -0
  45. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-plugin-table
2
2
 
3
+ ## 24.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`bd776b48c8623`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bd776b48c8623) -
8
+ [ux] Fix table drag-handle menu edge cases for merged cells in the first row/column behind the
9
+ `platform_editor_table_menu_updates` experiment.
10
+ - [`e850980f5a66f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e850980f5a66f) -
11
+ [EDITOR-6790] Block-controls drag-handle wrapper's background is removed and instead set
12
+ on`::before` on `.pm-table-container.pm-table-sticky` to keep masking the row insert dots when
13
+ legacy sticky header is activated while also not overlapping the column insert button.
14
+ - Updated dependencies
15
+
3
16
  ## 24.2.2
4
17
 
5
18
  ### Patch Changes
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.getColumnsWidths = exports.getColumnIndexMappedToColumnIndexInFirstRow = exports.getColumnDeleteButtonParams = exports.convertHTMLCellIndexToColumnIndex = exports.colWidthsForRow = void 0;
7
+ exports.getProportionalColumnWidths = exports.getColumnsWidthsWithMergedCells = exports.getColumnsWidths = exports.getColumnIndexMappedToColumnIndexInFirstRow = exports.getColumnIndexByMousePosition = exports.getColumnDeleteButtonParams = exports.convertHTMLCellIndexToColumnIndex = exports.colWidthsForRow = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _utils = require("@atlaskit/editor-common/utils");
10
10
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
@@ -40,6 +40,73 @@ var getColumnsWidths = exports.getColumnsWidths = function getColumnsWidths(view
40
40
  }
41
41
  return widths;
42
42
  };
43
+
44
+ /**
45
+ * Splits a merged cell's rendered width by `colwidth` ratios, falling back to an even split when
46
+ * usable ratios are unavailable.
47
+ */
48
+ var getProportionalColumnWidths = exports.getProportionalColumnWidths = function getProportionalColumnWidths(totalWidth, columnCount, ratios) {
49
+ var evenSplit = function evenSplit() {
50
+ return new Array(columnCount).fill(totalWidth / columnCount);
51
+ };
52
+ if (!ratios || ratios.length !== columnCount) {
53
+ return evenSplit();
54
+ }
55
+ var total = ratios.reduce(function (sum, ratio) {
56
+ return sum + (ratio > 0 ? ratio : 0);
57
+ }, 0);
58
+ if (total <= 0) {
59
+ return evenSplit();
60
+ }
61
+ return ratios.map(function (ratio) {
62
+ return totalWidth * ((ratio > 0 ? ratio : 0) / total);
63
+ });
64
+ };
65
+
66
+ /**
67
+ * Like `getColumnsWidths`, but fills every visual column under a first-row `colspan` so column
68
+ * controls can render one grid track per column.
69
+ */
70
+ var getColumnsWidthsWithMergedCells = exports.getColumnsWidthsWithMergedCells = function getColumnsWidthsWithMergedCells(view) {
71
+ var selection = view.state.selection;
72
+ var table = (0, _utils3.findTable)(selection);
73
+ if (!table) {
74
+ return [];
75
+ }
76
+ var map = _tableMap.TableMap.get(table.node);
77
+ var domAtPos = view.domAtPos.bind(view);
78
+ var widths = Array.from({
79
+ length: map.width
80
+ });
81
+ for (var i = 0; i < map.width; i++) {
82
+ var _node$attrs$colspan;
83
+ if (map.isCellMergedTopLeft(0, i)) {
84
+ continue;
85
+ }
86
+
87
+ // Ignored via go/ees005
88
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
89
+ var node = table.node.nodeAt(map.map[i]);
90
+ var pos = map.map[i] + table.start;
91
+ // Ignored via go/ees005
92
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
93
+ var cellRef = (0, _utils2.findDomRefAtPos)(pos, domAtPos);
94
+ var rect = cellRef.getBoundingClientRect();
95
+ var measuredWidth = (rect ? rect.width : cellRef.offsetWidth) + 1;
96
+ var colspan = (_node$attrs$colspan = node.attrs.colspan) !== null && _node$attrs$colspan !== void 0 ? _node$attrs$colspan : 1;
97
+ if (colspan <= 1) {
98
+ widths[i] = measuredWidth;
99
+ continue;
100
+ }
101
+ var colwidth = Array.isArray(node.attrs.colwidth) ? node.attrs.colwidth : undefined;
102
+ var perColumnWidths = getProportionalColumnWidths(measuredWidth, colspan, colwidth);
103
+ for (var span = 0; span < colspan && i + span < map.width; span++) {
104
+ widths[i + span] = perColumnWidths[span];
105
+ }
106
+ i += colspan - 1;
107
+ }
108
+ return widths;
109
+ };
43
110
  var getColumnDeleteButtonParams = exports.getColumnDeleteButtonParams = function getColumnDeleteButtonParams(columnsWidths, selection) {
44
111
  var rect = (0, _utils3.getSelectionRect)(selection);
45
112
  if (!rect) {
@@ -122,6 +189,9 @@ var getRelativeDomCellWidths = function getRelativeDomCellWidths(_ref) {
122
189
  });
123
190
  };
124
191
  var colWidthsForRow = exports.colWidthsForRow = function colWidthsForRow(tr) {
192
+ var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
193
+ _ref2$useColwidthRati = _ref2.useColwidthRatios,
194
+ useColwidthRatios = _ref2$useColwidthRati === void 0 ? false : _ref2$useColwidthRati;
125
195
  // get the colspans
126
196
  var rowColSpans = (0, _utils.maphElem)(tr, function (cell) {
127
197
  return Number(cell.getAttribute('colspan') || 1 /* default to span of 1 */);
@@ -149,7 +219,7 @@ var colWidthsForRow = exports.colWidthsForRow = function colWidthsForRow(tr) {
149
219
  // reverse engineer cell widths from table widths
150
220
  var domBasedCellWidths = [];
151
221
  cellInfos.map(function (cell) {
152
- domBasedCellWidths.push.apply(domBasedCellWidths, (0, _toConsumableArray2.default)(getRelativeDomCellWidths(cell)));
222
+ domBasedCellWidths.push.apply(domBasedCellWidths, (0, _toConsumableArray2.default)(useColwidthRatios ? getRelativeDomCellWidths(cell) : new Array(cell.colspan).fill(cell.width / cell.colspan)));
153
223
  });
154
224
  if (cellInfos.reduce(function (acc, cell) {
155
225
  return acc + cell.width;
@@ -173,6 +243,53 @@ var colWidthsForRow = exports.colWidthsForRow = function colWidthsForRow(tr) {
173
243
  return "".concat(pct, "%");
174
244
  }).join(' ');
175
245
  };
246
+
247
+ /**
248
+ * Returns the visual column index that the mouse pointer is over within a column-spanned
249
+ * cell, by walking the per-column boundaries of the spanned cell and finding the column whose
250
+ * horizontal range contains `mouseEvent.clientX`.
251
+ *
252
+ * `<td>.cellIndex` for a cell with `colspan > 1` only resolves to the first column the cell
253
+ * occupies, so hovering anywhere inside a colspanned first-row cell pins the column drag handle
254
+ * to that first column. This resolver disambiguates which visual column the pointer is actually
255
+ * over so the handle/menu can target a single column.
256
+ *
257
+ * The search is restricted to columns in `[startIndex, endIndex)` (the cell's own span). The
258
+ * spanned cell's bounding rect is read once and split into per-column boundaries using the cell's
259
+ * `data-colwidth` ratios (falling back to an even split for unresized columns) — this keeps the
260
+ * work bounded by the colspan size and avoids measuring the whole table.
261
+ *
262
+ * Returns `undefined` when the mouse is outside the spanned range (so callers can fall back to
263
+ * the converted HTML column index).
264
+ */
265
+ var getColumnIndexByMousePosition = exports.getColumnIndexByMousePosition = function getColumnIndexByMousePosition(cellElement, mouseEvent, columnIndexRange) {
266
+ var _cellElement$dataset$;
267
+ var startIndex = columnIndexRange.startIndex,
268
+ endIndex = columnIndexRange.endIndex;
269
+ var columnCount = endIndex - startIndex;
270
+ if (columnCount <= 0) {
271
+ return undefined;
272
+ }
273
+ var cellRect = cellElement.getBoundingClientRect();
274
+ if (mouseEvent.clientX < cellRect.left || mouseEvent.clientX >= cellRect.right) {
275
+ return undefined;
276
+ }
277
+
278
+ // Read DOM ratios here to avoid resolving the ProseMirror node on this mouse-move path.
279
+ var ratios = (_cellElement$dataset$ = cellElement.dataset.colwidth) === null || _cellElement$dataset$ === void 0 ? void 0 : _cellElement$dataset$.split(',').map(Number).filter(function (value) {
280
+ return !Number.isNaN(value);
281
+ });
282
+ var perColumnWidths = getProportionalColumnWidths(cellRect.width, columnCount, ratios);
283
+ var offsetFromLeft = mouseEvent.clientX - cellRect.left;
284
+ var cumulativeWidth = 0;
285
+ for (var column = 0; column < columnCount; column++) {
286
+ cumulativeWidth += perColumnWidths[column];
287
+ if (offsetFromLeft < cumulativeWidth) {
288
+ return startIndex + column;
289
+ }
290
+ }
291
+ return endIndex - 1;
292
+ };
176
293
  var convertHTMLCellIndexToColumnIndex = exports.convertHTMLCellIndexToColumnIndex = function convertHTMLCellIndexToColumnIndex(htmlColIndex, htmlRowIndex, tableMap) {
177
294
  // Same numbers (positions) in tableMap.map array mean that there are merged cells.
178
295
  // Cells can be merged across columns. So we need to check if the cell on the left and current cell have the same value.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isTableControlsButton = exports.isTableContainerOrWrapper = exports.isRowControlsButton = exports.isResizeHandleDecoration = exports.isInsertRowButton = exports.isDragRowFloatingInsertDot = exports.isDragCornerButton = exports.isDragColumnFloatingInsertDot = exports.isCornerButton = exports.isColumnControlsDecorations = exports.isCell = exports.hasResizeHandler = exports.getTree = exports.getTop = exports.getMousePositionVerticalRelativeByElement = exports.getMousePositionHorizontalRelativeByElement = exports.getColumnOrRowIndex = exports.findNearestCellIndexToPoint = exports.areAllRectsZero = void 0;
6
+ exports.isTableControlsButton = exports.isTableContainerOrWrapper = exports.isRowControlsButton = exports.isResizeHandleDecoration = exports.isInsertRowButton = exports.isDragRowFloatingInsertDot = exports.isDragCornerButton = exports.isDragColumnFloatingInsertDot = exports.isCornerButton = exports.isColumnControlsDecorations = exports.isCell = exports.hasResizeHandler = exports.getTree = exports.getTop = exports.getMousePositionVerticalRelativeByElement = exports.getMousePositionHorizontalRelativeByElement = exports.getIndexAttributeSourceElement = exports.getColumnOrRowIndex = exports.findNearestCellIndexToPoint = exports.areAllRectsZero = void 0;
7
7
  var _utils = require("@atlaskit/editor-common/utils");
8
8
  var _types = require("../../types");
9
9
  var isCell = exports.isCell = function isCell(node) {
@@ -18,6 +18,25 @@ var isInsertRowButton = exports.isInsertRowButton = function isInsertRowButton(n
18
18
  var getColumnOrRowIndex = exports.getColumnOrRowIndex = function getColumnOrRowIndex(target) {
19
19
  return [parseInt(target.getAttribute('data-start-index') || '-1', 10), parseInt(target.getAttribute('data-end-index') || '-1', 10)];
20
20
  };
21
+
22
+ /**
23
+ * Returns the element that carries the `data-start-index` / `data-end-index` attributes for a
24
+ * column/row control.
25
+ *
26
+ * The floating insert dot is a child of a wrapper that holds the index attributes, so when the
27
+ * pointer is directly over the dot the attributes are not on the event target. This walks up to
28
+ * the nearest ancestor that has `data-start-index`, falling back to the original element so the
29
+ * behaviour is unchanged when the target already carries the attributes.
30
+ */
31
+ var getIndexAttributeSourceElement = exports.getIndexAttributeSourceElement = function getIndexAttributeSourceElement(target) {
32
+ if (target.hasAttribute('data-start-index')) {
33
+ return target;
34
+ }
35
+ // Ignored via go/ees005
36
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
37
+ var closestWithIndex = target.closest('[data-start-index]');
38
+ return closestWithIndex !== null && closestWithIndex !== void 0 ? closestWithIndex : target;
39
+ };
21
40
  var isColumnControlsDecorations = exports.isColumnControlsDecorations = function isColumnControlsDecorations(node) {
22
41
  return (0, _utils.containsClassName)(node, _types.TableCssClassName.COLUMN_CONTROLS_DECORATIONS);
23
42
  };
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.getRowsParams = exports.getRowHeights = exports.getRowDeleteButtonParams = exports.getRowClassNames = exports.copyPreviousRow = void 0;
7
+ exports.getRowsParams = exports.getRowIndexByMousePosition = exports.getRowHeights = exports.getRowDeleteButtonParams = exports.getRowClassNames = exports.copyPreviousRow = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _utils = require("@atlaskit/editor-common/utils");
10
10
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
@@ -85,6 +85,48 @@ var getRowsParams = exports.getRowsParams = function getRowsParams(rowsHeights)
85
85
  }
86
86
  return rows;
87
87
  };
88
+
89
+ /**
90
+ * Returns the visual row index that the mouse pointer is over, by walking the row heights
91
+ * inside `tbody` and finding the row whose vertical range contains `mouseEvent.clientY`.
92
+ *
93
+ * When `rowIndexRange` is provided, the search is restricted to rows in that range (the
94
+ * `endIndex` is exclusive). This is the hot path used on `mousemove` when hovering over a
95
+ * row-spanned cell — restricting the range to `[startIndex, endIndex)` keeps the number
96
+ * of forced layout reads bounded by the row-span size, not the table size.
97
+ *
98
+ * Returns `undefined` when the mouse is above the search range or below it (so callers can
99
+ * fall back to the HTML row index).
100
+ */
101
+ var getRowIndexByMousePosition = exports.getRowIndexByMousePosition = function getRowIndexByMousePosition(tableRef, mouseEvent, rowIndexRange) {
102
+ var _rowIndexRange$startI, _rowIndexRange$endInd;
103
+ var tableBody = tableRef.querySelector('tbody');
104
+ if (!tableBody) {
105
+ return undefined;
106
+ }
107
+ var rows = tableBody.children;
108
+ var startIndex = (_rowIndexRange$startI = rowIndexRange === null || rowIndexRange === void 0 ? void 0 : rowIndexRange.startIndex) !== null && _rowIndexRange$startI !== void 0 ? _rowIndexRange$startI : 0;
109
+ var endIndex = Math.min((_rowIndexRange$endInd = rowIndexRange === null || rowIndexRange === void 0 ? void 0 : rowIndexRange.endIndex) !== null && _rowIndexRange$endInd !== void 0 ? _rowIndexRange$endInd : rows.length, rows.length);
110
+ if (startIndex >= endIndex) {
111
+ return undefined;
112
+ }
113
+ var firstRowRect = rows[startIndex].getBoundingClientRect();
114
+ if (mouseEvent.clientY < firstRowRect.top) {
115
+ return undefined;
116
+ }
117
+ var rowBottom = firstRowRect.bottom;
118
+ if (mouseEvent.clientY < rowBottom) {
119
+ return startIndex;
120
+ }
121
+ for (var rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) {
122
+ var rowRect = rows[rowIndex].getBoundingClientRect();
123
+ rowBottom = rowRect.bottom;
124
+ if (mouseEvent.clientY < rowBottom) {
125
+ return rowIndex;
126
+ }
127
+ }
128
+ return undefined;
129
+ };
88
130
  var getRowClassNames = exports.getRowClassNames = function getRowClassNames(index, selection) {
89
131
  var hoveredRows = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
90
132
  var isInDanger = arguments.length > 3 ? arguments[3] : undefined;
@@ -3,7 +3,9 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getSelectedRowIndexes = exports.getSelectedColumnIndexes = void 0;
6
+ exports.isRowSelectionWithMergedFirstColumn = exports.isColumnSelectionWithMergedFirstRow = exports.getSelectedRowIndexes = exports.getSelectedColumnIndexes = void 0;
7
+ var _tableMap = require("@atlaskit/editor-tables/table-map");
8
+ var _utils = require("@atlaskit/editor-tables/utils");
7
9
  var getSelectedColumnIndexes = exports.getSelectedColumnIndexes = function getSelectedColumnIndexes(selectionRect) {
8
10
  var columnIndexes = [];
9
11
  for (var i = selectionRect.left; i < selectionRect.right; i++) {
@@ -17,4 +19,62 @@ var getSelectedRowIndexes = exports.getSelectedRowIndexes = function getSelected
17
19
  rowIndexes.push(i);
18
20
  }
19
21
  return rowIndexes;
22
+ };
23
+
24
+ /**
25
+ * Treats a column as fully selected when its first-row area is covered by a horizontal merge and
26
+ * the explicit `CellSelection` starts below that merged cell. `CellSelection.isColSelection()`
27
+ * returns `false` in this case because the selection does not include the merged first-row cell.
28
+ */
29
+ var isColumnSelectionWithMergedFirstRow = exports.isColumnSelectionWithMergedFirstRow = function isColumnSelectionWithMergedFirstRow(selection) {
30
+ var rect = (0, _utils.getSelectionRect)(selection);
31
+ if (!rect) {
32
+ return false;
33
+ }
34
+ var table = (0, _utils.findTableClosestToPos)(selection.$anchorCell);
35
+ if (!table) {
36
+ return false;
37
+ }
38
+ var map = _tableMap.TableMap.get(table.node);
39
+ if (rect.bottom !== map.height || rect.top === 0) {
40
+ return false;
41
+ }
42
+ for (var col = rect.left; col < rect.right; col++) {
43
+ var topCellRect = map.findCell(map.map[col]);
44
+ var isHorizontallyMerged = topCellRect.right - topCellRect.left > 1;
45
+ var bridgesGapAboveSelection = topCellRect.bottom >= rect.top;
46
+ if (!isHorizontallyMerged || !bridgesGapAboveSelection) {
47
+ return false;
48
+ }
49
+ }
50
+ return true;
51
+ };
52
+
53
+ /**
54
+ * Treats a row as fully selected when its first-column area is covered by a vertical merge and the
55
+ * explicit `CellSelection` starts to the right of that merged cell. `CellSelection.isRowSelection()`
56
+ * returns `false` in this case because the selection does not include the merged first-column cell.
57
+ */
58
+ var isRowSelectionWithMergedFirstColumn = exports.isRowSelectionWithMergedFirstColumn = function isRowSelectionWithMergedFirstColumn(selection) {
59
+ var rect = (0, _utils.getSelectionRect)(selection);
60
+ if (!rect) {
61
+ return false;
62
+ }
63
+ var table = (0, _utils.findTableClosestToPos)(selection.$anchorCell);
64
+ if (!table) {
65
+ return false;
66
+ }
67
+ var map = _tableMap.TableMap.get(table.node);
68
+ if (rect.right !== map.width || rect.left === 0) {
69
+ return false;
70
+ }
71
+ for (var row = rect.top; row < rect.bottom; row++) {
72
+ var leftCellRect = map.findCell(map.map[row * map.width]);
73
+ var isVerticallyMerged = leftCellRect.bottom - leftCellRect.top > 1;
74
+ var bridgesGapLeftOfSelection = leftCellRect.right >= rect.left;
75
+ if (!isVerticallyMerged || !bridgesGapLeftOfSelection) {
76
+ return false;
77
+ }
78
+ }
79
+ return true;
20
80
  };
@@ -224,12 +224,20 @@ var DragHandleComponent = function DragHandleComponent(_ref) {
224
224
  // return focus to editor so copying table selections whilst still works, i cannot call e.preventDefault in a mousemove event as this stops dragstart events from firing
225
225
  // -> this is bad for a11y but is the current standard new copy/paste keyboard shortcuts should be introduced instead
226
226
  editorView.focus();
227
- toggleDragMenu && toggleDragMenu('mouse', e);
227
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
228
+ toggleDragMenu && toggleDragMenu('mouse', e, indexes[0]);
229
+ } else {
230
+ toggleDragMenu && toggleDragMenu('mouse', e);
231
+ }
228
232
  },
229
233
  onClick: onClick,
230
234
  onKeyDown: function onKeyDown(e) {
231
235
  if (e.key === 'Enter' || e.key === ' ') {
232
- toggleDragMenu && toggleDragMenu('keyboard');
236
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
237
+ toggleDragMenu && toggleDragMenu('keyboard', undefined, indexes[0]);
238
+ } else {
239
+ toggleDragMenu && toggleDragMenu('keyboard');
240
+ }
233
241
  }
234
242
  }
235
243
  }, appearance !== 'placeholder' ?
@@ -38,6 +38,7 @@ function getRowOptions(index) {
38
38
  });
39
39
  }
40
40
  function getColumnOptions(index, tableContainer, hasNumberedColumns) {
41
+ var verticalOffsetCorrection = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
41
42
  var options = {
42
43
  alignX: 'end',
43
44
  alignY: 'top',
@@ -48,20 +49,32 @@ function getColumnOptions(index, tableContainer, hasNumberedColumns) {
48
49
  // we should always set the InsertButton on the start,
49
50
  // considering the offset from the first column
50
51
  onPositionCalculated: function onPositionCalculated(position) {
52
+ // Move the popup upward whether the offset parent provides `top` or `bottom`.
53
+ var verticalCorrection;
54
+ if (verticalOffsetCorrection) {
55
+ if (position.top !== undefined) {
56
+ verticalCorrection = {
57
+ top: position.top - verticalOffsetCorrection
58
+ };
59
+ } else if (position.bottom !== undefined) {
60
+ verticalCorrection = {
61
+ bottom: position.bottom + verticalOffsetCorrection
62
+ };
63
+ }
64
+ }
51
65
  var left = position.left;
52
66
  if (!left) {
53
- // If not left, lest skip expensive next calculations.
54
- return position;
67
+ return _objectSpread(_objectSpread({}, position), verticalCorrection);
55
68
  }
56
69
  if (index === 0) {
57
- return _objectSpread(_objectSpread({}, position), {}, {
70
+ return _objectSpread(_objectSpread(_objectSpread({}, position), verticalCorrection), {}, {
58
71
  left: hasNumberedColumns ? HORIZONTAL_ALIGN_NUMBERED_COLUMN_BUTTON : HORIZONTAL_ALIGN_COLUMN_BUTTON
59
72
  });
60
73
  }
61
74
 
62
75
  // Check if current position is greater than the available container width
63
76
  var rect = tableContainer ? tableContainer.getBoundingClientRect() : null;
64
- return _objectSpread(_objectSpread({}, position), {}, {
77
+ return _objectSpread(_objectSpread(_objectSpread({}, position), verticalCorrection), {}, {
65
78
  left: rect && left > rect.width ? rect.width : left
66
79
  });
67
80
  }
@@ -78,9 +91,10 @@ function getColumnOptions(index, tableContainer, hasNumberedColumns) {
78
91
  return options;
79
92
  }
80
93
  function getPopupOptions(direction, index, hasNumberedColumns, tableContainer) {
94
+ var verticalOffsetCorrection = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
81
95
  switch (direction) {
82
96
  case 'column':
83
- return getColumnOptions(index, tableContainer, hasNumberedColumns);
97
+ return getColumnOptions(index, tableContainer, hasNumberedColumns, verticalOffsetCorrection);
84
98
  case 'row':
85
99
  return getRowOptions(index);
86
100
  default:
@@ -25,6 +25,7 @@ var _utils3 = require("@atlaskit/editor-tables/utils");
25
25
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
26
26
  var _commandsWithAnalytics = require("../../pm-plugins/commands/commands-with-analytics");
27
27
  var _nodes = require("../../pm-plugins/utils/nodes");
28
+ var _selection = require("../../pm-plugins/utils/selection");
28
29
  var _types = require("../../types");
29
30
  var _getPopupOptions = _interopRequireDefault(require("./getPopupOptions"));
30
31
  var _InsertButton = require("./InsertButton");
@@ -80,8 +81,14 @@ var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function
80
81
  return null;
81
82
  }
82
83
  var tr = editorView.state.tr;
83
- if (tr.selection instanceof _cellSelection.CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) {
84
- return null;
84
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
85
+ if (tr.selection instanceof _cellSelection.CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection() || (0, _selection.isColumnSelectionWithMergedFirstRow)(tr.selection) || (0, _selection.isRowSelectionWithMergedFirstColumn)(tr.selection))) {
86
+ return null;
87
+ }
88
+ } else {
89
+ if (tr.selection instanceof _cellSelection.CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) {
90
+ return null;
91
+ }
85
92
  }
86
93
  var tablePos = (0, _utils3.findTable)(tr.selection);
87
94
  if (!tablePos) {
@@ -135,6 +142,15 @@ var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function
135
142
  var index = type === 'column' ? insertColumnButtonIndex : insertRowButtonIndex;
136
143
  var hasNumberedColumns = (0, _nodes.checkIfNumberColumnEnabled)(editorView.state.selection);
137
144
 
145
+ // If row 0 has a colspan, anchor to a lower row for the real column boundary (X),
146
+ // then move back up to the table top (Y):
147
+ // row 0: [ colspan=2 ]
148
+ // row 1: [ col 1 ][ col 2 ] ← anchor here for X
149
+ // ↑ button should render at row 0/table top
150
+ var verticalOffsetCorrection = 0;
151
+ if (type === 'column' && (0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
152
+ verticalOffsetCorrection = Math.max(0, targetCellRef.getBoundingClientRect().top - tableRef.getBoundingClientRect().top);
153
+ }
138
154
  // Fixed the 'add column button' not visible issue when sticky header is enabled
139
155
  // By setting the Popup z-index higher than the sticky header z-index ( common-styles.ts tr.sticky)
140
156
  // Only when inserting a column, otherwise set to undefined
@@ -152,7 +168,7 @@ var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function
152
168
  allowOutOfBounds: true
153
169
  // Ignored via go/ees005
154
170
  // eslint-disable-next-line react/jsx-props-no-spreading
155
- }, (0, _getPopupOptions.default)(type, index, hasNumberedColumns, tableContainerWrapper), {
171
+ }, (0, _getPopupOptions.default)(type, index, hasNumberedColumns, tableContainerWrapper, verticalOffsetCorrection), {
156
172
  zIndex: zIndex
157
173
  }), /*#__PURE__*/_react.default.createElement(_InsertButton.DragAndDropInsertButton, {
158
174
  type: type,
@@ -162,6 +178,20 @@ var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function
162
178
  isChromelessEditor: isChromelessEditor
163
179
  }));
164
180
  }
181
+
182
+ // Finds a row where `columnIndex` has real left/right cell boundaries.
183
+ }, {
184
+ key: "findRowWithUnmergedColumn",
185
+ value: function findRowWithUnmergedColumn(tableMap, columnIndex) {
186
+ for (var row = 0; row < tableMap.height; row++) {
187
+ var pos = tableMap.map[row * tableMap.width + columnIndex];
188
+ var rect = tableMap.findCell(pos);
189
+ if (rect.left === columnIndex && rect.right === columnIndex + 1) {
190
+ return row;
191
+ }
192
+ }
193
+ return null;
194
+ }
165
195
  }, {
166
196
  key: "getCellPosition",
167
197
  value: function getCellPosition(type, tableNode) {
@@ -179,6 +209,10 @@ var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function
179
209
  if (columnIndex > tableMap.width - 1) {
180
210
  return null;
181
211
  }
212
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
213
+ var rowWithRealColumn = this.findRowWithUnmergedColumn(tableMap, columnIndex);
214
+ return rowWithRealColumn === null ? null : tableMap.positionAt(rowWithRealColumn, columnIndex, tableNode);
215
+ }
182
216
  return tableMap.positionAt(0, columnIndex, tableNode);
183
217
  } else {
184
218
  // This condition is to make typescript happy.
@@ -24,12 +24,26 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
24
24
  /* eslint-disable @atlaskit/design-system/prefer-primitives */
25
25
 
26
26
  var getSelectedColumns = function getSelectedColumns(selection) {
27
- if (selection instanceof _editorTables.CellSelection && selection.isColSelection()) {
27
+ if (!(selection instanceof _editorTables.CellSelection)) {
28
+ return [];
29
+ }
30
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
31
+ // New behaviour: also treat a column selection that sits below a merged first-row cell as a
32
+ // full column selection.
33
+ if (!selection.isColSelection() && !(0, _selection.isColumnSelectionWithMergedFirstRow)(selection)) {
34
+ return [];
35
+ }
28
36
  var rect = (0, _utils.getSelectionRect)(selection);
29
- if (!rect) {
37
+ return rect ? (0, _selection.getSelectedColumnIndexes)(rect) : [];
38
+ }
39
+
40
+ // Old behaviour: only standard column selections are recognised.
41
+ if (selection.isColSelection()) {
42
+ var _rect = (0, _utils.getSelectionRect)(selection);
43
+ if (!_rect) {
30
44
  return [];
31
45
  }
32
- return (0, _selection.getSelectedColumnIndexes)(rect);
46
+ return (0, _selection.getSelectedColumnIndexes)(_rect);
33
47
  }
34
48
  return [];
35
49
  };
@@ -126,24 +140,20 @@ var ColumnControls = exports.ColumnControls = function ColumnControls(_ref) {
126
140
  (0, _commands.clearHoverSelection)()(state, dispatch);
127
141
  }
128
142
  }, [editorView, tableActive]);
129
- var toggleDragMenuHandler = (0, _react.useCallback)(function (trigger, event) {
143
+ var toggleDragMenuHandler = (0, _react.useCallback)(function (trigger, event, handleIndex) {
130
144
  var _api$analytics2;
131
- var state = editorView.state,
132
- dispatch = editorView.dispatch;
133
145
  if (event !== null && event !== void 0 && event.shiftKey) {
134
- // Shift-click extends the selection rather than toggling the menu, but the
135
- // open drag menu would otherwise stay anchored to a stale column. Close it here
136
- // for the updated menu (legacy menu closes via outside-click on its dropdown).
137
- if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
138
- api === null || api === void 0 || api.core.actions.execute((0, _commands.closeActiveTableMenu)(api));
139
- }
140
146
  return;
141
147
  }
148
+ var state = editorView.state,
149
+ dispatch = editorView.dispatch;
142
150
  if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
143
- if (colIndex !== undefined && api) {
151
+ // Use the clicked handle index because `hoveredCell` can point at a merged cell's first column.
152
+ var targetColIndex = handleIndex !== null && handleIndex !== void 0 ? handleIndex : colIndex;
153
+ if (targetColIndex !== undefined && api) {
144
154
  var _getTablePluginState = (0, _pluginFactory.getPluginState)(state),
145
155
  currentActiveTableMenu = _getTablePluginState.activeTableMenu;
146
- var isSameActiveMenu = (currentActiveTableMenu === null || currentActiveTableMenu === void 0 ? void 0 : currentActiveTableMenu.type) === 'column' && currentActiveTableMenu.index === colIndex;
156
+ var isSameActiveMenu = (currentActiveTableMenu === null || currentActiveTableMenu === void 0 ? void 0 : currentActiveTableMenu.type) === 'column' && currentActiveTableMenu.index === targetColIndex;
147
157
  api.core.actions.execute(function (_ref2) {
148
158
  var tr = _ref2.tr;
149
159
  if (!isSameActiveMenu) {
@@ -161,7 +171,7 @@ var ColumnControls = exports.ColumnControls = function ColumnControls(_ref) {
161
171
  }
162
172
  (0, _commands.toggleActiveTableMenu)({
163
173
  type: 'column',
164
- index: colIndex,
174
+ index: targetColIndex,
165
175
  openedBy: trigger
166
176
  }, currentActiveTableMenu, api)({
167
177
  tr: tr
@@ -73,7 +73,7 @@ var TableFloatingColumnControls = function TableFloatingColumnControls(_ref) {
73
73
  if (!tableRef || !tableActive || isResizing) {
74
74
  return null;
75
75
  }
76
- var colWidths = (0, _columnControls.getColumnsWidths)(editorView);
76
+ var colWidths = (0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true) ? (0, _columnControls.getColumnsWidthsWithMergedCells)(editorView) : (0, _columnControls.getColumnsWidths)(editorView);
77
77
  if (stickyTop) {
78
78
  var _containerRef$current;
79
79
  var columnControlTopOffsetFromParent = '-12px';
@@ -27,12 +27,26 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
27
27
  /* eslint-disable @atlaskit/design-system/prefer-primitives */
28
28
 
29
29
  var getSelectedRows = function getSelectedRows(selection) {
30
- if (selection instanceof _editorTables.CellSelection && selection.isRowSelection()) {
30
+ if (!(selection instanceof _editorTables.CellSelection)) {
31
+ return [];
32
+ }
33
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
34
+ // New behaviour: also treat a row selection that sits to the right of a merged first-column
35
+ // cell as a full row selection.
36
+ if (!selection.isRowSelection() && !(0, _selection.isRowSelectionWithMergedFirstColumn)(selection)) {
37
+ return [];
38
+ }
31
39
  var rect = (0, _utils.getSelectionRect)(selection);
32
- if (!rect) {
40
+ return rect ? (0, _selection.getSelectedRowIndexes)(rect) : [];
41
+ }
42
+
43
+ // Old behaviour: only standard row selections are recognised.
44
+ if (selection.isRowSelection()) {
45
+ var _rect = (0, _utils.getSelectionRect)(selection);
46
+ if (!_rect) {
33
47
  return [];
34
48
  }
35
- return (0, _selection.getSelectedRowIndexes)(rect);
49
+ return (0, _selection.getSelectedRowIndexes)(_rect);
36
50
  }
37
51
  return [];
38
52
  };
@@ -89,18 +103,14 @@ var DragControls = exports.DragControls = function DragControls(_ref) {
89
103
  }
90
104
  });
91
105
  }, [editorView]);
92
- var toggleDragMenuHandler = (0, _react.useCallback)(function (trigger, event) {
106
+ var toggleDragMenuHandler = (0, _react.useCallback)(function (trigger, event, handleIndex) {
93
107
  var _api$analytics2;
94
108
  if (event !== null && event !== void 0 && event.shiftKey) {
95
- // Shift-click extends the selection rather than toggling the menu, but the
96
- // open drag menu would otherwise stay anchored to a stale row. Close it here
97
- // for the updated menu (legacy menu closes via outside-click on its dropdown).
98
- if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
99
- api === null || api === void 0 || api.core.actions.execute((0, _commands.closeActiveTableMenu)(api));
100
- }
101
109
  return;
102
110
  }
103
- var rowIndex = hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex;
111
+
112
+ // Use the clicked handle index because `hoveredCell` can point at a merged cell's first row.
113
+ var rowIndex = handleIndex !== null && handleIndex !== void 0 ? handleIndex : hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex;
104
114
  if ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
105
115
  if (rowIndex !== undefined && api) {
106
116
  var _getTablePluginState2 = (0, _pluginFactory.getPluginState)(editorView.state),
@@ -239,10 +249,16 @@ var DragControls = exports.DragControls = function DragControls(_ref) {
239
249
  if (!tableActive) {
240
250
  return null;
241
251
  }
252
+ var selectedAppearance = isRowSelected && isEntireTableSelected ? isInDanger ? 'danger' : 'selected' : 'placeholder';
242
253
 
243
254
  // placeholder / selected need to always render at least one handle
244
255
  // so it can be focused via keyboard shortcuts
245
- handles.push(generateHandleByType('selected', isRowSelected && isEntireTableSelected ? isInDanger ? 'danger' : 'selected' : 'placeholder', "".concat(selectedRowIndexes[0] + 1, " / span ").concat(selectedRowIndexes.length), selectedRowIndexes));
256
+ var selectedGridRow = (0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true) ?
257
+ // New behaviour: always position the placeholder in the first row to avoid an invalid
258
+ // `NaN / span 0` grid placement (which makes the handle disappear) when no rows are selected.
259
+ selectedAppearance === 'placeholder' ? '1 / span 1' : "".concat(selectedRowIndexes[0] + 1, " / span ").concat(selectedRowIndexes.length) : // Old behaviour.
260
+ "".concat(selectedRowIndexes[0] + 1, " / span ").concat(selectedRowIndexes.length);
261
+ handles.push(generateHandleByType('selected', selectedAppearance, selectedGridRow, selectedRowIndexes));
246
262
  if (hoveredCell && isTableHovered && rowIndex !== undefined && !selectedRowIndexes.includes(rowIndex) && rowIndex < rowHeights.length) {
247
263
  handles.push(generateHandleByType('hover', 'default', "".concat(rowIndex + 1, " / span 1"), rowIndexes));
248
264
  }