Skip to content

Commit 1d5ef8d

Browse files
committed
Preserve table selection in editor modal
1 parent dab55ab commit 1d5ef8d

1 file changed

Lines changed: 102 additions & 11 deletions

File tree

components/RichTextEditor.tsx

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
$setSelection,
6767
} from 'lexical';
6868
import {
69+
$createTableSelection,
6970
$deleteTableColumn__EXPERIMENTAL,
7071
$deleteTableRow__EXPERIMENTAL,
7172
$getTableCellNodeFromLexicalNode,
@@ -155,6 +156,12 @@ type SelectionSnapshot =
155156
focusType: 'text' | 'element';
156157
}
157158
| { type: 'node'; keys: string[] }
159+
| {
160+
type: 'table';
161+
tableKey: string;
162+
anchorCellKey: string;
163+
focusCellKey: string;
164+
}
158165
| null;
159166

160167
const RICH_TEXT_THEME = {
@@ -504,6 +511,7 @@ const ToolbarPlugin: React.FC<{
504511
const [isInTable, setIsInTable] = useState(false);
505512
const [hasHeaderRow, setHasHeaderRow] = useState(false);
506513
const pendingLinkSelectionRef = useRef<SelectionSnapshot>(null);
514+
const pendingTableSelectionRef = useRef<SelectionSnapshot>(null);
507515
const closeLinkModal = useCallback(() => {
508516
setIsLinkModalOpen(false);
509517
}, []);
@@ -513,27 +521,43 @@ const ToolbarPlugin: React.FC<{
513521
}, [closeLinkModal]);
514522

515523
const restoreSelectionFromSnapshot = useCallback(
516-
(snapshot: SelectionSnapshot = pendingLinkSelectionRef.current) => {
517-
if (!snapshot) {
524+
(snapshot: SelectionSnapshot | null | undefined = pendingLinkSelectionRef.current) => {
525+
const snapshotToUse = snapshot ?? pendingLinkSelectionRef.current;
526+
527+
if (!snapshotToUse) {
518528
return null;
519529
}
520530

521-
if (snapshot.type === 'range') {
531+
if (snapshotToUse.type === 'table') {
532+
const selection = $createTableSelection();
533+
const tableNode = $getNodeByKey(snapshotToUse.tableKey);
534+
const anchorCell = $getNodeByKey(snapshotToUse.anchorCellKey);
535+
const focusCell = $getNodeByKey(snapshotToUse.focusCellKey);
536+
537+
if (!tableNode || !anchorCell || !focusCell) {
538+
return null;
539+
}
540+
541+
selection.set(snapshotToUse.tableKey, snapshotToUse.anchorCellKey, snapshotToUse.focusCellKey);
542+
return selection;
543+
}
544+
545+
if (snapshotToUse.type === 'range') {
522546
const selection = $createRangeSelection();
523-
const anchorNode = $getNodeByKey(snapshot.anchorKey);
524-
const focusNode = $getNodeByKey(snapshot.focusKey);
547+
const anchorNode = $getNodeByKey(snapshotToUse.anchorKey);
548+
const focusNode = $getNodeByKey(snapshotToUse.focusKey);
525549

526550
if (!anchorNode || !focusNode) {
527551
return null;
528552
}
529553

530-
selection.anchor.set(snapshot.anchorKey, snapshot.anchorOffset, snapshot.anchorType);
531-
selection.focus.set(snapshot.focusKey, snapshot.focusOffset, snapshot.focusType);
554+
selection.anchor.set(snapshotToUse.anchorKey, snapshotToUse.anchorOffset, snapshotToUse.anchorType);
555+
selection.focus.set(snapshotToUse.focusKey, snapshotToUse.focusOffset, snapshotToUse.focusType);
532556
return selection;
533557
}
534558

535559
const selection = $createNodeSelection();
536-
snapshot.keys.forEach(key => {
560+
snapshotToUse.keys.forEach(key => {
537561
const node = $getNodeByKey(key);
538562
if (node) {
539563
selection.add(node.getKey());
@@ -729,6 +753,54 @@ const ToolbarPlugin: React.FC<{
729753
return true;
730754
}, [editor]);
731755

756+
const captureTableSelection = useCallback(() => {
757+
let snapshot: SelectionSnapshot = null;
758+
759+
editor.getEditorState().read(() => {
760+
const selection = $getSelection();
761+
762+
if ($isTableSelection(selection)) {
763+
const anchorCell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
764+
const focusCell = $getTableCellNodeFromLexicalNode(selection.focus.getNode());
765+
766+
if (!anchorCell || !focusCell) {
767+
return;
768+
}
769+
770+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(anchorCell);
771+
snapshot = {
772+
type: 'table',
773+
tableKey: tableNode.getKey(),
774+
anchorCellKey: anchorCell.getKey(),
775+
focusCellKey: focusCell.getKey(),
776+
};
777+
return;
778+
}
779+
780+
if ($isRangeSelection(selection)) {
781+
const anchorCell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
782+
const focusCell = $getTableCellNodeFromLexicalNode(selection.focus.getNode());
783+
784+
if (!anchorCell || !focusCell) {
785+
return;
786+
}
787+
788+
snapshot = {
789+
type: 'range',
790+
anchorKey: selection.anchor.key,
791+
anchorOffset: selection.anchor.offset,
792+
anchorType: selection.anchor.type,
793+
focusKey: selection.focus.key,
794+
focusOffset: selection.focus.offset,
795+
focusType: selection.focus.type,
796+
};
797+
}
798+
});
799+
800+
pendingTableSelectionRef.current = snapshot;
801+
return snapshot !== null;
802+
}, [editor]);
803+
732804
const applyLink = useCallback(
733805
(url: string) => {
734806
closeLinkModal();
@@ -814,13 +886,31 @@ const ToolbarPlugin: React.FC<{
814886
);
815887

816888
const closeTableModal = useCallback(() => {
889+
pendingTableSelectionRef.current = null;
817890
setIsTableModalOpen(false);
818891
}, []);
819892

893+
const openTableModal = useCallback(() => {
894+
if (readOnly) {
895+
return;
896+
}
897+
898+
captureTableSelection();
899+
setIsTableModalOpen(true);
900+
}, [captureTableSelection, readOnly]);
901+
820902
const runWithActiveTable = useCallback(
821903
(action: (selection: RangeSelection | NodeSelection | TableSelection) => void) => {
822904
editor.update(() => {
823-
const selection = $getSelection();
905+
let selection = $getSelection();
906+
if (!selection || (!$isRangeSelection(selection) && !$isTableSelection(selection))) {
907+
const restoredSelection = restoreSelectionFromSnapshot(pendingTableSelectionRef.current);
908+
if (restoredSelection) {
909+
$setSelection(restoredSelection);
910+
selection = restoredSelection;
911+
}
912+
}
913+
824914
if (!selection || (!$isRangeSelection(selection) && !$isTableSelection(selection))) {
825915
return;
826916
}
@@ -831,7 +921,7 @@ const ToolbarPlugin: React.FC<{
831921
action(selection);
832922
});
833923
},
834-
[editor],
924+
[editor, restoreSelectionFromSnapshot],
835925
);
836926

837927
const insertTable = useCallback(
@@ -1090,7 +1180,7 @@ const ToolbarPlugin: React.FC<{
10901180
icon: TableIcon,
10911181
group: 'insert',
10921182
disabled: readOnly,
1093-
onClick: () => setIsTableModalOpen(true),
1183+
onClick: openTableModal,
10941184
},
10951185
{
10961186
id: 'image',
@@ -1183,6 +1273,7 @@ const ToolbarPlugin: React.FC<{
11831273
isStrikethrough,
11841274
isUnderline,
11851275
openImagePicker,
1276+
openTableModal,
11861277
readOnly,
11871278
toggleLink,
11881279
],

0 commit comments

Comments
 (0)