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