@blocklet/editor 2.2.46 → 2.3.0

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 (51) hide show
  1. package/lib/ext/PostLinkEmbedPlugin/PostLinkNode.d.ts +2 -2
  2. package/lib/ext/PostLinkEmbedPlugin/PostLinkNode.js +3 -3
  3. package/lib/main/editor.js +3 -7
  4. package/lib/main/index.css +16 -18
  5. package/lib/main/markdown-editor/editor.js +1 -1
  6. package/lib/main/markdown-editor/transformers.js +7 -6
  7. package/lib/main/nodes/EmojiNode.d.ts +1 -2
  8. package/lib/main/nodes/EmojiNode.js +4 -9
  9. package/lib/main/nodes/EquationNode.js +14 -7
  10. package/lib/main/nodes/ImageComponent.js +2 -2
  11. package/lib/main/nodes/PlaygroundNodes.js +0 -2
  12. package/lib/main/nodes/StickyComponent.js +1 -1
  13. package/lib/main/plugins/CodeActionMenuPlugin/index.js +24 -28
  14. package/lib/main/plugins/MarkdownTransformers/index.d.ts +1 -4
  15. package/lib/main/plugins/MarkdownTransformers/index.js +48 -79
  16. package/lib/main/plugins/TableActionMenuPlugin/index.js +229 -171
  17. package/lib/main/plugins/TableCellResizer/index.css +8 -2
  18. package/lib/main/plugins/TableCellResizer/index.js +168 -120
  19. package/lib/main/plugins/TableOfContentsPlugin/index.js +2 -2
  20. package/lib/main/plugins/TablePlugin.d.ts +3 -4
  21. package/lib/main/plugins/TablePlugin.js +7 -24
  22. package/lib/main/plugins/ToolbarPlugin/index.js +0 -1
  23. package/lib/main/themes/defaultTheme.js +3 -1
  24. package/lib/main/ui/ContentEditable.d.ts +1 -4
  25. package/lib/main/ui/Modal.css +1 -1
  26. package/lib/main/ui/TextInput.d.ts +4 -3
  27. package/lib/main/ui/TextInput.js +3 -19
  28. package/package.json +20 -21
  29. package/lib/main/nodes/AutocompleteNode.d.ts +0 -34
  30. package/lib/main/nodes/AutocompleteNode.js +0 -52
  31. package/lib/main/nodes/EquationComponent.d.ts +0 -16
  32. package/lib/main/nodes/EquationComponent.js +0 -64
  33. package/lib/main/plugins/ActionsPlugin/index.d.ts +0 -11
  34. package/lib/main/plugins/ActionsPlugin/index.js +0 -129
  35. package/lib/main/plugins/AutocompletePlugin/index.d.ts +0 -10
  36. package/lib/main/plugins/AutocompletePlugin/index.js +0 -2461
  37. package/lib/main/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +0 -14
  38. package/lib/main/plugins/CodeActionMenuPlugin/components/PrettierButton/index.d.ts +0 -17
  39. package/lib/main/plugins/CodeActionMenuPlugin/components/PrettierButton/index.js +0 -95
  40. package/lib/main/plugins/EquationsPlugin/index.d.ts +0 -21
  41. package/lib/main/plugins/EquationsPlugin/index.js +0 -42
  42. package/lib/main/plugins/SpeechToTextPlugin/index.d.ts +0 -12
  43. package/lib/main/plugins/SpeechToTextPlugin/index.js +0 -87
  44. package/lib/main/ui/EquationEditor.css +0 -38
  45. package/lib/main/ui/EquationEditor.d.ts +0 -19
  46. package/lib/main/ui/EquationEditor.js +0 -26
  47. package/lib/main/ui/KatexEquationAlterer.css +0 -41
  48. package/lib/main/ui/KatexEquationAlterer.d.ts +0 -15
  49. package/lib/main/ui/KatexEquationAlterer.js +0 -34
  50. package/lib/main/ui/KatexRenderer.d.ts +0 -13
  51. package/lib/main/ui/KatexRenderer.js +0 -33
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import useLexicalEditable from '@lexical/react/useLexicalEditable';
4
- import { $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $getNodeTriplet, $getTableCellNodeFromLexicalNode, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $insertTableColumn__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL, $isTableCellNode, $isTableRowNode, $isTableSelection, $unmergeCell, getTableObserverFromTableElement, TableCellHeaderStates, TableCellNode, } from '@lexical/table';
5
- import { $createParagraphNode, $getRoot, $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection, $isTextNode, } from 'lexical';
3
+ import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
4
+ import { $computeTableMapSkipCellCheck, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $getNodeTriplet, $getTableCellNodeFromLexicalNode, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isTableCellNode, $isTableSelection, $mergeCells, $unmergeCell, getTableElement, getTableObserverFromTableElement, TableCellHeaderStates, TableCellNode, } from '@lexical/table';
5
+ import { mergeRegister } from '@lexical/utils';
6
+ import { $getSelection, $isElementNode, $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_CRITICAL, getDOMSelection, isDOMNode, SELECTION_CHANGE_COMMAND, } from 'lexical';
6
7
  import { useCallback, useEffect, useRef, useState } from 'react';
7
8
  import { createPortal } from 'react-dom';
8
- import invariant from '../../../shared/invariant';
9
+ import { Box } from '@mui/material';
9
10
  import useModal from '../../hooks/useModal';
10
11
  import ColorPicker from '../../ui/ColorPicker';
12
+ import DropDown, { DropDownItem } from '../../ui/DropDown';
11
13
  function computeSelectionCount(selection) {
12
14
  const selectionShape = selection.getShape();
13
15
  return {
@@ -15,41 +17,6 @@ function computeSelectionCount(selection) {
15
17
  rows: selectionShape.toY - selectionShape.fromY + 1,
16
18
  };
17
19
  }
18
- // This is important when merging cells as there is no good way to re-merge weird shapes (a result
19
- // of selecting merged cells and non-merged)
20
- function isTableSelectionRectangular(selection) {
21
- const nodes = selection.getNodes();
22
- const currentRows = [];
23
- let currentRow = null;
24
- let expectedColumns = null;
25
- let currentColumns = 0;
26
- for (let i = 0; i < nodes.length; i++) {
27
- const node = nodes[i];
28
- if ($isTableCellNode(node)) {
29
- const row = node.getParentOrThrow();
30
- invariant($isTableRowNode(row), 'Expected CellNode to have a RowNode parent');
31
- if (currentRow !== row) {
32
- if (expectedColumns !== null && currentColumns !== expectedColumns) {
33
- return false;
34
- }
35
- if (currentRow !== null) {
36
- expectedColumns = currentColumns;
37
- }
38
- currentRow = row;
39
- currentColumns = 0;
40
- }
41
- const colSpan = node.__colSpan;
42
- for (let j = 0; j < colSpan; j++) {
43
- if (currentRows[currentColumns + j] === undefined) {
44
- currentRows[currentColumns + j] = 0;
45
- }
46
- currentRows[currentColumns + j] += node.__rowSpan;
47
- }
48
- currentColumns += colSpan;
49
- }
50
- }
51
- return ((expectedColumns === null || currentColumns === expectedColumns) && currentRows.every((v) => v === currentRows[0]));
52
- }
53
20
  function $canUnmerge() {
54
21
  const selection = $getSelection();
55
22
  if (($isRangeSelection(selection) && !selection.isCollapsed()) ||
@@ -60,16 +27,6 @@ function $canUnmerge() {
60
27
  const [cell] = $getNodeTriplet(selection.anchor);
61
28
  return cell.__colSpan > 1 || cell.__rowSpan > 1;
62
29
  }
63
- function $cellContainsEmptyParagraph(cell) {
64
- if (cell.getChildrenSize() !== 1) {
65
- return false;
66
- }
67
- const firstChild = cell.getFirstChildOrThrow();
68
- if (!$isParagraphNode(firstChild) || !firstChild.isEmpty()) {
69
- return false;
70
- }
71
- return true;
72
- }
73
30
  function $selectLastDescendant(node) {
74
31
  const lastDescendant = node.getLastDescendant();
75
32
  if ($isTextNode(lastDescendant)) {
@@ -114,7 +71,7 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
114
71
  });
115
72
  setBackgroundColor(currentCellBackgroundColor(editor) || '');
116
73
  }
117
- });
74
+ }, { skipInitialization: true });
118
75
  }, [editor, tableCellNode]);
119
76
  useEffect(() => {
120
77
  editor.getEditorState().read(() => {
@@ -123,8 +80,7 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
123
80
  if ($isTableSelection(selection)) {
124
81
  const currentSelectionCounts = computeSelectionCount(selection);
125
82
  updateSelectionCounts(computeSelectionCount(selection));
126
- setCanMergeCells(isTableSelectionRectangular(selection) &&
127
- (currentSelectionCounts.columns > 1 || currentSelectionCounts.rows > 1));
83
+ setCanMergeCells(currentSelectionCounts.columns > 1 || currentSelectionCounts.rows > 1);
128
84
  }
129
85
  // Unmerge cell
130
86
  setCanUnmergeCell($canUnmerge());
@@ -150,15 +106,16 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
150
106
  let topPosition = menuButtonRect.top;
151
107
  if (topPosition + dropDownElementRect.height > window.innerHeight) {
152
108
  const position = menuButtonRect.bottom - dropDownElementRect.height;
153
- topPosition = (position < 0 ? margin : position) + window.pageYOffset;
109
+ topPosition = position < 0 ? margin : position;
154
110
  }
155
- dropDownElement.style.top = `${topPosition + +window.pageYOffset}px`;
111
+ dropDownElement.style.top = `${topPosition}px`;
156
112
  }
157
113
  }, [contextRef, dropDownRef, editor]);
158
114
  useEffect(() => {
159
115
  function handleClickOutside(event) {
160
116
  if (dropDownRef.current != null &&
161
117
  contextRef.current != null &&
118
+ isDOMNode(event.target) &&
162
119
  !dropDownRef.current.contains(event.target) &&
163
120
  !contextRef.current.contains(event.target)) {
164
121
  setIsMenuOpen(false);
@@ -171,56 +128,31 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
171
128
  editor.update(() => {
172
129
  if (tableCellNode.isAttached()) {
173
130
  const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
174
- const tableElement = editor.getElementByKey(tableNode.getKey());
175
- if (!tableElement) {
176
- throw new Error('Expected to find tableElement in DOM');
131
+ const tableElement = getTableElement(tableNode, editor.getElementByKey(tableNode.getKey()));
132
+ if (tableElement === null) {
133
+ throw new Error('TableActionMenu: Expected to find tableElement in DOM');
177
134
  }
178
- const tableSelection = getTableObserverFromTableElement(tableElement);
179
- if (tableSelection !== null) {
180
- tableSelection.clearHighlight();
135
+ const tableObserver = getTableObserverFromTableElement(tableElement);
136
+ if (tableObserver !== null) {
137
+ tableObserver.$clearHighlight();
181
138
  }
182
139
  tableNode.markDirty();
183
140
  updateTableCellNode(tableCellNode.getLatest());
184
141
  }
185
- const rootNode = $getRoot();
186
- rootNode.selectStart();
142
+ $setSelection(null);
187
143
  });
188
144
  }, [editor, tableCellNode]);
189
145
  const mergeTableCellsAtSelection = () => {
190
146
  editor.update(() => {
191
147
  const selection = $getSelection();
192
- if ($isTableSelection(selection)) {
193
- const { columns, rows } = computeSelectionCount(selection);
194
- const nodes = selection.getNodes();
195
- let firstCell = null;
196
- for (let i = 0; i < nodes.length; i++) {
197
- const node = nodes[i];
198
- if ($isTableCellNode(node)) {
199
- if (firstCell === null) {
200
- node.setColSpan(columns).setRowSpan(rows);
201
- firstCell = node;
202
- const isEmpty = $cellContainsEmptyParagraph(node);
203
- let firstChild;
204
- // eslint-disable-next-line no-cond-assign
205
- if (isEmpty && $isParagraphNode((firstChild = node.getFirstChild()))) {
206
- firstChild.remove();
207
- }
208
- }
209
- else if ($isTableCellNode(firstCell)) {
210
- const isEmpty = $cellContainsEmptyParagraph(node);
211
- if (!isEmpty) {
212
- firstCell.append(...node.getChildren());
213
- }
214
- node.remove();
215
- }
216
- }
217
- }
218
- if (firstCell !== null) {
219
- if (firstCell.getChildrenSize() === 0) {
220
- firstCell.append($createParagraphNode());
221
- }
222
- $selectLastDescendant(firstCell);
223
- }
148
+ if (!$isTableSelection(selection)) {
149
+ return;
150
+ }
151
+ const nodes = selection.getNodes();
152
+ const tableCells = nodes.filter($isTableCellNode);
153
+ const targetCell = $mergeCells(tableCells);
154
+ if (targetCell) {
155
+ $selectLastDescendant(targetCell);
224
156
  onClose();
225
157
  }
226
158
  });
@@ -232,21 +164,23 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
232
164
  };
233
165
  const insertTableRowAtSelection = useCallback((shouldInsertAfter) => {
234
166
  editor.update(() => {
235
- $insertTableRow__EXPERIMENTAL(shouldInsertAfter);
167
+ for (let i = 0; i < selectionCounts.rows; i++) {
168
+ $insertTableRowAtSelection(shouldInsertAfter);
169
+ }
236
170
  onClose();
237
171
  });
238
- }, [editor, onClose]);
172
+ }, [editor, onClose, selectionCounts.rows]);
239
173
  const insertTableColumnAtSelection = useCallback((shouldInsertAfter) => {
240
174
  editor.update(() => {
241
175
  for (let i = 0; i < selectionCounts.columns; i++) {
242
- $insertTableColumn__EXPERIMENTAL(shouldInsertAfter);
176
+ $insertTableColumnAtSelection(shouldInsertAfter);
243
177
  }
244
178
  onClose();
245
179
  });
246
180
  }, [editor, onClose, selectionCounts.columns]);
247
181
  const deleteTableRowAtSelection = useCallback(() => {
248
182
  editor.update(() => {
249
- $deleteTableRow__EXPERIMENTAL();
183
+ $deleteTableRowAtSelection();
250
184
  onClose();
251
185
  });
252
186
  }, [editor, onClose]);
@@ -260,7 +194,7 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
260
194
  }, [editor, tableCellNode, clearTableSelection, onClose]);
261
195
  const deleteTableColumnAtSelection = useCallback(() => {
262
196
  editor.update(() => {
263
- $deleteTableColumn__EXPERIMENTAL();
197
+ $deleteTableColumnAtSelection();
264
198
  onClose();
265
199
  });
266
200
  }, [editor, onClose]);
@@ -268,20 +202,20 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
268
202
  editor.update(() => {
269
203
  const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
270
204
  const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
271
- const tableRows = tableNode.getChildren();
272
- if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
273
- throw new Error('Expected table cell to be inside of table row.');
274
- }
275
- const tableRow = tableRows[tableRowIndex];
276
- if (!$isTableRowNode(tableRow)) {
277
- throw new Error('Expected table row');
278
- }
279
- tableRow.getChildren().forEach((tableCell) => {
280
- if (!$isTableCellNode(tableCell)) {
281
- throw new Error('Expected table cell');
205
+ const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
206
+ const rowCells = new Set();
207
+ const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.ROW;
208
+ for (let col = 0; col < gridMap[tableRowIndex].length; col++) {
209
+ const mapCell = gridMap[tableRowIndex][col];
210
+ if (!mapCell?.cell) {
211
+ // eslint-disable-next-line no-continue
212
+ continue;
213
+ }
214
+ if (!rowCells.has(mapCell.cell)) {
215
+ rowCells.add(mapCell.cell);
216
+ mapCell.cell.setHeaderStyles(newStyle, TableCellHeaderStates.ROW);
282
217
  }
283
- tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW);
284
- });
218
+ }
285
219
  clearTableSelection();
286
220
  onClose();
287
221
  });
@@ -290,27 +224,55 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
290
224
  editor.update(() => {
291
225
  const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
292
226
  const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode);
293
- const tableRows = tableNode.getChildren();
294
- const maxRowsLength = Math.max(...tableRows.map((row) => row.getChildren().length));
295
- if (tableColumnIndex >= maxRowsLength || tableColumnIndex < 0) {
296
- throw new Error('Expected table cell to be inside of table row.');
297
- }
298
- for (let r = 0; r < tableRows.length; r++) {
299
- const tableRow = tableRows[r];
300
- if (!$isTableRowNode(tableRow)) {
301
- throw new Error('Expected table row');
302
- }
303
- const tableCells = tableRow.getChildren();
304
- if (tableColumnIndex >= tableCells.length) {
305
- // if cell is outside of bounds for the current row (for example various merge cell cases) we shouldn't highlight it
227
+ const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
228
+ const columnCells = new Set();
229
+ const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.COLUMN;
230
+ for (let row = 0; row < gridMap.length; row++) {
231
+ const mapCell = gridMap[row][tableColumnIndex];
232
+ if (!mapCell?.cell) {
306
233
  // eslint-disable-next-line no-continue
307
234
  continue;
308
235
  }
309
- const tableCell = tableCells[tableColumnIndex];
310
- if (!$isTableCellNode(tableCell)) {
311
- throw new Error('Expected table cell');
236
+ if (!columnCells.has(mapCell.cell)) {
237
+ columnCells.add(mapCell.cell);
238
+ mapCell.cell.setHeaderStyles(newStyle, TableCellHeaderStates.COLUMN);
239
+ }
240
+ }
241
+ clearTableSelection();
242
+ onClose();
243
+ });
244
+ }, [editor, tableCellNode, clearTableSelection, onClose]);
245
+ const toggleRowStriping = useCallback(() => {
246
+ editor.update(() => {
247
+ if (tableCellNode.isAttached()) {
248
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
249
+ if (tableNode) {
250
+ tableNode.setRowStriping(!tableNode.getRowStriping());
251
+ }
252
+ }
253
+ clearTableSelection();
254
+ onClose();
255
+ });
256
+ }, [editor, tableCellNode, clearTableSelection, onClose]);
257
+ const toggleFirstRowFreeze = useCallback(() => {
258
+ editor.update(() => {
259
+ if (tableCellNode.isAttached()) {
260
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
261
+ if (tableNode) {
262
+ tableNode.setFrozenRows(tableNode.getFrozenRows() === 0 ? 1 : 0);
263
+ }
264
+ }
265
+ clearTableSelection();
266
+ onClose();
267
+ });
268
+ }, [editor, tableCellNode, clearTableSelection, onClose]);
269
+ const toggleFirstColumnFreeze = useCallback(() => {
270
+ editor.update(() => {
271
+ if (tableCellNode.isAttached()) {
272
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
273
+ if (tableNode) {
274
+ tableNode.setFrozenColumns(tableNode.getFrozenColumns() === 0 ? 1 : 0);
312
275
  }
313
- tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN);
314
276
  }
315
277
  clearTableSelection();
316
278
  onClose();
@@ -336,22 +298,46 @@ function TableActionMenu({ onClose, tableCellNode: _tableCellNode, setIsMenuOpen
336
298
  }
337
299
  });
338
300
  }, [editor]);
301
+ const formatVerticalAlign = (value) => {
302
+ editor.update(() => {
303
+ const selection = $getSelection();
304
+ if ($isRangeSelection(selection) || $isTableSelection(selection)) {
305
+ const [cell] = $getNodeTriplet(selection.anchor);
306
+ if ($isTableCellNode(cell)) {
307
+ cell.setVerticalAlign(value);
308
+ }
309
+ if ($isTableSelection(selection)) {
310
+ const nodes = selection.getNodes();
311
+ for (let i = 0; i < nodes.length; i++) {
312
+ const node = nodes[i];
313
+ if ($isTableCellNode(node)) {
314
+ node.setVerticalAlign(value);
315
+ }
316
+ }
317
+ }
318
+ }
319
+ });
320
+ };
339
321
  let mergeCellButton = null;
340
322
  if (cellMerge) {
341
323
  if (canMergeCells) {
342
- mergeCellButton = (_jsx("button", { type: "button", className: "item", onClick: () => mergeTableCellsAtSelection(), "data-test-id": "table-merge-cells", children: "Merge cells" }));
324
+ mergeCellButton = (_jsx("button", { type: "button", className: "item", onClick: () => mergeTableCellsAtSelection(), "data-test-id": "table-merge-cells", children: _jsx("span", { className: "text", children: "Merge cells" }) }));
343
325
  }
344
326
  else if (canUnmergeCell) {
345
- mergeCellButton = (_jsx("button", { type: "button", className: "item", onClick: () => unmergeTableCellsAtSelection(), "data-test-id": "table-unmerge-cells", children: "Unmerge cells" }));
327
+ mergeCellButton = (_jsx("button", { type: "button", className: "item", onClick: () => unmergeTableCellsAtSelection(), "data-test-id": "table-unmerge-cells", children: _jsx("span", { className: "text", children: "Unmerge cells" }) }));
346
328
  }
347
329
  }
348
330
  return createPortal(
349
331
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
350
332
  _jsxs("div", { className: "dropdown", ref: dropDownRef, onClick: (e) => {
351
333
  e.stopPropagation();
352
- }, children: [mergeCellButton, _jsx("button", { type: "button", className: "item", onClick: () => showColorPickerModal('Cell background color', () => (
353
- // @ts-ignore
354
- _jsx(ColorPicker, { color: backgroundColor, onChange: handleCellBackgroundColor }))), "data-test-id": "table-background-color", children: _jsx("span", { className: "text", children: "Background color" }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => insertTableRowAtSelection(false), "data-test-id": "table-insert-row-above", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`, " above"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => insertTableRowAtSelection(true), "data-test-id": "table-insert-row-below", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`, " below"] }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => insertTableColumnAtSelection(false), "data-test-id": "table-insert-column-before", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`, " left"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => insertTableColumnAtSelection(true), "data-test-id": "table-insert-column-after", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`, " right"] }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableColumnAtSelection(), "data-test-id": "table-delete-columns", children: _jsx("span", { className: "text", children: "Delete column" }) }), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableRowAtSelection(), "data-test-id": "table-delete-rows", children: _jsx("span", { className: "text", children: "Delete row" }) }), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableAtSelection(), "data-test-id": "table-delete", children: _jsx("span", { className: "text", children: "Delete table" }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => toggleTableRowIsHeader(), children: _jsxs("span", { className: "text", children: [(tableCellNode.__headerState & TableCellHeaderStates.ROW) === TableCellHeaderStates.ROW ? 'Remove' : 'Add', ' ', "row header"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => toggleTableColumnIsHeader(), "data-test-id": "table-column-header", children: _jsxs("span", { className: "text", children: [(tableCellNode.__headerState & TableCellHeaderStates.COLUMN) === TableCellHeaderStates.COLUMN
334
+ }, children: [mergeCellButton, _jsx("button", { type: "button", className: "item", onClick: () => showColorPickerModal('Cell background color', () => (_jsx(ColorPicker, { color: backgroundColor, onChange: handleCellBackgroundColor, buttonClassName: "" }))), "data-test-id": "table-background-color", children: _jsx("span", { className: "text", children: "Background color" }) }), _jsx("button", { type: "button", className: "item", onClick: () => toggleRowStriping(), "data-test-id": "table-row-striping", children: _jsx("span", { className: "text", children: "Toggle Row Striping" }) }), _jsxs(DropDown, { buttonLabel: "Vertical Align", buttonClassName: "item", buttonAriaLabel: "Formatting options for vertical alignment", children: [_jsx(DropDownItem, { onClick: () => {
335
+ formatVerticalAlign('top');
336
+ }, className: "item wide", children: _jsxs("div", { className: "icon-text-container", children: [_jsx("i", { className: "icon vertical-top" }), _jsx("span", { className: "text", children: "Top Align" })] }) }), _jsx(DropDownItem, { onClick: () => {
337
+ formatVerticalAlign('middle');
338
+ }, className: "item wide", children: _jsxs("div", { className: "icon-text-container", children: [_jsx("i", { className: "icon vertical-middle" }), _jsx("span", { className: "text", children: "Middle Align" })] }) }), _jsx(DropDownItem, { onClick: () => {
339
+ formatVerticalAlign('bottom');
340
+ }, className: "item wide", children: _jsxs("div", { className: "icon-text-container", children: [_jsx("i", { className: "icon vertical-bottom" }), _jsx("span", { className: "text", children: "Bottom Align" })] }) })] }), _jsx("button", { type: "button", className: "item", onClick: () => toggleFirstRowFreeze(), "data-test-id": "table-freeze-first-row", children: _jsx("span", { className: "text", children: "Toggle First Row Freeze" }) }), _jsx("button", { type: "button", className: "item", onClick: () => toggleFirstColumnFreeze(), "data-test-id": "table-freeze-first-column", children: _jsx("span", { className: "text", children: "Toggle First Column Freeze" }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => insertTableRowAtSelection(false), "data-test-id": "table-insert-row-above", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`, " above"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => insertTableRowAtSelection(true), "data-test-id": "table-insert-row-below", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`, " below"] }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => insertTableColumnAtSelection(false), "data-test-id": "table-insert-column-before", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`, " left"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => insertTableColumnAtSelection(true), "data-test-id": "table-insert-column-after", children: _jsxs("span", { className: "text", children: ["Insert ", selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`, " right"] }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableColumnAtSelection(), "data-test-id": "table-delete-columns", children: _jsx("span", { className: "text", children: "Delete column" }) }), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableRowAtSelection(), "data-test-id": "table-delete-rows", children: _jsx("span", { className: "text", children: "Delete row" }) }), _jsx("button", { type: "button", className: "item", onClick: () => deleteTableAtSelection(), "data-test-id": "table-delete", children: _jsx("span", { className: "text", children: "Delete table" }) }), _jsx("hr", {}), _jsx("button", { type: "button", className: "item", onClick: () => toggleTableRowIsHeader(), "data-test-id": "table-row-header", children: _jsxs("span", { className: "text", children: [(tableCellNode.__headerState & TableCellHeaderStates.ROW) === TableCellHeaderStates.ROW ? 'Remove' : 'Add', ' ', "row header"] }) }), _jsx("button", { type: "button", className: "item", onClick: () => toggleTableColumnIsHeader(), "data-test-id": "table-column-header", children: _jsxs("span", { className: "text", children: [(tableCellNode.__headerState & TableCellHeaderStates.COLUMN) === TableCellHeaderStates.COLUMN
355
341
  ? 'Remove'
356
342
  : 'Add', ' ', "column header"] }) })] }), document.body);
357
343
  }
@@ -362,62 +348,123 @@ function TableCellActionMenuContainer({ anchorElem, cellMerge, }) {
362
348
  const [isMenuOpen, setIsMenuOpen] = useState(false);
363
349
  const [tableCellNode, setTableMenuCellNode] = useState(null);
364
350
  const [colorPickerModal, showColorPickerModal] = useModal();
365
- const moveMenu = useCallback(() => {
351
+ const checkTableCellOverflow = useCallback((tableCellParentNodeDOM) => {
352
+ const scrollableContainer = tableCellParentNodeDOM.closest('.PlaygroundEditorTheme__tableScrollableWrapper');
353
+ if (scrollableContainer) {
354
+ const containerRect = scrollableContainer.getBoundingClientRect();
355
+ const cellRect = tableCellParentNodeDOM.getBoundingClientRect();
356
+ // Calculate where the action button would be positioned (5px from right edge of cell)
357
+ // Also account for the button width and table cell padding (8px)
358
+ const actionButtonRight = cellRect.right - 5;
359
+ const actionButtonLeft = actionButtonRight - 28; // 20px width + 8px padding
360
+ // Only hide if the action button would overflow the container
361
+ if (actionButtonRight > containerRect.right || actionButtonLeft < containerRect.left) {
362
+ return true;
363
+ }
364
+ }
365
+ return false;
366
+ }, []);
367
+ const $moveMenu = useCallback(() => {
366
368
  const menu = menuButtonRef.current;
367
369
  const selection = $getSelection();
368
- const nativeSelection = window.getSelection();
370
+ const nativeSelection = getDOMSelection(editor._window);
369
371
  const { activeElement } = document;
370
- if (selection == null || menu == null) {
372
+ function disable() {
373
+ if (menu) {
374
+ menu.classList.remove('table-cell-action-button-container--active');
375
+ menu.classList.add('table-cell-action-button-container--inactive');
376
+ }
371
377
  setTableMenuCellNode(null);
372
- return;
378
+ }
379
+ if (selection == null || menu == null) {
380
+ return disable();
373
381
  }
374
382
  const rootElement = editor.getRootElement();
383
+ let tableObserver = null;
384
+ let tableCellParentNodeDOM = null;
375
385
  if ($isRangeSelection(selection) &&
376
386
  rootElement !== null &&
377
387
  nativeSelection !== null &&
378
388
  rootElement.contains(nativeSelection.anchorNode)) {
379
389
  const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
380
390
  if (tableCellNodeFromSelection == null) {
381
- setTableMenuCellNode(null);
382
- return;
391
+ return disable();
383
392
  }
384
- const tableCellParentNodeDOM = editor.getElementByKey(tableCellNodeFromSelection.getKey());
385
- if (tableCellParentNodeDOM == null) {
386
- setTableMenuCellNode(null);
387
- return;
393
+ tableCellParentNodeDOM = editor.getElementByKey(tableCellNodeFromSelection.getKey());
394
+ if (tableCellParentNodeDOM == null || !tableCellNodeFromSelection.isAttached()) {
395
+ return disable();
396
+ }
397
+ if (checkTableCellOverflow(tableCellParentNodeDOM)) {
398
+ return disable();
399
+ }
400
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNodeFromSelection);
401
+ const tableElement = getTableElement(tableNode, editor.getElementByKey(tableNode.getKey()));
402
+ if (tableElement === null) {
403
+ throw new Error('TableActionMenu: Expected to find tableElement in DOM');
388
404
  }
405
+ tableObserver = getTableObserverFromTableElement(tableElement);
389
406
  setTableMenuCellNode(tableCellNodeFromSelection);
390
407
  }
408
+ else if ($isTableSelection(selection)) {
409
+ const anchorNode = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
410
+ if (!$isTableCellNode(anchorNode)) {
411
+ throw new Error('TableSelection anchorNode must be a TableCellNode');
412
+ }
413
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(anchorNode);
414
+ const tableElement = getTableElement(tableNode, editor.getElementByKey(tableNode.getKey()));
415
+ if (tableElement === null) {
416
+ throw new Error('TableActionMenu: Expected to find tableElement in DOM');
417
+ }
418
+ tableObserver = getTableObserverFromTableElement(tableElement);
419
+ tableCellParentNodeDOM = editor.getElementByKey(anchorNode.getKey());
420
+ if (tableCellParentNodeDOM === null) {
421
+ return disable();
422
+ }
423
+ if (checkTableCellOverflow(tableCellParentNodeDOM)) {
424
+ return disable();
425
+ }
426
+ }
391
427
  else if (!activeElement) {
392
- setTableMenuCellNode(null);
428
+ return disable();
393
429
  }
394
- }, [editor]);
395
- useEffect(() => {
396
- return editor.registerUpdateListener(() => {
397
- editor.getEditorState().read(() => {
398
- moveMenu();
399
- });
400
- });
401
- });
430
+ if (tableObserver === null || tableCellParentNodeDOM === null) {
431
+ return disable();
432
+ }
433
+ const enabled = !tableObserver || !tableObserver.isSelecting;
434
+ menu.classList.toggle('table-cell-action-button-container--active', enabled);
435
+ menu.classList.toggle('table-cell-action-button-container--inactive', !enabled);
436
+ if (enabled) {
437
+ const tableCellRect = tableCellParentNodeDOM.getBoundingClientRect();
438
+ const anchorRect = anchorElem.getBoundingClientRect();
439
+ const top = tableCellRect.top - anchorRect.top;
440
+ const left = tableCellRect.right - anchorRect.left;
441
+ menu.style.transform = `translate(${left}px, ${top}px)`;
442
+ }
443
+ }, [editor, anchorElem, checkTableCellOverflow]);
402
444
  useEffect(() => {
403
- const menuButtonDOM = menuButtonRef.current;
404
- if (menuButtonDOM != null && tableCellNode != null) {
405
- const tableCellNodeDOM = editor.getElementByKey(tableCellNode.getKey());
406
- if (tableCellNodeDOM != null) {
407
- const tableCellRect = tableCellNodeDOM.getBoundingClientRect();
408
- const menuRect = menuButtonDOM.getBoundingClientRect();
409
- const anchorRect = anchorElem.getBoundingClientRect();
410
- const top = tableCellRect.top - anchorRect.top + 4;
411
- const left = tableCellRect.right - menuRect.width - 10 - anchorRect.left;
412
- menuButtonDOM.style.opacity = '1';
413
- menuButtonDOM.style.transform = `translate(${left}px, ${top}px)`;
445
+ // We call the $moveMenu callback every time the selection changes,
446
+ // once up front, and once after each pointerUp
447
+ let timeoutId;
448
+ const callback = () => {
449
+ timeoutId = undefined;
450
+ editor.getEditorState().read($moveMenu);
451
+ };
452
+ const delayedCallback = () => {
453
+ if (timeoutId === undefined) {
454
+ timeoutId = setTimeout(callback, 0);
414
455
  }
415
- else {
416
- menuButtonDOM.style.opacity = '0';
417
- menuButtonDOM.style.transform = 'translate(-10000px, -10000px)';
456
+ return false;
457
+ };
458
+ return mergeRegister(editor.registerUpdateListener(delayedCallback), editor.registerCommand(SELECTION_CHANGE_COMMAND, delayedCallback, COMMAND_PRIORITY_CRITICAL), editor.registerRootListener((rootElement, prevRootElement) => {
459
+ if (prevRootElement) {
460
+ prevRootElement.removeEventListener('pointerup', delayedCallback);
418
461
  }
419
- }
420
- }, [menuButtonRef, tableCellNode, editor, anchorElem]);
462
+ if (rootElement) {
463
+ rootElement.addEventListener('pointerup', delayedCallback);
464
+ delayedCallback();
465
+ }
466
+ }), () => clearTimeout(timeoutId));
467
+ });
421
468
  const prevTableCellDOM = useRef(tableCellNode);
422
469
  useEffect(() => {
423
470
  if (prevTableCellDOM.current !== tableCellNode) {
@@ -425,10 +472,21 @@ function TableCellActionMenuContainer({ anchorElem, cellMerge, }) {
425
472
  }
426
473
  prevTableCellDOM.current = tableCellNode;
427
474
  }, [prevTableCellDOM, tableCellNode]);
428
- return (_jsx("div", { className: "table-cell-action-button-container", ref: menuButtonRef, children: tableCellNode != null && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "table-cell-action-button chevron-down", onClick: (e) => {
475
+ return (_jsx("div", { className: "table-cell-action-button-container", ref: menuButtonRef, children: tableCellNode != null && (_jsxs(_Fragment, { children: [_jsx(Box, { component: "button", type: "button", className: "table-cell-action-button chevron-down", onClick: (e) => {
429
476
  e.stopPropagation();
430
477
  setIsMenuOpen(!isMenuOpen);
431
- }, ref: menuRootRef, children: _jsx("i", { className: "iconify", "data-icon": "mdi:chevron-down" }) }), colorPickerModal, isMenuOpen && (_jsx(TableActionMenu, { contextRef: menuRootRef, setIsMenuOpen: setIsMenuOpen, onClose: () => setIsMenuOpen(false), tableCellNode: tableCellNode, cellMerge: cellMerge, showColorPickerModal: showColorPickerModal }))] })) }));
478
+ }, ref: menuRootRef, sx: {
479
+ display: 'flex',
480
+ alignItems: 'center',
481
+ justifyContent: 'center',
482
+ width: 22,
483
+ height: 22,
484
+ p: 0,
485
+ borderRadius: '100%',
486
+ '& svg': {
487
+ fontSize: 13,
488
+ },
489
+ }, children: _jsx("i", { className: "iconify", "data-icon": "tabler:chevron-down" }) }), colorPickerModal, isMenuOpen && (_jsx(TableActionMenu, { contextRef: menuRootRef, setIsMenuOpen: setIsMenuOpen, onClose: () => setIsMenuOpen(false), tableCellNode: tableCellNode, cellMerge: cellMerge, showColorPickerModal: showColorPickerModal }))] })) }));
432
490
  }
433
491
  export default function TableActionMenuPlugin({ anchorElem = document.body, cellMerge = false, }) {
434
492
  const isEditable = useLexicalEditable();
@@ -9,6 +9,12 @@
9
9
 
10
10
  .TableCellResizer__resizer {
11
11
  position: absolute;
12
- /* [!] table cell resizer 无法正常工作 */
13
- z-index: 1;
12
+ touch-action: none;
13
+ }
14
+
15
+ @media (pointer: coarse) {
16
+ .TableCellResizer__resizer {
17
+ background-color: #adf;
18
+ mix-blend-mode: color;
19
+ }
14
20
  }