@atlaskit/editor-plugin-table 24.2.1 → 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.
- package/CHANGELOG.md +21 -0
- package/dist/cjs/pm-plugins/utils/column-controls.js +119 -2
- package/dist/cjs/pm-plugins/utils/dom.js +20 -1
- package/dist/cjs/pm-plugins/utils/row-controls.js +43 -1
- package/dist/cjs/pm-plugins/utils/selection.js +61 -1
- package/dist/cjs/ui/DragHandle/index.js +10 -2
- package/dist/cjs/ui/FloatingInsertButton/getPopupOptions.js +19 -5
- package/dist/cjs/ui/FloatingInsertButton/index.js +37 -3
- package/dist/cjs/ui/TableFloatingColumnControls/ColumnControls/index.js +25 -15
- package/dist/cjs/ui/TableFloatingColumnControls/index.js +1 -1
- package/dist/cjs/ui/TableFloatingControls/RowControls/DragControls.js +28 -12
- package/dist/cjs/ui/common-styles.js +6 -1
- package/dist/cjs/ui/event-handlers.js +47 -7
- package/dist/cjs/ui/rounded-table-styles.js +1 -1
- package/dist/es2019/pm-plugins/utils/column-controls.js +114 -2
- package/dist/es2019/pm-plugins/utils/dom.js +19 -0
- package/dist/es2019/pm-plugins/utils/row-controls.js +42 -0
- package/dist/es2019/pm-plugins/utils/selection.js +60 -0
- package/dist/es2019/ui/DragHandle/index.js +10 -2
- package/dist/es2019/ui/FloatingInsertButton/getPopupOptions.js +24 -5
- package/dist/es2019/ui/FloatingInsertButton/index.js +35 -3
- package/dist/es2019/ui/TableFloatingColumnControls/ColumnControls/index.js +26 -16
- package/dist/es2019/ui/TableFloatingColumnControls/index.js +2 -2
- package/dist/es2019/ui/TableFloatingControls/RowControls/DragControls.js +29 -12
- package/dist/es2019/ui/common-styles.js +28 -2
- package/dist/es2019/ui/event-handlers.js +47 -7
- package/dist/es2019/ui/rounded-table-styles.js +1 -1
- package/dist/esm/pm-plugins/utils/column-controls.js +118 -1
- package/dist/esm/pm-plugins/utils/dom.js +19 -0
- package/dist/esm/pm-plugins/utils/row-controls.js +42 -0
- package/dist/esm/pm-plugins/utils/selection.js +60 -0
- package/dist/esm/ui/DragHandle/index.js +10 -2
- package/dist/esm/ui/FloatingInsertButton/getPopupOptions.js +19 -5
- package/dist/esm/ui/FloatingInsertButton/index.js +37 -3
- package/dist/esm/ui/TableFloatingColumnControls/ColumnControls/index.js +27 -17
- package/dist/esm/ui/TableFloatingColumnControls/index.js +2 -2
- package/dist/esm/ui/TableFloatingControls/RowControls/DragControls.js +30 -14
- package/dist/esm/ui/common-styles.js +7 -2
- package/dist/esm/ui/event-handlers.js +49 -9
- package/dist/esm/ui/rounded-table-styles.js +1 -1
- package/dist/types/pm-plugins/utils/column-controls.d.ts +35 -1
- package/dist/types/pm-plugins/utils/dom.d.ts +10 -0
- package/dist/types/pm-plugins/utils/row-controls.d.ts +16 -0
- package/dist/types/pm-plugins/utils/selection.d.ts +13 -0
- package/dist/types/ui/DragHandle/index.d.ts +1 -1
- package/dist/types/ui/FloatingInsertButton/getPopupOptions.d.ts +1 -1
- package/dist/types/ui/FloatingInsertButton/index.d.ts +1 -0
- package/package.json +3 -3
|
@@ -34,6 +34,69 @@ export const getColumnsWidths = view => {
|
|
|
34
34
|
}
|
|
35
35
|
return widths;
|
|
36
36
|
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Splits a merged cell's rendered width by `colwidth` ratios, falling back to an even split when
|
|
40
|
+
* usable ratios are unavailable.
|
|
41
|
+
*/
|
|
42
|
+
export const getProportionalColumnWidths = (totalWidth, columnCount, ratios) => {
|
|
43
|
+
const evenSplit = () => new Array(columnCount).fill(totalWidth / columnCount);
|
|
44
|
+
if (!ratios || ratios.length !== columnCount) {
|
|
45
|
+
return evenSplit();
|
|
46
|
+
}
|
|
47
|
+
const total = ratios.reduce((sum, ratio) => sum + (ratio > 0 ? ratio : 0), 0);
|
|
48
|
+
if (total <= 0) {
|
|
49
|
+
return evenSplit();
|
|
50
|
+
}
|
|
51
|
+
return ratios.map(ratio => totalWidth * ((ratio > 0 ? ratio : 0) / total));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Like `getColumnsWidths`, but fills every visual column under a first-row `colspan` so column
|
|
56
|
+
* controls can render one grid track per column.
|
|
57
|
+
*/
|
|
58
|
+
export const getColumnsWidthsWithMergedCells = view => {
|
|
59
|
+
const {
|
|
60
|
+
selection
|
|
61
|
+
} = view.state;
|
|
62
|
+
const table = findTable(selection);
|
|
63
|
+
if (!table) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const map = TableMap.get(table.node);
|
|
67
|
+
const domAtPos = view.domAtPos.bind(view);
|
|
68
|
+
const widths = Array.from({
|
|
69
|
+
length: map.width
|
|
70
|
+
});
|
|
71
|
+
for (let i = 0; i < map.width; i++) {
|
|
72
|
+
var _node$attrs$colspan;
|
|
73
|
+
if (map.isCellMergedTopLeft(0, i)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Ignored via go/ees005
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
79
|
+
const node = table.node.nodeAt(map.map[i]);
|
|
80
|
+
const pos = map.map[i] + table.start;
|
|
81
|
+
// Ignored via go/ees005
|
|
82
|
+
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
83
|
+
const cellRef = findDomRefAtPos(pos, domAtPos);
|
|
84
|
+
const rect = cellRef.getBoundingClientRect();
|
|
85
|
+
const measuredWidth = (rect ? rect.width : cellRef.offsetWidth) + 1;
|
|
86
|
+
const colspan = (_node$attrs$colspan = node.attrs.colspan) !== null && _node$attrs$colspan !== void 0 ? _node$attrs$colspan : 1;
|
|
87
|
+
if (colspan <= 1) {
|
|
88
|
+
widths[i] = measuredWidth;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const colwidth = Array.isArray(node.attrs.colwidth) ? node.attrs.colwidth : undefined;
|
|
92
|
+
const perColumnWidths = getProportionalColumnWidths(measuredWidth, colspan, colwidth);
|
|
93
|
+
for (let span = 0; span < colspan && i + span < map.width; span++) {
|
|
94
|
+
widths[i + span] = perColumnWidths[span];
|
|
95
|
+
}
|
|
96
|
+
i += colspan - 1;
|
|
97
|
+
}
|
|
98
|
+
return widths;
|
|
99
|
+
};
|
|
37
100
|
export const getColumnDeleteButtonParams = (columnsWidths, selection) => {
|
|
38
101
|
const rect = getSelectionRect(selection);
|
|
39
102
|
if (!rect) {
|
|
@@ -108,7 +171,9 @@ const getRelativeDomCellWidths = ({
|
|
|
108
171
|
const totalCalculatedCellWidth = cellColWidths.reduce((acc, cellColWidth) => acc + cellColWidth, 0);
|
|
109
172
|
return cellColWidths.map(cellColWidth => width * (cellColWidth / totalCalculatedCellWidth));
|
|
110
173
|
};
|
|
111
|
-
export const colWidthsForRow = tr
|
|
174
|
+
export const colWidthsForRow = (tr, {
|
|
175
|
+
useColwidthRatios = false
|
|
176
|
+
} = {}) => {
|
|
112
177
|
// get the colspans
|
|
113
178
|
const rowColSpans = maphElem(tr, cell => Number(cell.getAttribute('colspan') || 1 /* default to span of 1 */));
|
|
114
179
|
|
|
@@ -132,7 +197,7 @@ export const colWidthsForRow = tr => {
|
|
|
132
197
|
// reverse engineer cell widths from table widths
|
|
133
198
|
const domBasedCellWidths = [];
|
|
134
199
|
cellInfos.map(cell => {
|
|
135
|
-
domBasedCellWidths.push(...getRelativeDomCellWidths(cell));
|
|
200
|
+
domBasedCellWidths.push(...(useColwidthRatios ? getRelativeDomCellWidths(cell) : new Array(cell.colspan).fill(cell.width / cell.colspan)));
|
|
136
201
|
});
|
|
137
202
|
if (cellInfos.reduce((acc, cell) => acc + cell.width, 0) !== 0) {
|
|
138
203
|
const newWidths = mapTableColwidthsToRow(rowColSpans, domBasedCellWidths);
|
|
@@ -146,6 +211,53 @@ export const colWidthsForRow = tr => {
|
|
|
146
211
|
const pctWidths = rowColSpans.map(cellColSpan => cellColSpan / visualColCount * 100);
|
|
147
212
|
return pctWidths.map(pct => `${pct}%`).join(' ');
|
|
148
213
|
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Returns the visual column index that the mouse pointer is over within a column-spanned
|
|
217
|
+
* cell, by walking the per-column boundaries of the spanned cell and finding the column whose
|
|
218
|
+
* horizontal range contains `mouseEvent.clientX`.
|
|
219
|
+
*
|
|
220
|
+
* `<td>.cellIndex` for a cell with `colspan > 1` only resolves to the first column the cell
|
|
221
|
+
* occupies, so hovering anywhere inside a colspanned first-row cell pins the column drag handle
|
|
222
|
+
* to that first column. This resolver disambiguates which visual column the pointer is actually
|
|
223
|
+
* over so the handle/menu can target a single column.
|
|
224
|
+
*
|
|
225
|
+
* The search is restricted to columns in `[startIndex, endIndex)` (the cell's own span). The
|
|
226
|
+
* spanned cell's bounding rect is read once and split into per-column boundaries using the cell's
|
|
227
|
+
* `data-colwidth` ratios (falling back to an even split for unresized columns) — this keeps the
|
|
228
|
+
* work bounded by the colspan size and avoids measuring the whole table.
|
|
229
|
+
*
|
|
230
|
+
* Returns `undefined` when the mouse is outside the spanned range (so callers can fall back to
|
|
231
|
+
* the converted HTML column index).
|
|
232
|
+
*/
|
|
233
|
+
export const getColumnIndexByMousePosition = (cellElement, mouseEvent, columnIndexRange) => {
|
|
234
|
+
var _cellElement$dataset$;
|
|
235
|
+
const {
|
|
236
|
+
startIndex,
|
|
237
|
+
endIndex
|
|
238
|
+
} = columnIndexRange;
|
|
239
|
+
const columnCount = endIndex - startIndex;
|
|
240
|
+
if (columnCount <= 0) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const cellRect = cellElement.getBoundingClientRect();
|
|
244
|
+
if (mouseEvent.clientX < cellRect.left || mouseEvent.clientX >= cellRect.right) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Read DOM ratios here to avoid resolving the ProseMirror node on this mouse-move path.
|
|
249
|
+
const ratios = (_cellElement$dataset$ = cellElement.dataset.colwidth) === null || _cellElement$dataset$ === void 0 ? void 0 : _cellElement$dataset$.split(',').map(Number).filter(value => !Number.isNaN(value));
|
|
250
|
+
const perColumnWidths = getProportionalColumnWidths(cellRect.width, columnCount, ratios);
|
|
251
|
+
const offsetFromLeft = mouseEvent.clientX - cellRect.left;
|
|
252
|
+
let cumulativeWidth = 0;
|
|
253
|
+
for (let column = 0; column < columnCount; column++) {
|
|
254
|
+
cumulativeWidth += perColumnWidths[column];
|
|
255
|
+
if (offsetFromLeft < cumulativeWidth) {
|
|
256
|
+
return startIndex + column;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return endIndex - 1;
|
|
260
|
+
};
|
|
149
261
|
export const convertHTMLCellIndexToColumnIndex = (htmlColIndex, htmlRowIndex, tableMap) => {
|
|
150
262
|
// Same numbers (positions) in tableMap.map array mean that there are merged cells.
|
|
151
263
|
// Cells can be merged across columns. So we need to check if the cell on the left and current cell have the same value.
|
|
@@ -6,6 +6,25 @@ export const isCell = node => {
|
|
|
6
6
|
export const isCornerButton = node => containsClassName(node, ClassName.CONTROLS_CORNER_BUTTON);
|
|
7
7
|
export const isInsertRowButton = node => containsClassName(node, ClassName.CONTROLS_INSERT_ROW) || closestElement(node, `.${ClassName.CONTROLS_INSERT_ROW}`) || containsClassName(node, ClassName.CONTROLS_BUTTON_OVERLAY) && closestElement(node, `.${ClassName.ROW_CONTROLS}`);
|
|
8
8
|
export const getColumnOrRowIndex = target => [parseInt(target.getAttribute('data-start-index') || '-1', 10), parseInt(target.getAttribute('data-end-index') || '-1', 10)];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the element that carries the `data-start-index` / `data-end-index` attributes for a
|
|
12
|
+
* column/row control.
|
|
13
|
+
*
|
|
14
|
+
* The floating insert dot is a child of a wrapper that holds the index attributes, so when the
|
|
15
|
+
* pointer is directly over the dot the attributes are not on the event target. This walks up to
|
|
16
|
+
* the nearest ancestor that has `data-start-index`, falling back to the original element so the
|
|
17
|
+
* behaviour is unchanged when the target already carries the attributes.
|
|
18
|
+
*/
|
|
19
|
+
export const getIndexAttributeSourceElement = target => {
|
|
20
|
+
if (target.hasAttribute('data-start-index')) {
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
// Ignored via go/ees005
|
|
24
|
+
// eslint-disable-next-line @atlaskit/editor/no-as-casting
|
|
25
|
+
const closestWithIndex = target.closest('[data-start-index]');
|
|
26
|
+
return closestWithIndex !== null && closestWithIndex !== void 0 ? closestWithIndex : target;
|
|
27
|
+
};
|
|
9
28
|
export const isColumnControlsDecorations = node => containsClassName(node, ClassName.COLUMN_CONTROLS_DECORATIONS);
|
|
10
29
|
export const isRowControlsButton = node => containsClassName(node, ClassName.ROW_CONTROLS_BUTTON) || containsClassName(node, ClassName.NUMBERED_COLUMN_BUTTON);
|
|
11
30
|
export const isResizeHandleDecoration = node => containsClassName(node, ClassName.RESIZE_HANDLE_DECORATION);
|
|
@@ -74,6 +74,48 @@ export const getRowsParams = rowsHeights => {
|
|
|
74
74
|
}
|
|
75
75
|
return rows;
|
|
76
76
|
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns the visual row index that the mouse pointer is over, by walking the row heights
|
|
80
|
+
* inside `tbody` and finding the row whose vertical range contains `mouseEvent.clientY`.
|
|
81
|
+
*
|
|
82
|
+
* When `rowIndexRange` is provided, the search is restricted to rows in that range (the
|
|
83
|
+
* `endIndex` is exclusive). This is the hot path used on `mousemove` when hovering over a
|
|
84
|
+
* row-spanned cell — restricting the range to `[startIndex, endIndex)` keeps the number
|
|
85
|
+
* of forced layout reads bounded by the row-span size, not the table size.
|
|
86
|
+
*
|
|
87
|
+
* Returns `undefined` when the mouse is above the search range or below it (so callers can
|
|
88
|
+
* fall back to the HTML row index).
|
|
89
|
+
*/
|
|
90
|
+
export const getRowIndexByMousePosition = (tableRef, mouseEvent, rowIndexRange) => {
|
|
91
|
+
var _rowIndexRange$startI, _rowIndexRange$endInd;
|
|
92
|
+
const tableBody = tableRef.querySelector('tbody');
|
|
93
|
+
if (!tableBody) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const rows = tableBody.children;
|
|
97
|
+
const startIndex = (_rowIndexRange$startI = rowIndexRange === null || rowIndexRange === void 0 ? void 0 : rowIndexRange.startIndex) !== null && _rowIndexRange$startI !== void 0 ? _rowIndexRange$startI : 0;
|
|
98
|
+
const 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);
|
|
99
|
+
if (startIndex >= endIndex) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const firstRowRect = rows[startIndex].getBoundingClientRect();
|
|
103
|
+
if (mouseEvent.clientY < firstRowRect.top) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
let rowBottom = firstRowRect.bottom;
|
|
107
|
+
if (mouseEvent.clientY < rowBottom) {
|
|
108
|
+
return startIndex;
|
|
109
|
+
}
|
|
110
|
+
for (let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) {
|
|
111
|
+
const rowRect = rows[rowIndex].getBoundingClientRect();
|
|
112
|
+
rowBottom = rowRect.bottom;
|
|
113
|
+
if (mouseEvent.clientY < rowBottom) {
|
|
114
|
+
return rowIndex;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
};
|
|
77
119
|
export const getRowClassNames = (index, selection, hoveredRows = [], isInDanger, isResizing) => {
|
|
78
120
|
const classNames = [];
|
|
79
121
|
if (isRowSelected(index)(selection) || hoveredRows.indexOf(index) > -1 && !isResizing) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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 (
|
|
69
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 ===
|
|
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:
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|