diff --git a/packages/lexical-clipboard/src/clipboard.ts b/packages/lexical-clipboard/src/clipboard.ts index a005cda3332..c7b58f5d88c 100644 --- a/packages/lexical-clipboard/src/clipboard.ts +++ b/packages/lexical-clipboard/src/clipboard.ts @@ -12,13 +12,11 @@ import { $cloneWithProperties, $sliceSelectedTextNodeContent, } from '@lexical/selection'; -import {$findMatchingParent, objectKlassEquals} from '@lexical/utils'; +import {objectKlassEquals} from '@lexical/utils'; import { - $createParagraphNode, $createTabNode, $getRoot, $getSelection, - $INTERNAL_isPointSelection, $isElementNode, $isRangeSelection, $isTextNode, @@ -26,16 +24,10 @@ import { BaseSelection, COMMAND_PRIORITY_CRITICAL, COPY_COMMAND, - DEPRECATED_$isGridCellNode, - DEPRECATED_$isGridNode, - DEPRECATED_$isGridRowNode, - DEPRECATED_GridNode, - INTERNAL_PointSelection, isSelectionWithinEditor, - LexicalCommand, LexicalEditor, LexicalNode, - SELECTION_CHANGE_COMMAND, + SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SerializedElementNode, SerializedTextNode, } from 'lexical'; @@ -204,151 +196,17 @@ export function $insertGeneratedNodes( nodes: Array, selection: BaseSelection, ): void { - const isPointSelection = $INTERNAL_isPointSelection(selection); - const isRangeSelection = $isRangeSelection(selection); - const isSelectionInsideOfGrid = - (isRangeSelection && - $findMatchingParent(selection.anchor.getNode(), (n) => - DEPRECATED_$isGridCellNode(n), - ) !== null && - $findMatchingParent(selection.focus.getNode(), (n) => - DEPRECATED_$isGridCellNode(n), - ) !== null) || - (isPointSelection && !isRangeSelection); - if ( - isSelectionInsideOfGrid && - nodes.length === 1 && - DEPRECATED_$isGridNode(nodes[0]) + !editor.dispatchCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, { + nodes, + selection, + }) ) { - $mergeGridNodesStrategy(nodes, selection, false, editor); - return; + selection.insertNodes(nodes); } - - selection.insertNodes(nodes); return; } -function $mergeGridNodesStrategy( - nodes: LexicalNode[], - selection: INTERNAL_PointSelection, - isFromLexical: boolean, - editor: LexicalEditor, -) { - if (nodes.length !== 1 || !DEPRECATED_$isGridNode(nodes[0])) { - invariant(false, '$mergeGridNodesStrategy: Expected Grid insertion.'); - } - - const newGrid = nodes[0]; - const newGridRows = newGrid.getChildren(); - const newColumnCount = newGrid - .getFirstChildOrThrow() - .getChildrenSize(); - const newRowCount = newGrid.getChildrenSize(); - const gridCellNode = $findMatchingParent(selection.anchor.getNode(), (n) => - DEPRECATED_$isGridCellNode(n), - ); - const gridRowNode = - gridCellNode && - $findMatchingParent(gridCellNode, (n) => DEPRECATED_$isGridRowNode(n)); - const gridNode = - gridRowNode && - $findMatchingParent(gridRowNode, (n) => DEPRECATED_$isGridNode(n)); - - if ( - !DEPRECATED_$isGridCellNode(gridCellNode) || - !DEPRECATED_$isGridRowNode(gridRowNode) || - !DEPRECATED_$isGridNode(gridNode) - ) { - invariant( - false, - '$mergeGridNodesStrategy: Expected selection to be inside of a Grid.', - ); - } - - const startY = gridRowNode.getIndexWithinParent(); - const stopY = Math.min( - gridNode.getChildrenSize() - 1, - startY + newRowCount - 1, - ); - const startX = gridCellNode.getIndexWithinParent(); - const stopX = Math.min( - gridRowNode.getChildrenSize() - 1, - startX + newColumnCount - 1, - ); - const fromX = Math.min(startX, stopX); - const fromY = Math.min(startY, stopY); - const toX = Math.max(startX, stopX); - const toY = Math.max(startY, stopY); - const gridRowNodes = gridNode.getChildren(); - let newRowIdx = 0; - let newAnchorCellKey; - let newFocusCellKey; - - for (let r = fromY; r <= toY; r++) { - const currentGridRowNode = gridRowNodes[r]; - - if (!DEPRECATED_$isGridRowNode(currentGridRowNode)) { - invariant(false, 'getNodes: expected to find GridRowNode'); - } - - const newGridRowNode = newGridRows[newRowIdx]; - - if (!DEPRECATED_$isGridRowNode(newGridRowNode)) { - invariant(false, 'getNodes: expected to find GridRowNode'); - } - - const gridCellNodes = currentGridRowNode.getChildren(); - const newGridCellNodes = newGridRowNode.getChildren(); - let newColumnIdx = 0; - - for (let c = fromX; c <= toX; c++) { - const currentGridCellNode = gridCellNodes[c]; - - if (!DEPRECATED_$isGridCellNode(currentGridCellNode)) { - invariant(false, 'getNodes: expected to find GridCellNode'); - } - - const newGridCellNode = newGridCellNodes[newColumnIdx]; - - if (!DEPRECATED_$isGridCellNode(newGridCellNode)) { - invariant(false, 'getNodes: expected to find GridCellNode'); - } - - if (r === fromY && c === fromX) { - newAnchorCellKey = currentGridCellNode.getKey(); - } else if (r === toY && c === toX) { - newFocusCellKey = currentGridCellNode.getKey(); - } - - const originalChildren = currentGridCellNode.getChildren(); - newGridCellNode.getChildren().forEach((child) => { - if ($isTextNode(child)) { - const paragraphNode = $createParagraphNode(); - paragraphNode.append(child); - currentGridCellNode.append(child); - } else { - currentGridCellNode.append(child); - } - }); - originalChildren.forEach((n) => n.remove()); - newColumnIdx++; - } - - newRowIdx++; - } - - if (newAnchorCellKey && newFocusCellKey) { - editor.dispatchCommand< - LexicalCommand<{node: LexicalNode; anchorKey: string; focusKey: string}> - >(SELECTION_CHANGE_COMMAND, { - anchorKey: newAnchorCellKey, - focusKey: newFocusCellKey, - node: gridNode, - }); - } -} - export interface BaseSerializedNode { children?: Array; type: string; diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts index a277c5cc7c4..139416c1451 100644 --- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts +++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts @@ -12,6 +12,7 @@ import type {TableNode} from './LexicalTableNode'; import type {Cell, Cells, Grid} from './LexicalTableSelection'; import type { BaseSelection, + DEPRECATED_GridNode, LexicalCommand, LexicalEditor, LexicalNode, @@ -25,8 +26,10 @@ import { $getNearestNodeFromDOMNode, $getPreviousSelection, $getSelection, + $INTERNAL_isPointSelection, $isElementNode, $isRangeSelection, + $isTextNode, $setSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, @@ -34,7 +37,9 @@ import { DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, + DEPRECATED_$isGridCellNode, DEPRECATED_$isGridNode, + DEPRECATED_$isGridRowNode, FOCUS_COMMAND, FORMAT_TEXT_COMMAND, KEY_ARROW_DOWN_COMMAND, @@ -46,6 +51,7 @@ import { KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND, SELECTION_CHANGE_COMMAND, + SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, } from 'lexical'; import invariant from 'shared/invariant'; @@ -461,34 +467,147 @@ export function applyTableHandlers( } tableSelection.listenersToRemove.add( - editor.registerCommand<{ - node: LexicalNode; - anchorKey: string; - focusKey: string; - }>( - SELECTION_CHANGE_COMMAND, - ( - selectionPayload: { - node: LexicalNode; - anchorKey: string; - focusKey: string; - } | null, - ) => { + editor.registerCommand( + SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, + (selectionPayload) => { + const {nodes, selection} = selectionPayload; + const isPointSelection = $INTERNAL_isPointSelection(selection); + const isRangeSelection = $isRangeSelection(selection); + const isSelectionInsideOfGrid = + (isRangeSelection && + $findMatchingParent(selection.anchor.getNode(), (n) => + DEPRECATED_$isGridCellNode(n), + ) !== null && + $findMatchingParent(selection.focus.getNode(), (n) => + DEPRECATED_$isGridCellNode(n), + ) !== null) || + (isPointSelection && !isRangeSelection); + if ( - selectionPayload != null && - DEPRECATED_$isGridNode(selectionPayload.node) && - selectionPayload.anchorKey && - selectionPayload.focusKey + nodes.length !== 1 || + !DEPRECATED_$isGridNode(nodes[0]) || + !isSelectionInsideOfGrid ) { + return false; + } + const {anchor} = selection; + + const newGrid = nodes[0]; + const newGridRows = newGrid.getChildren(); + const newColumnCount = newGrid + .getFirstChildOrThrow() + .getChildrenSize(); + const newRowCount = newGrid.getChildrenSize(); + const gridCellNode = $findMatchingParent(anchor.getNode(), (n) => + DEPRECATED_$isGridCellNode(n), + ); + const gridRowNode = + gridCellNode && + $findMatchingParent(gridCellNode, (n) => + DEPRECATED_$isGridRowNode(n), + ); + const gridNode = + gridRowNode && + $findMatchingParent(gridRowNode, (n) => DEPRECATED_$isGridNode(n)); + + if ( + !DEPRECATED_$isGridCellNode(gridCellNode) || + !DEPRECATED_$isGridRowNode(gridRowNode) || + !DEPRECATED_$isGridNode(gridNode) + ) { + return false; + } + + const startY = gridRowNode.getIndexWithinParent(); + const stopY = Math.min( + gridNode.getChildrenSize() - 1, + startY + newRowCount - 1, + ); + const startX = gridCellNode.getIndexWithinParent(); + const stopX = Math.min( + gridRowNode.getChildrenSize() - 1, + startX + newColumnCount - 1, + ); + const fromX = Math.min(startX, stopX); + const fromY = Math.min(startY, stopY); + const toX = Math.max(startX, stopX); + const toY = Math.max(startY, stopY); + const gridRowNodes = gridNode.getChildren(); + let newRowIdx = 0; + let newAnchorCellKey; + let newFocusCellKey; + + for (let r = fromY; r <= toY; r++) { + const currentGridRowNode = gridRowNodes[r]; + + if (!DEPRECATED_$isGridRowNode(currentGridRowNode)) { + return false; + } + + const newGridRowNode = newGridRows[newRowIdx]; + + if (!DEPRECATED_$isGridRowNode(newGridRowNode)) { + return false; + } + + const gridCellNodes = currentGridRowNode.getChildren(); + const newGridCellNodes = newGridRowNode.getChildren(); + let newColumnIdx = 0; + + for (let c = fromX; c <= toX; c++) { + const currentGridCellNode = gridCellNodes[c]; + + if (!DEPRECATED_$isGridCellNode(currentGridCellNode)) { + return false; + } + + const newGridCellNode = newGridCellNodes[newColumnIdx]; + + if (!DEPRECATED_$isGridCellNode(newGridCellNode)) { + return false; + } + + if (r === fromY && c === fromX) { + newAnchorCellKey = currentGridCellNode.getKey(); + } else if (r === toY && c === toX) { + newFocusCellKey = currentGridCellNode.getKey(); + } + + const originalChildren = currentGridCellNode.getChildren(); + newGridCellNode.getChildren().forEach((child) => { + if ($isTextNode(child)) { + const paragraphNode = $createParagraphNode(); + paragraphNode.append(child); + currentGridCellNode.append(child); + } else { + currentGridCellNode.append(child); + } + }); + originalChildren.forEach((n) => n.remove()); + newColumnIdx++; + } + + newRowIdx++; + } + if (newAnchorCellKey && newFocusCellKey) { const newGridSelection = DEPRECATED_$createGridSelection(); newGridSelection.set( - selectionPayload.node.getKey(), - selectionPayload.anchorKey, - selectionPayload.focusKey, + nodes[0].getKey(), + newAnchorCellKey, + newFocusCellKey, ); $setSelection(newGridSelection); } + return true; + }, + COMMAND_PRIORITY_CRITICAL, + ), + ); + tableSelection.listenersToRemove.add( + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + () => { const selection = $getSelection(); const prevSelection = $getPreviousSelection(); diff --git a/packages/lexical/src/LexicalCommands.ts b/packages/lexical/src/LexicalCommands.ts index 45a3722c336..0f1c0a5d31e 100644 --- a/packages/lexical/src/LexicalCommands.ts +++ b/packages/lexical/src/LexicalCommands.ts @@ -6,7 +6,13 @@ * */ -import type {ElementFormatType, LexicalCommand, TextFormatType} from 'lexical'; +import type { + BaseSelection, + ElementFormatType, + LexicalCommand, + LexicalNode, + TextFormatType, +} from 'lexical'; export type PasteCommandType = ClipboardEvent | InputEvent | KeyboardEvent; @@ -17,6 +23,10 @@ export function createCommand(type?: string): LexicalCommand { export const SELECTION_CHANGE_COMMAND: LexicalCommand = createCommand( 'SELECTION_CHANGE_COMMAND', ); +export const SELECTION_INSERT_CLIPBOARD_NODES_COMMAND: LexicalCommand<{ + nodes: Array; + selection: BaseSelection; +}> = createCommand('SELECTION_INSERT_CLIPBOARD_NODES_COMMAND'); export const CLICK_COMMAND: LexicalCommand = createCommand('CLICK_COMMAND'); export const DELETE_CHARACTER_COMMAND: LexicalCommand = createCommand( diff --git a/packages/lexical/src/index.ts b/packages/lexical/src/index.ts index 2132a36d09c..12c1793cb99 100644 --- a/packages/lexical/src/index.ts +++ b/packages/lexical/src/index.ts @@ -109,6 +109,7 @@ export { REMOVE_TEXT_COMMAND, SELECT_ALL_COMMAND, SELECTION_CHANGE_COMMAND, + SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, UNDO_COMMAND, } from './LexicalCommands'; export {