@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
@@ -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 const getSelectedColumnIndexes = selectionRect => {
2
4
  const columnIndexes = [];
3
5
  for (let i = selectionRect.left; i < selectionRect.right; i++) {
@@ -11,4 +13,62 @@ export const 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 const isColumnSelectionWithMergedFirstRow = selection => {
24
+ const rect = getSelectionRect(selection);
25
+ if (!rect) {
26
+ return false;
27
+ }
28
+ const table = findTableClosestToPos(selection.$anchorCell);
29
+ if (!table) {
30
+ return false;
31
+ }
32
+ const map = TableMap.get(table.node);
33
+ if (rect.bottom !== map.height || rect.top === 0) {
34
+ return false;
35
+ }
36
+ for (let col = rect.left; col < rect.right; col++) {
37
+ const topCellRect = map.findCell(map.map[col]);
38
+ const isHorizontallyMerged = topCellRect.right - topCellRect.left > 1;
39
+ const 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 const isRowSelectionWithMergedFirstColumn = selection => {
53
+ const rect = getSelectionRect(selection);
54
+ if (!rect) {
55
+ return false;
56
+ }
57
+ const table = findTableClosestToPos(selection.$anchorCell);
58
+ if (!table) {
59
+ return false;
60
+ }
61
+ const map = TableMap.get(table.node);
62
+ if (rect.right !== map.width || rect.left === 0) {
63
+ return false;
64
+ }
65
+ for (let row = rect.top; row < rect.bottom; row++) {
66
+ const leftCellRect = map.findCell(map.map[row * map.width]);
67
+ const isVerticallyMerged = leftCellRect.bottom - leftCellRect.top > 1;
68
+ const bridgesGapLeftOfSelection = leftCellRect.right >= rect.left;
69
+ if (!isVerticallyMerged || !bridgesGapLeftOfSelection) {
70
+ return false;
71
+ }
72
+ }
73
+ return true;
14
74
  };
@@ -216,12 +216,20 @@ const DragHandleComponent = ({
216
216
  // 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
217
217
  // -> this is bad for a11y but is the current standard new copy/paste keyboard shortcuts should be introduced instead
218
218
  editorView.focus();
219
- toggleDragMenu && toggleDragMenu('mouse', e);
219
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
220
+ toggleDragMenu && toggleDragMenu('mouse', e, indexes[0]);
221
+ } else {
222
+ toggleDragMenu && toggleDragMenu('mouse', e);
223
+ }
220
224
  },
221
225
  onClick: onClick,
222
226
  onKeyDown: e => {
223
227
  if (e.key === 'Enter' || e.key === ' ') {
224
- toggleDragMenu && toggleDragMenu('keyboard');
228
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
229
+ toggleDragMenu && toggleDragMenu('keyboard', undefined, indexes[0]);
230
+ } else {
231
+ toggleDragMenu && toggleDragMenu('keyboard');
232
+ }
225
233
  }
226
234
  }
227
235
  }, appearance !== 'placeholder' ?
@@ -30,7 +30,9 @@ function getRowOptions(index) {
30
30
  }
31
31
  };
32
32
  }
33
- function getColumnOptions(index, tableContainer, hasNumberedColumns) {
33
+ function getColumnOptions(index, tableContainer, hasNumberedColumns,
34
+ // Distance between the target cell top and table top, used when anchoring to a lower-row cell.
35
+ verticalOffsetCorrection = 0) {
34
36
  const options = {
35
37
  alignX: 'end',
36
38
  alignY: 'top',
@@ -41,16 +43,32 @@ function getColumnOptions(index, tableContainer, hasNumberedColumns) {
41
43
  // we should always set the InsertButton on the start,
42
44
  // considering the offset from the first column
43
45
  onPositionCalculated(position) {
46
+ // Move the popup upward whether the offset parent provides `top` or `bottom`.
47
+ let verticalCorrection;
48
+ if (verticalOffsetCorrection) {
49
+ if (position.top !== undefined) {
50
+ verticalCorrection = {
51
+ top: position.top - verticalOffsetCorrection
52
+ };
53
+ } else if (position.bottom !== undefined) {
54
+ verticalCorrection = {
55
+ bottom: position.bottom + verticalOffsetCorrection
56
+ };
57
+ }
58
+ }
44
59
  const {
45
60
  left
46
61
  } = position;
47
62
  if (!left) {
48
- // If not left, lest skip expensive next calculations.
49
- return position;
63
+ return {
64
+ ...position,
65
+ ...verticalCorrection
66
+ };
50
67
  }
51
68
  if (index === 0) {
52
69
  return {
53
70
  ...position,
71
+ ...verticalCorrection,
54
72
  left: hasNumberedColumns ? HORIZONTAL_ALIGN_NUMBERED_COLUMN_BUTTON : HORIZONTAL_ALIGN_COLUMN_BUTTON
55
73
  };
56
74
  }
@@ -59,6 +77,7 @@ function getColumnOptions(index, tableContainer, hasNumberedColumns) {
59
77
  const rect = tableContainer ? tableContainer.getBoundingClientRect() : null;
60
78
  return {
61
79
  ...position,
80
+ ...verticalCorrection,
62
81
  left: rect && left > rect.width ? rect.width : left
63
82
  };
64
83
  }
@@ -75,10 +94,10 @@ function getColumnOptions(index, tableContainer, hasNumberedColumns) {
75
94
  }
76
95
  return options;
77
96
  }
78
- function getPopupOptions(direction, index, hasNumberedColumns, tableContainer) {
97
+ function getPopupOptions(direction, index, hasNumberedColumns, tableContainer, verticalOffsetCorrection = 0) {
79
98
  switch (direction) {
80
99
  case 'column':
81
- return getColumnOptions(index, tableContainer, hasNumberedColumns);
100
+ return getColumnOptions(index, tableContainer, hasNumberedColumns, verticalOffsetCorrection);
82
101
  case 'row':
83
102
  return getRowOptions(index);
84
103
  default:
@@ -13,6 +13,7 @@ import { findTable } from '@atlaskit/editor-tables/utils';
13
13
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
14
  import { insertColumnWithAnalytics, insertRowWithAnalytics } from '../../pm-plugins/commands/commands-with-analytics';
15
15
  import { checkIfNumberColumnEnabled } from '../../pm-plugins/utils/nodes';
16
+ import { isColumnSelectionWithMergedFirstRow, isRowSelectionWithMergedFirstColumn } from '../../pm-plugins/utils/selection';
16
17
  import { TableCssClassName as ClassName } from '../../types';
17
18
  import getPopupOptions from './getPopupOptions';
18
19
  import { DragAndDropInsertButton } from './InsertButton';
@@ -65,8 +66,14 @@ export class FloatingInsertButton extends React.Component {
65
66
  tr
66
67
  }
67
68
  } = editorView;
68
- if (tr.selection instanceof CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) {
69
- return null;
69
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
70
+ if (tr.selection instanceof CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection() || isColumnSelectionWithMergedFirstRow(tr.selection) || isRowSelectionWithMergedFirstColumn(tr.selection))) {
71
+ return null;
72
+ }
73
+ } else {
74
+ if (tr.selection instanceof CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) {
75
+ return null;
76
+ }
70
77
  }
71
78
  const tablePos = findTable(tr.selection);
72
79
  if (!tablePos) {
@@ -120,6 +127,15 @@ export class FloatingInsertButton extends React.Component {
120
127
  const index = type === 'column' ? insertColumnButtonIndex : insertRowButtonIndex;
121
128
  const hasNumberedColumns = checkIfNumberColumnEnabled(editorView.state.selection);
122
129
 
130
+ // If row 0 has a colspan, anchor to a lower row for the real column boundary (X),
131
+ // then move back up to the table top (Y):
132
+ // row 0: [ colspan=2 ]
133
+ // row 1: [ col 1 ][ col 2 ] ← anchor here for X
134
+ // ↑ button should render at row 0/table top
135
+ let verticalOffsetCorrection = 0;
136
+ if (type === 'column' && expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
137
+ verticalOffsetCorrection = Math.max(0, targetCellRef.getBoundingClientRect().top - tableRef.getBoundingClientRect().top);
138
+ }
123
139
  // Fixed the 'add column button' not visible issue when sticky header is enabled
124
140
  // By setting the Popup z-index higher than the sticky header z-index ( common-styles.ts tr.sticky)
125
141
  // Only when inserting a column, otherwise set to undefined
@@ -137,7 +153,7 @@ export class FloatingInsertButton extends React.Component {
137
153
  allowOutOfBounds: true
138
154
  // Ignored via go/ees005
139
155
  // eslint-disable-next-line react/jsx-props-no-spreading
140
- }, getPopupOptions(type, index, hasNumberedColumns, tableContainerWrapper), {
156
+ }, getPopupOptions(type, index, hasNumberedColumns, tableContainerWrapper, verticalOffsetCorrection), {
141
157
  zIndex: zIndex
142
158
  }), /*#__PURE__*/React.createElement(DragAndDropInsertButton, {
143
159
  type: type,
@@ -147,6 +163,18 @@ export class FloatingInsertButton extends React.Component {
147
163
  isChromelessEditor: isChromelessEditor
148
164
  }));
149
165
  }
166
+
167
+ // Finds a row where `columnIndex` has real left/right cell boundaries.
168
+ findRowWithUnmergedColumn(tableMap, columnIndex) {
169
+ for (let row = 0; row < tableMap.height; row++) {
170
+ const pos = tableMap.map[row * tableMap.width + columnIndex];
171
+ const rect = tableMap.findCell(pos);
172
+ if (rect.left === columnIndex && rect.right === columnIndex + 1) {
173
+ return row;
174
+ }
175
+ }
176
+ return null;
177
+ }
150
178
  getCellPosition(type, tableNode) {
151
179
  const {
152
180
  insertColumnButtonIndex,
@@ -163,6 +191,10 @@ export class FloatingInsertButton extends React.Component {
163
191
  if (columnIndex > tableMap.width - 1) {
164
192
  return null;
165
193
  }
194
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
195
+ const rowWithRealColumn = this.findRowWithUnmergedColumn(tableMap, columnIndex);
196
+ return rowWithRealColumn === null ? null : tableMap.positionAt(rowWithRealColumn, columnIndex, tableNode);
197
+ }
166
198
  return tableMap.positionAt(0, columnIndex, tableNode);
167
199
  } else {
168
200
  // This condition is to make typescript happy.
@@ -8,15 +8,29 @@ 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
  const 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
+ }
28
+ const rect = getSelectionRect(selection);
29
+ return rect ? getSelectedColumnIndexes(rect) : [];
30
+ }
31
+
32
+ // Old behaviour: only standard column selections are recognised.
33
+ if (selection.isColSelection()) {
20
34
  const rect = getSelectionRect(selection);
21
35
  if (!rect) {
22
36
  return [];
@@ -125,27 +139,23 @@ export const ColumnControls = ({
125
139
  clearHoverSelection()(state, dispatch);
126
140
  }
127
141
  }, [editorView, tableActive]);
128
- const toggleDragMenuHandler = useCallback((trigger, event) => {
142
+ const toggleDragMenuHandler = useCallback((trigger, event, handleIndex) => {
129
143
  var _api$analytics2;
144
+ if (event !== null && event !== void 0 && event.shiftKey) {
145
+ return;
146
+ }
130
147
  const {
131
148
  state,
132
149
  dispatch
133
150
  } = editorView;
134
- if (event !== null && event !== void 0 && event.shiftKey) {
135
- // Shift-click extends the selection rather than toggling the menu, but the
136
- // open drag menu would otherwise stay anchored to a stale column. Close it here
137
- // for the updated menu (legacy menu closes via outside-click on its dropdown).
138
- if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
139
- api === null || api === void 0 ? void 0 : api.core.actions.execute(closeActiveTableMenu(api));
140
- }
141
- return;
142
- }
143
151
  if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
144
- if (colIndex !== undefined && api) {
152
+ // Use the clicked handle index because `hoveredCell` can point at a merged cell's first column.
153
+ const targetColIndex = handleIndex !== null && handleIndex !== void 0 ? handleIndex : colIndex;
154
+ if (targetColIndex !== undefined && api) {
145
155
  const {
146
156
  activeTableMenu: currentActiveTableMenu
147
157
  } = getTablePluginState(state);
148
- const isSameActiveMenu = (currentActiveTableMenu === null || currentActiveTableMenu === void 0 ? void 0 : currentActiveTableMenu.type) === 'column' && currentActiveTableMenu.index === colIndex;
158
+ const isSameActiveMenu = (currentActiveTableMenu === null || currentActiveTableMenu === void 0 ? void 0 : currentActiveTableMenu.type) === 'column' && currentActiveTableMenu.index === targetColIndex;
149
159
  api.core.actions.execute(({
150
160
  tr
151
161
  }) => {
@@ -164,7 +174,7 @@ export const ColumnControls = ({
164
174
  }
165
175
  toggleActiveTableMenu({
166
176
  type: 'column',
167
- index: colIndex,
177
+ index: targetColIndex,
168
178
  openedBy: trigger
169
179
  }, currentActiveTableMenu, api)({
170
180
  tr
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
3
3
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
- import { getColumnsWidths } from '../../pm-plugins/utils/column-controls';
4
+ import { getColumnsWidths, getColumnsWidthsWithMergedCells } from '../../pm-plugins/utils/column-controls';
5
5
  import { containsHeaderColumn } from '../../pm-plugins/utils/nodes';
6
6
  import { getRowHeights } from '../../pm-plugins/utils/row-controls';
7
7
  import { isNativeStickySupported } from '../../pm-plugins/utils/sticky-header';
@@ -63,7 +63,7 @@ const TableFloatingColumnControls = ({
63
63
  if (!tableRef || !tableActive || isResizing) {
64
64
  return null;
65
65
  }
66
- const colWidths = getColumnsWidths(editorView);
66
+ const colWidths = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true) ? getColumnsWidthsWithMergedCells(editorView) : getColumnsWidths(editorView);
67
67
  if (stickyTop) {
68
68
  var _containerRef$current;
69
69
  const columnControlTopOffsetFromParent = '-12px';
@@ -7,17 +7,31 @@ import { CellSelection } from '@atlaskit/editor-tables';
7
7
  import { getSelectionRect } from '@atlaskit/editor-tables/utils';
8
8
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
9
9
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
10
- import { clearHoverSelection, closeActiveTableMenu, toggleActiveTableMenu } from '../../../pm-plugins/commands';
10
+ import { clearHoverSelection, toggleActiveTableMenu } from '../../../pm-plugins/commands';
11
11
  import { toggleDragMenuWithAnalytics } from '../../../pm-plugins/drag-and-drop/commands-with-analytics';
12
12
  import { getPluginState as getTablePluginState } from '../../../pm-plugins/plugin-factory';
13
13
  import { getRowHeights, getRowsParams } from '../../../pm-plugins/utils/row-controls';
14
- import { getSelectedRowIndexes } from '../../../pm-plugins/utils/selection';
14
+ import { getSelectedRowIndexes, isRowSelectionWithMergedFirstColumn } from '../../../pm-plugins/utils/selection';
15
15
  import { TableCssClassName as ClassName } from '../../../types';
16
16
  import { dragRowControlsWidth, dropTargetExtendedWidth } from '../../consts';
17
17
  import { DragHandle } from '../../DragHandle';
18
18
  import RowDropTarget from '../RowDropTarget';
19
19
  const getSelectedRows = selection => {
20
- if (selection instanceof CellSelection && selection.isRowSelection()) {
20
+ if (!(selection instanceof CellSelection)) {
21
+ return [];
22
+ }
23
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
24
+ // New behaviour: also treat a row selection that sits to the right of a merged first-column
25
+ // cell as a full row selection.
26
+ if (!selection.isRowSelection() && !isRowSelectionWithMergedFirstColumn(selection)) {
27
+ return [];
28
+ }
29
+ const rect = getSelectionRect(selection);
30
+ return rect ? getSelectedRowIndexes(rect) : [];
31
+ }
32
+
33
+ // Old behaviour: only standard row selections are recognised.
34
+ if (selection.isRowSelection()) {
21
35
  const rect = getSelectionRect(selection);
22
36
  if (!rect) {
23
37
  return [];
@@ -78,18 +92,14 @@ export const DragControls = ({
78
92
  }
79
93
  });
80
94
  }, [editorView]);
81
- const toggleDragMenuHandler = useCallback((trigger, event) => {
95
+ const toggleDragMenuHandler = useCallback((trigger, event, handleIndex) => {
82
96
  var _api$analytics2;
83
97
  if (event !== null && event !== void 0 && event.shiftKey) {
84
- // Shift-click extends the selection rather than toggling the menu, but the
85
- // open drag menu would otherwise stay anchored to a stale row. Close it here
86
- // for the updated menu (legacy menu closes via outside-click on its dropdown).
87
- if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
88
- api === null || api === void 0 ? void 0 : api.core.actions.execute(closeActiveTableMenu(api));
89
- }
90
98
  return;
91
99
  }
92
- const rowIndex = hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex;
100
+
101
+ // Use the clicked handle index because `hoveredCell` can point at a merged cell's first row.
102
+ const rowIndex = handleIndex !== null && handleIndex !== void 0 ? handleIndex : hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex;
93
103
  if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
94
104
  if (rowIndex !== undefined && api) {
95
105
  const {
@@ -230,10 +240,17 @@ export const DragControls = ({
230
240
  if (!tableActive) {
231
241
  return null;
232
242
  }
243
+ const 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', `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}`, selectedRowIndexes));
247
+ const 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' : `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}` :
251
+ // Old behaviour.
252
+ `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}`;
253
+ handles.push(generateHandleByType('selected', selectedAppearance, selectedGridRow, selectedRowIndexes));
237
254
  if (hoveredCell && isTableHovered && rowIndex !== undefined && !selectedRowIndexes.includes(rowIndex) && rowIndex < rowHeights.length) {
238
255
  handles.push(generateHandleByType('hover', 'default', `${rowIndex + 1} / span 1`, rowIndexes));
239
256
  }
@@ -6,7 +6,7 @@
6
6
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
7
7
  import { css } from '@emotion/react';
8
8
  import { getBrowserInfo } from '@atlaskit/editor-common/browser';
9
- import { ANCHOR_VARIABLE_NAME, tableMarginTop, tableSharedStyle, TableSharedCssClassName } from '@atlaskit/editor-common/styles';
9
+ import { ANCHOR_VARIABLE_NAME, DRAG_HANDLE_WIDTH, tableMarginTop, tableSharedStyle, TableSharedCssClassName } from '@atlaskit/editor-common/styles';
10
10
  import { SORTABLE_COLUMN_ICON_CLASSNAME } from '@atlaskit/editor-common/table';
11
11
  import { akEditorSelectedNodeClassName, akEditorSmallZIndex, akEditorStickyHeaderZIndex, akEditorTableCellOnStickyHeaderZIndex, akEditorTableNumberColumnWidth, akEditorTableToolbarSize, akEditorUnitZIndex, getSelectionStyles, MAX_BROWSER_SCROLLBAR_HEIGHT, SelectionStyle, relativeSizeToBaseFontSize, relativeFontSizeToBase16, akEditorSelectedBorderColor } from '@atlaskit/editor-shared-styles';
12
12
  import { akEditorTableContainerBg } from '@atlaskit/editor-shared-styles/consts';
@@ -425,6 +425,31 @@ const baseTableStylesWithoutSharedStyle = props => css`
425
425
  width: ${insertColumnButtonOffset + 1}px;
426
426
  }
427
427
 
428
+ /* use :before element to hide table row insert dots when legacy table sticky header is activated */
429
+ ${expValEquals('platform_editor_table_col_insert', 'isEnabled', true) ?
430
+ // Mask geometry mirrors the drag-handle wrapper geometry from
431
+ // editor-plugin-block-controls/src/ui/drag-handle.tsx
432
+ // (`buttonWrapperStyles`):
433
+ // height = paddingTop (=calc(space.400 - 1px)) + paddingBottom (=space.200) + DRAG_HANDLE_HEIGHT
434
+ // width = DRAG_HANDLE_WIDTH + paddingRight('space.150')
435
+ `.${ClassName.TABLE_CONTAINER}.${ClassName.TABLE_STICKY}:has(tr.sticky)::before {
436
+ content: ' ';
437
+ position: sticky;
438
+ pointer-events: none;
439
+ top: 0;
440
+ float: left;
441
+ transform: translateX(calc(-1 * (${DRAG_HANDLE_WIDTH}px + ${"var(--ds-space-150, 12px)"})));
442
+ margin-bottom: calc(-1 * (${"var(--ds-space-400, 32px)"} - 1px + ${"var(--ds-space-200, 16px)"} + ${"var(--ds-space-300, 24px)"}));
443
+ height: calc(${"var(--ds-space-400, 32px)"} - 1px + ${"var(--ds-space-200, 16px)"} + ${"var(--ds-space-300, 24px)"});
444
+ width: calc(${DRAG_HANDLE_WIDTH}px + ${"var(--ds-space-150, 12px)"});
445
+ background: linear-gradient(
446
+ to bottom,
447
+ ${expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true) ? `var(${akEditorTableContainerBg}, ${"var(--ds-surface, #FFFFFF)"})` : "var(--ds-surface, #FFFFFF)"} 90%,
448
+ transparent
449
+ );
450
+ z-index: ${rowControlsZIndex + 5};
451
+ }` : ``}
452
+
428
453
  /* To fix jumpiness caused in Chrome Browsers for sticky headers */
429
454
  .${ClassName.TABLE_STICKY} .sticky + tr {
430
455
  min-height: 0px;
@@ -16,9 +16,10 @@ import { getPluginState as getResizePluginState } from '../pm-plugins/table-resi
16
16
  import { deleteColumns } from '../pm-plugins/transforms/delete-columns';
17
17
  import { deleteRows } from '../pm-plugins/transforms/delete-rows';
18
18
  import { getSelectedCellInfo } from '../pm-plugins/utils/analytics';
19
- import { convertHTMLCellIndexToColumnIndex, getColumnIndexMappedToColumnIndexInFirstRow } from '../pm-plugins/utils/column-controls';
20
- import { getColumnOrRowIndex, getMousePositionHorizontalRelativeByElement, getMousePositionVerticalRelativeByElement, hasResizeHandler, isCell, isColumnControlsDecorations, isCornerButton, isDragColumnFloatingInsertDot, isDragCornerButton, isDragRowFloatingInsertDot, isInsertRowButton, isResizeHandleDecoration, isRowControlsButton, isTableContainerOrWrapper, isTableControlsButton } from '../pm-plugins/utils/dom';
19
+ import { convertHTMLCellIndexToColumnIndex, getColumnIndexByMousePosition, getColumnIndexMappedToColumnIndexInFirstRow } from '../pm-plugins/utils/column-controls';
20
+ import { getColumnOrRowIndex, getIndexAttributeSourceElement, getMousePositionHorizontalRelativeByElement, getMousePositionVerticalRelativeByElement, hasResizeHandler, isCell, isColumnControlsDecorations, isCornerButton, isDragColumnFloatingInsertDot, isDragCornerButton, isDragRowFloatingInsertDot, isInsertRowButton, isResizeHandleDecoration, isRowControlsButton, isTableContainerOrWrapper, isTableControlsButton } from '../pm-plugins/utils/dom';
21
21
  import { getAllowAddColumnCustomStep } from '../pm-plugins/utils/get-allow-add-column-custom-step';
22
+ import { getRowIndexByMousePosition } from '../pm-plugins/utils/row-controls';
22
23
  import { TableCssClassName as ClassName, RESIZE_HANDLE_AREA_DECORATION_GAP } from '../types';
23
24
  import { TABLE_MENU_SELECTOR } from './TableMenu/shared/consts';
24
25
  const isFocusingCalendar = event => event instanceof FocusEvent && event.relatedTarget instanceof HTMLElement && event.relatedTarget.getAttribute('aria-label') === 'calendar';
@@ -292,6 +293,19 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
292
293
  return false;
293
294
  }
294
295
  const element = event.target;
296
+ const isTableMenuUpdatesEnabled = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true);
297
+
298
+ // Spanned cells need mouse-position-based hover indexes; normal cells already report them.
299
+ if (isTableMenuUpdatesEnabled) {
300
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
301
+ const tableCell = isElementInTableCell(element);
302
+ if (tableCell && (tableCell.rowSpan > 1 || tableCell.colSpan > 1)) {
303
+ const dragDropState = getDragDropPluginState(view.state);
304
+ if (dragDropState && !dragDropState.isDragging) {
305
+ trackCellLocation(view, event);
306
+ }
307
+ }
308
+ }
295
309
  if (isColumnControlsDecorations(element) || isDragColumnFloatingInsertDot(element)) {
296
310
  const {
297
311
  state,
@@ -300,7 +314,8 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
300
314
  const {
301
315
  insertColumnButtonIndex
302
316
  } = getPluginState(state);
303
- const [startIndex, endIndex] = getColumnOrRowIndex(element);
317
+ const indexSourceElement = isTableMenuUpdatesEnabled ? getIndexAttributeSourceElement(element) : element;
318
+ const [startIndex, endIndex] = getColumnOrRowIndex(indexSourceElement);
304
319
  const positionColumn = getMousePositionHorizontalRelativeByElement(event, offsetX, undefined) === 'right' ? endIndex : startIndex;
305
320
  if (positionColumn !== insertColumnButtonIndex) {
306
321
  return showInsertColumnButton(positionColumn)(state, dispatch);
@@ -314,7 +329,8 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
314
329
  const {
315
330
  insertRowButtonIndex
316
331
  } = getPluginState(state);
317
- const [startIndex, endIndex] = getColumnOrRowIndex(element);
332
+ const indexSourceElement = isTableMenuUpdatesEnabled ? getIndexAttributeSourceElement(element) : element;
333
+ const [startIndex, endIndex] = getColumnOrRowIndex(indexSourceElement);
318
334
  const positionRow = getMousePositionVerticalRelativeByElement(event) === 'bottom' ? endIndex : startIndex;
319
335
  if (positionRow !== insertRowButtonIndex) {
320
336
  return showInsertRowButton(positionRow)(state, dispatch);
@@ -462,7 +478,8 @@ export const whenTableInFocus = (eventHandler, pluginInjectionApi) => (view, mou
462
478
  return eventHandler(view, mouseEvent);
463
479
  };
464
480
  const trackCellLocation = (view, mouseEvent) => {
465
- var _tableElement$dataset;
481
+ var _tableElement$dataset, _getRowIndexByMousePo;
482
+ const isTableMenuUpdatesEnabled = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true);
466
483
  const target = mouseEvent.target;
467
484
  // Ignored via go/ees005
468
485
  // eslint-disable-next-line @atlaskit/editor/no-as-casting
@@ -488,13 +505,36 @@ const trackCellLocation = (view, mouseEvent) => {
488
505
  // eslint-disable-next-line @atlaskit/editor/no-as-casting
489
506
  const rowElement = closestElement(target, 'tr');
490
507
  const htmlRowIndex = rowElement && rowElement.rowIndex;
508
+ const rowIndex = maybeTableCell.rowSpan > 1 && mouseEvent instanceof MouseEvent && isTableMenuUpdatesEnabled ? (_getRowIndexByMousePo = getRowIndexByMousePosition(tableRef, mouseEvent, {
509
+ startIndex: htmlRowIndex,
510
+ endIndex: htmlRowIndex + maybeTableCell.rowSpan
511
+ })) !== null && _getRowIndexByMousePo !== void 0 ? _getRowIndexByMousePo : htmlRowIndex : htmlRowIndex;
491
512
  const tableMap = tableNode && TableMap.get(tableNode);
492
513
  let colIndex = htmlColIndex;
493
514
  if (tableMap) {
494
515
  const convertedColIndex = convertHTMLCellIndexToColumnIndex(htmlColIndex, htmlRowIndex, tableMap);
495
- colIndex = getColumnIndexMappedToColumnIndexInFirstRow(convertedColIndex, htmlRowIndex, tableMap);
516
+ if (isTableMenuUpdatesEnabled) {
517
+ // New behaviour: the column controls grid renders one track per visual column
518
+ // (`getColumnsWidthsWithMergedCells`), so the hover index must be the visual column
519
+ // index — not the first-row-cell index. Mapping to the first-row cell would collapse
520
+ // every column under a merged first-row cell to the same index, leaving the drag handle
521
+ // stuck when hovering cells in other rows. For a colspanned cell, refine the converted
522
+ // (left-most) visual column to the exact visual column under the pointer.
523
+ colIndex = convertedColIndex;
524
+ if (maybeTableCell.colSpan > 1 && mouseEvent instanceof MouseEvent) {
525
+ var _getColumnIndexByMous;
526
+ colIndex = (_getColumnIndexByMous = getColumnIndexByMousePosition(maybeTableCell, mouseEvent, {
527
+ startIndex: convertedColIndex,
528
+ endIndex: convertedColIndex + maybeTableCell.colSpan
529
+ })) !== null && _getColumnIndexByMous !== void 0 ? _getColumnIndexByMous : convertedColIndex;
530
+ }
531
+ } else {
532
+ // Old behaviour: the legacy grid renders one track per first-row cell, so snap to the
533
+ // first-row column index (a merged first-row cell collapses to a single column track).
534
+ colIndex = getColumnIndexMappedToColumnIndexInFirstRow(convertedColIndex, htmlRowIndex, tableMap);
535
+ }
496
536
  }
497
- hoverCell(htmlRowIndex, colIndex)(view.state, view.dispatch);
537
+ hoverCell(rowIndex, colIndex)(view.state, view.dispatch);
498
538
  };
499
539
  export const withCellTracking = eventHandler => (view, mouseEvent) => {
500
540
  if (getDragDropPluginState(view.state) && !getDragDropPluginState(view.state).isDragging) {