@atlaskit/editor-plugin-table 24.2.2 → 24.2.4

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 (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/pm-plugins/commands/active-table-menu.js +14 -5
  3. package/dist/cjs/pm-plugins/drag-and-drop/plugin.js +3 -1
  4. package/dist/cjs/pm-plugins/main.js +3 -1
  5. package/dist/cjs/pm-plugins/utils/column-controls.js +119 -2
  6. package/dist/cjs/pm-plugins/utils/dom.js +23 -1
  7. package/dist/cjs/pm-plugins/utils/row-controls.js +43 -1
  8. package/dist/cjs/pm-plugins/utils/selection.js +74 -1
  9. package/dist/cjs/ui/DragHandle/index.js +46 -4
  10. package/dist/cjs/ui/FloatingInsertButton/getPopupOptions.js +19 -5
  11. package/dist/cjs/ui/FloatingInsertButton/index.js +37 -3
  12. package/dist/cjs/ui/FloatingTableMenu/index.js +4 -7
  13. package/dist/cjs/ui/TableFloatingColumnControls/ColumnControls/index.js +26 -15
  14. package/dist/cjs/ui/TableFloatingColumnControls/index.js +1 -1
  15. package/dist/cjs/ui/TableFloatingControls/RowControls/DragControls.js +29 -12
  16. package/dist/cjs/ui/common-styles.js +6 -1
  17. package/dist/cjs/ui/event-handlers.js +61 -8
  18. package/dist/es2019/pm-plugins/commands/active-table-menu.js +14 -5
  19. package/dist/es2019/pm-plugins/drag-and-drop/plugin.js +3 -1
  20. package/dist/es2019/pm-plugins/main.js +1 -1
  21. package/dist/es2019/pm-plugins/utils/column-controls.js +114 -2
  22. package/dist/es2019/pm-plugins/utils/dom.js +20 -0
  23. package/dist/es2019/pm-plugins/utils/row-controls.js +42 -0
  24. package/dist/es2019/pm-plugins/utils/selection.js +73 -0
  25. package/dist/es2019/ui/DragHandle/index.js +47 -4
  26. package/dist/es2019/ui/FloatingInsertButton/getPopupOptions.js +24 -5
  27. package/dist/es2019/ui/FloatingInsertButton/index.js +35 -3
  28. package/dist/es2019/ui/FloatingTableMenu/index.js +4 -7
  29. package/dist/es2019/ui/TableFloatingColumnControls/ColumnControls/index.js +27 -16
  30. package/dist/es2019/ui/TableFloatingColumnControls/index.js +2 -2
  31. package/dist/es2019/ui/TableFloatingControls/RowControls/DragControls.js +30 -12
  32. package/dist/es2019/ui/common-styles.js +26 -1
  33. package/dist/es2019/ui/event-handlers.js +61 -8
  34. package/dist/esm/pm-plugins/commands/active-table-menu.js +14 -5
  35. package/dist/esm/pm-plugins/drag-and-drop/plugin.js +3 -1
  36. package/dist/esm/pm-plugins/main.js +3 -1
  37. package/dist/esm/pm-plugins/utils/column-controls.js +118 -1
  38. package/dist/esm/pm-plugins/utils/dom.js +22 -0
  39. package/dist/esm/pm-plugins/utils/row-controls.js +42 -0
  40. package/dist/esm/pm-plugins/utils/selection.js +73 -0
  41. package/dist/esm/ui/DragHandle/index.js +47 -5
  42. package/dist/esm/ui/FloatingInsertButton/getPopupOptions.js +19 -5
  43. package/dist/esm/ui/FloatingInsertButton/index.js +37 -3
  44. package/dist/esm/ui/FloatingTableMenu/index.js +4 -7
  45. package/dist/esm/ui/TableFloatingColumnControls/ColumnControls/index.js +28 -17
  46. package/dist/esm/ui/TableFloatingColumnControls/index.js +2 -2
  47. package/dist/esm/ui/TableFloatingControls/RowControls/DragControls.js +31 -14
  48. package/dist/esm/ui/common-styles.js +7 -2
  49. package/dist/esm/ui/event-handlers.js +63 -10
  50. package/dist/types/pm-plugins/commands/active-table-menu.d.ts +3 -1
  51. package/dist/types/pm-plugins/utils/column-controls.d.ts +35 -1
  52. package/dist/types/pm-plugins/utils/dom.d.ts +11 -0
  53. package/dist/types/pm-plugins/utils/row-controls.d.ts +16 -0
  54. package/dist/types/pm-plugins/utils/selection.d.ts +20 -0
  55. package/dist/types/ui/DragHandle/index.d.ts +3 -2
  56. package/dist/types/ui/FloatingInsertButton/getPopupOptions.d.ts +1 -1
  57. package/dist/types/ui/FloatingInsertButton/index.d.ts +1 -0
  58. package/dist/types/ui/event-handlers.d.ts +1 -1
  59. package/package.json +4 -4
@@ -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 { isFullRowOrColumnSelected } 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 (isFullRowOrColumnSelected(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.
@@ -58,9 +58,6 @@ const FloatingTableMenu = ({
58
58
  popupContentRef.current = el;
59
59
  setOutsideClickTargetRef === null || setOutsideClickTargetRef === void 0 ? void 0 : setOutsideClickTargetRef(el);
60
60
  }, [setOutsideClickTargetRef]);
61
- const dismiss = useCallback(() => {
62
- api === null || api === void 0 ? void 0 : api.core.actions.execute(closeActiveTableMenu(api));
63
- }, [api]);
64
61
  const returnFocusToDragHandle = useCallback(() => {
65
62
  // Match legacy DragMenu's closeMenu('handle') behaviour.
66
63
  const handleId = dragMenuDirection === 'row' ? '#drag-handle-button-row' : '#drag-handle-button-column';
@@ -93,9 +90,9 @@ const FloatingTableMenu = ({
93
90
  const handleEscape = useCallback(event => {
94
91
  event.preventDefault();
95
92
  event.stopPropagation();
96
- dismiss();
93
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(closeActiveTableMenu(api));
97
94
  returnFocusToDragHandle();
98
- }, [dismiss, returnFocusToDragHandle]);
95
+ }, [api, returnFocusToDragHandle]);
99
96
 
100
97
  // Memoize the editor DOM reference so the provider doesn't re-bind listeners
101
98
  // on every render (the provider depends on `dom` in its effect's deps).
@@ -112,8 +109,8 @@ const FloatingTableMenu = ({
112
109
  if (target instanceof Node && (_popupContentRef$curr = popupContentRef.current) !== null && _popupContentRef$curr !== void 0 && _popupContentRef$curr.contains(target) || target instanceof Element && (target.closest(DRAG_HANDLE_CONTROLS_SELECTOR) || target.closest(NESTED_DROPDOWN_SELECTOR))) {
113
110
  return;
114
111
  }
115
- dismiss();
116
- }, [dismiss]);
112
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(closeActiveTableMenu(api));
113
+ }, [api]);
117
114
  if (!isDragMenuOpen || !targetCellPosition || editorView.state.doc.nodeSize <= targetCellPosition) {
118
115
  return null;
119
116
  }
@@ -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
@@ -222,6 +232,7 @@ export const ColumnControls = ({
222
232
  },
223
233
  "data-testid": `table-floating-column-${isHover ? colIndex : isPlaceholder ? appearance : selectedColIndexes[0]}-drag-handle`
224
234
  }, /*#__PURE__*/React.createElement(DragHandle, {
235
+ api: api,
225
236
  isDragMenuTarget: !isHover,
226
237
  direction: "column",
227
238
  tableLocalId: localId || '',
@@ -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 {
@@ -205,6 +215,7 @@ export const DragControls = ({
205
215
  "data-selected-row-index": selectedRowIndexes[0],
206
216
  "data-handle-appearance": appearance
207
217
  }, /*#__PURE__*/React.createElement(DragHandle, {
218
+ api: api,
208
219
  isDragMenuTarget: !isHover,
209
220
  direction: "row",
210
221
  tableLocalId: currentNodeLocalId,
@@ -230,10 +241,17 @@ export const DragControls = ({
230
241
  if (!tableActive) {
231
242
  return null;
232
243
  }
244
+ const selectedAppearance = isRowSelected && isEntireTableSelected ? isInDanger ? 'danger' : 'selected' : 'placeholder';
233
245
 
234
246
  // placeholder / selected need to always render at least one handle
235
247
  // 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));
248
+ const selectedGridRow = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true) ?
249
+ // New behaviour: always position the placeholder in the first row to avoid an invalid
250
+ // `NaN / span 0` grid placement (which makes the handle disappear) when no rows are selected.
251
+ selectedAppearance === 'placeholder' ? '1 / span 1' : `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}` :
252
+ // Old behaviour.
253
+ `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}`;
254
+ handles.push(generateHandleByType('selected', selectedAppearance, selectedGridRow, selectedRowIndexes));
237
255
  if (hoveredCell && isTableHovered && rowIndex !== undefined && !selectedRowIndexes.includes(rowIndex) && rowIndex < rowHeights.length) {
238
256
  handles.push(generateHandleByType('hover', 'default', `${rowIndex + 1} / span 1`, rowIndexes));
239
257
  }
@@ -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, isTableDragHandleButton } 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';
@@ -196,14 +197,27 @@ export const handleMouseUp = (view, mouseEvent) => {
196
197
  }
197
198
  return false;
198
199
  };
200
+ const resetDragHandleSelectedIntentOnMouseDown = (event, api) => {
201
+ var _api$userIntent, _api$userIntent$share;
202
+ if (isTableDragHandleButton(event.target)) {
203
+ return;
204
+ }
205
+ if ((api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'dragHandleSelected') {
206
+ var _api$core, _api$userIntent2;
207
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('default'));
208
+ }
209
+ };
199
210
 
200
211
  // Ignore any `mousedown` `event` from control and numbered column buttons
201
212
  // PM end up changing selection during shift selection if not prevented
202
- export const handleMouseDown = (_, event) => {
213
+ export const handleMouseDown = (_view, event, api) => {
203
214
  const isControl = !!(event.target && event.target instanceof HTMLElement && (isTableContainerOrWrapper(event.target) || isColumnControlsDecorations(event.target) || isRowControlsButton(event.target) || isDragCornerButton(event.target)));
204
215
  if (isControl) {
205
216
  event.preventDefault();
206
217
  }
218
+ if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) {
219
+ resetDragHandleSelectedIntentOnMouseDown(event, api);
220
+ }
207
221
  return isControl;
208
222
  };
209
223
  export const handleMouseOut = (view, mouseEvent) => {
@@ -292,6 +306,19 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
292
306
  return false;
293
307
  }
294
308
  const element = event.target;
309
+ const isTableMenuUpdatesEnabled = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true);
310
+
311
+ // Spanned cells need mouse-position-based hover indexes; normal cells already report them.
312
+ if (isTableMenuUpdatesEnabled) {
313
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
314
+ const tableCell = isElementInTableCell(element);
315
+ if (tableCell && (tableCell.rowSpan > 1 || tableCell.colSpan > 1)) {
316
+ const dragDropState = getDragDropPluginState(view.state);
317
+ if (dragDropState && !dragDropState.isDragging) {
318
+ trackCellLocation(view, event);
319
+ }
320
+ }
321
+ }
295
322
  if (isColumnControlsDecorations(element) || isDragColumnFloatingInsertDot(element)) {
296
323
  const {
297
324
  state,
@@ -300,7 +327,8 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
300
327
  const {
301
328
  insertColumnButtonIndex
302
329
  } = getPluginState(state);
303
- const [startIndex, endIndex] = getColumnOrRowIndex(element);
330
+ const indexSourceElement = isTableMenuUpdatesEnabled ? getIndexAttributeSourceElement(element) : element;
331
+ const [startIndex, endIndex] = getColumnOrRowIndex(indexSourceElement);
304
332
  const positionColumn = getMousePositionHorizontalRelativeByElement(event, offsetX, undefined) === 'right' ? endIndex : startIndex;
305
333
  if (positionColumn !== insertColumnButtonIndex) {
306
334
  return showInsertColumnButton(positionColumn)(state, dispatch);
@@ -314,7 +342,8 @@ const handleMouseMoveDebounce = nodeViewPortalProviderAPI => rafSchedule((view,
314
342
  const {
315
343
  insertRowButtonIndex
316
344
  } = getPluginState(state);
317
- const [startIndex, endIndex] = getColumnOrRowIndex(element);
345
+ const indexSourceElement = isTableMenuUpdatesEnabled ? getIndexAttributeSourceElement(element) : element;
346
+ const [startIndex, endIndex] = getColumnOrRowIndex(indexSourceElement);
318
347
  const positionRow = getMousePositionVerticalRelativeByElement(event) === 'bottom' ? endIndex : startIndex;
319
348
  if (positionRow !== insertRowButtonIndex) {
320
349
  return showInsertRowButton(positionRow)(state, dispatch);
@@ -462,7 +491,8 @@ export const whenTableInFocus = (eventHandler, pluginInjectionApi) => (view, mou
462
491
  return eventHandler(view, mouseEvent);
463
492
  };
464
493
  const trackCellLocation = (view, mouseEvent) => {
465
- var _tableElement$dataset;
494
+ var _tableElement$dataset, _getRowIndexByMousePo;
495
+ const isTableMenuUpdatesEnabled = expValEquals('platform_editor_table_menu_updates', 'isEnabled', true);
466
496
  const target = mouseEvent.target;
467
497
  // Ignored via go/ees005
468
498
  // eslint-disable-next-line @atlaskit/editor/no-as-casting
@@ -488,13 +518,36 @@ const trackCellLocation = (view, mouseEvent) => {
488
518
  // eslint-disable-next-line @atlaskit/editor/no-as-casting
489
519
  const rowElement = closestElement(target, 'tr');
490
520
  const htmlRowIndex = rowElement && rowElement.rowIndex;
521
+ const rowIndex = maybeTableCell.rowSpan > 1 && mouseEvent instanceof MouseEvent && isTableMenuUpdatesEnabled ? (_getRowIndexByMousePo = getRowIndexByMousePosition(tableRef, mouseEvent, {
522
+ startIndex: htmlRowIndex,
523
+ endIndex: htmlRowIndex + maybeTableCell.rowSpan
524
+ })) !== null && _getRowIndexByMousePo !== void 0 ? _getRowIndexByMousePo : htmlRowIndex : htmlRowIndex;
491
525
  const tableMap = tableNode && TableMap.get(tableNode);
492
526
  let colIndex = htmlColIndex;
493
527
  if (tableMap) {
494
528
  const convertedColIndex = convertHTMLCellIndexToColumnIndex(htmlColIndex, htmlRowIndex, tableMap);
495
- colIndex = getColumnIndexMappedToColumnIndexInFirstRow(convertedColIndex, htmlRowIndex, tableMap);
529
+ if (isTableMenuUpdatesEnabled) {
530
+ // New behaviour: the column controls grid renders one track per visual column
531
+ // (`getColumnsWidthsWithMergedCells`), so the hover index must be the visual column
532
+ // index — not the first-row-cell index. Mapping to the first-row cell would collapse
533
+ // every column under a merged first-row cell to the same index, leaving the drag handle
534
+ // stuck when hovering cells in other rows. For a colspanned cell, refine the converted
535
+ // (left-most) visual column to the exact visual column under the pointer.
536
+ colIndex = convertedColIndex;
537
+ if (maybeTableCell.colSpan > 1 && mouseEvent instanceof MouseEvent) {
538
+ var _getColumnIndexByMous;
539
+ colIndex = (_getColumnIndexByMous = getColumnIndexByMousePosition(maybeTableCell, mouseEvent, {
540
+ startIndex: convertedColIndex,
541
+ endIndex: convertedColIndex + maybeTableCell.colSpan
542
+ })) !== null && _getColumnIndexByMous !== void 0 ? _getColumnIndexByMous : convertedColIndex;
543
+ }
544
+ } else {
545
+ // Old behaviour: the legacy grid renders one track per first-row cell, so snap to the
546
+ // first-row column index (a merged first-row cell collapses to a single column track).
547
+ colIndex = getColumnIndexMappedToColumnIndexInFirstRow(convertedColIndex, htmlRowIndex, tableMap);
548
+ }
496
549
  }
497
- hoverCell(htmlRowIndex, colIndex)(view.state, view.dispatch);
550
+ hoverCell(rowIndex, colIndex)(view.state, view.dispatch);
498
551
  };
499
552
  export const withCellTracking = eventHandler => (view, mouseEvent) => {
500
553
  if (getDragDropPluginState(view.state) && !getDragDropPluginState(view.state).isDragging) {
@@ -1,12 +1,19 @@
1
1
  import { pluginKey } from '../plugin-key';
2
+ import { isFullRowOrColumnSelected } from '../utils/selection';
2
3
  var applyMenuUserIntent = function applyMenuUserIntent(tr, api, nextActiveTableMenu) {
3
- var _api$userIntent;
4
- var isRowOrColumnMenuOpen = nextActiveTableMenu.type === 'row' || nextActiveTableMenu.type === 'column';
5
- api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent(isRowOrColumnMenuOpen ? 'tableDragMenuPopupOpen' : 'default')({
4
+ var _api$userIntent2;
5
+ if (nextActiveTableMenu.type === 'row' || nextActiveTableMenu.type === 'column') {
6
+ var _api$userIntent;
7
+ api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent('tableDragMenuPopupOpen')({
8
+ tr: tr
9
+ });
10
+ return;
11
+ }
12
+ api === null || api === void 0 || (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 || _api$userIntent2.commands.setCurrentUserIntent(isFullRowOrColumnSelected(tr.selection) ? 'dragHandleSelected' : 'default')({
6
13
  tr: tr
7
14
  });
8
15
  };
9
- export var closeActiveTableMenu = function closeActiveTableMenu(api) {
16
+ export var closeActiveTableMenu = function closeActiveTableMenu(api, options) {
10
17
  return function (_ref) {
11
18
  var tr = _ref.tr;
12
19
  var nextActiveTableMenu = {
@@ -18,7 +25,9 @@ export var closeActiveTableMenu = function closeActiveTableMenu(api) {
18
25
  activeTableMenu: nextActiveTableMenu
19
26
  }
20
27
  });
21
- applyMenuUserIntent(tr, api, nextActiveTableMenu);
28
+ if (!(options !== null && options !== void 0 && options.skipUserIntent)) {
29
+ applyMenuUserIntent(tr, api, nextActiveTableMenu);
30
+ }
22
31
  if (!tr.docChanged) {
23
32
  tr.setMeta('addToHistory', false);
24
33
  }
@@ -90,7 +90,9 @@ var destroyFn = function destroyFn(editorView, editorAnalyticsAPI, isTableScalin
90
90
  api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
91
91
  var _api$userIntent;
92
92
  var tr = _ref7.tr;
93
- closeActiveTableMenu(api)({
93
+ closeActiveTableMenu(api, {
94
+ skipUserIntent: true
95
+ })({
94
96
  tr: tr
95
97
  });
96
98
  api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent('dragging')({
@@ -341,7 +341,9 @@ export var createPlugin = function createPlugin(dispatchAnalyticsEvent, dispatch
341
341
  handleDOMEvents: {
342
342
  focus: handleFocus,
343
343
  blur: handleBlur,
344
- mousedown: withCellTracking(handleMouseDown),
344
+ mousedown: withCellTracking(function (view, event) {
345
+ return handleMouseDown(view, event, pluginInjectionApi);
346
+ }),
345
347
  mouseleave: handleMouseLeave,
346
348
  mousemove: whenTableInFocus(handleMouseMove(nodeViewPortalProviderAPI), pluginInjectionApi),
347
349
  mouseenter: handleMouseEnter,