diff --git a/packages/lexical-selection/README.md b/packages/lexical-selection/README.md index 95720e7c559..e989293c910 100644 --- a/packages/lexical-selection/README.md +++ b/packages/lexical-selection/README.md @@ -4,20 +4,6 @@ This package contains selection helpers for Lexical. ### Methods -#### `$cloneContents` - -Clones the Lexical nodes in the selection, returning a map of Key -> LexicalNode and a list containing the keys -of all direct children of the RootNode. Useful for insertion/transfer operations, such as copy and paste. - -```ts -export function $cloneContents( - selection: RangeSelection | NodeSelection | GridSelection, -): { - nodeMap: Array<[NodeKey, LexicalNode]>; - range: Array; -}; -``` - #### `getStyleObjectFromCSS` Given a CSS string, returns an object from the style cache. diff --git a/packages/lexical-selection/flow/LexicalSelection.js.flow b/packages/lexical-selection/flow/LexicalSelection.js.flow index 52599abe0da..76975697c31 100644 --- a/packages/lexical-selection/flow/LexicalSelection.js.flow +++ b/packages/lexical-selection/flow/LexicalSelection.js.flow @@ -16,12 +16,6 @@ import type { Point, RangeSelection, } from 'lexical'; -declare export function $cloneContents( - selection: RangeSelection | NodeSelection | GridSelection, -): { - nodeMap: Array<[NodeKey, T]>, - range: Array, -}; declare export function $cloneWithProperties(node: T): T; declare export function getStyleObjectFromCSS(css: string): { [string]: string, diff --git a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts index 398a9058202..c51b4fdbfa9 100644 --- a/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts +++ b/packages/lexical-selection/src/__tests__/unit/LexicalSelectionHelpers.test.ts @@ -8,7 +8,7 @@ import {$createLinkNode} from '@lexical/link'; import {$createHeadingNode} from '@lexical/rich-text'; -import {$cloneContents, $patchStyleText} from '@lexical/selection'; +import {$patchStyleText} from '@lexical/selection'; import { $createParagraphNode, $createTextNode, @@ -22,8 +22,6 @@ import { } from 'lexical'; import { $createTestDecoratorNode, - $createTestElementNode, - $createTestExcludeFromCopyElementNode, createTestEditor, TestDecoratorNode, } from 'lexical/src/__tests__/utils'; @@ -241,17 +239,6 @@ describe('LexicalSelectionHelpers tests', () => { setupTestCase((selection, state) => { expect(selection.extract()).toEqual([$getNodeByKey('a')]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['a', {...$getNodeByKey('a'), __text: ''}], - [element.getKey(), {...element, __children: ['a']}], - ], - range: [element.getKey()], - }); - }); }); test('Has correct text point after removal after merge', async () => { @@ -831,14 +818,6 @@ describe('LexicalSelectionHelpers tests', () => { setupTestCase((selection, element) => { expect(selection.extract()).toEqual([element]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [[element.getKey(), element]], - range: [element.getKey()], - }); - }); }); test('Can handle a start element point', () => { @@ -991,17 +970,6 @@ describe('LexicalSelectionHelpers tests', () => { setupTestCase((selection, element) => { expect(selection.extract()).toEqual([$getNodeByKey('a')]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['a', {...$getNodeByKey('a'), __text: ''}], - [element.getKey(), {...element, __children: ['a']}], - ], - range: [element.getKey()], - }); - }); }); test('Can handle an end element point', () => { @@ -1154,17 +1122,6 @@ describe('LexicalSelectionHelpers tests', () => { setupTestCase((selection, element) => { expect(selection.extract()).toEqual([$getNodeByKey('c')]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['c', {...$getNodeByKey('c'), __text: ''}], - [element.getKey(), {...element, __children: ['c']}], - ], - range: [element.getKey()], - }); - }); }); test('Has correct element point after merge from middle', async () => { @@ -1481,18 +1438,6 @@ describe('LexicalSelectionHelpers tests', () => { setupTestCase((selection, state) => { expect(selection.extract()).toEqual([{...$getNodeByKey('a')}]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['a', $getNodeByKey('a')], - [element.getKey(), {...element, __children: ['a', 'b']}], - ['b', {...$getNodeByKey('b'), __text: ''}], - ], - range: [element.getKey()], - }); - }); }); test('Can handle multiple element points', () => { @@ -1649,18 +1594,6 @@ describe('LexicalSelectionHelpers tests', () => { expect(selection.extract()).toEqual([firstChild]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['a', $getNodeByKey('a')], - [element.getKey(), {...element, __children: ['a', 'b']}], - ['b', {...$getNodeByKey('b'), __text: ''}], - ], - range: [element.getKey()], - }); - }); }); test('Can handle a mix of text and element points', () => { @@ -1825,199 +1758,6 @@ describe('LexicalSelectionHelpers tests', () => { $getNodeByKey('c'), ]); }); - - // cloneContents - setupTestCase((selection, element) => { - expect($cloneContents(selection)).toEqual({ - nodeMap: [ - ['a', $getNodeByKey('a')], - [element.getKey(), element], - ['b', $getNodeByKey('b')], - ['c', $getNodeByKey('c')], - ], - range: [element.getKey()], - }); - }); - }); - }); - - test('range with multiple paragraphs', async () => { - const editor = createTestEditor(); - - const element = document.createElement('div'); - - editor.setRootElement(element); - - await editor.update(() => { - const root = $getRoot(); - - const paragraph1 = $createParagraphNode(); - const paragraph2 = $createParagraphNode(); - const paragraph3 = $createParagraphNode(); - - const text1 = $createTextNode('First'); - const text2 = $createTextNode('Second'); - const text3 = $createTextNode('Third'); - - root.append(paragraph1, paragraph2, paragraph3); - - paragraph1.append(text1); - paragraph2.append(text2); - paragraph3.append(text3); - text1.select(0, 0); - const selection1 = $getSelection(); - - if ($isNodeSelection(selection1)) { - return; - } - - selection1.focus.set(text3.getKey(), 1, 'text'); - - const selectedNodes1 = $cloneContents($getSelection()); - - expect(selectedNodes1.range).toEqual([ - paragraph1.getKey(), - paragraph2.getKey(), - paragraph3.getKey(), - ]); - - expect(selectedNodes1.nodeMap[0][0]).toEqual(text1.getKey()); - expect((selectedNodes1.nodeMap[0][1] as TextNode).__text).toBe('First'); - expect(selectedNodes1.nodeMap[1][0]).toEqual(paragraph1.getKey()); - expect(selectedNodes1.nodeMap[2][0]).toEqual(paragraph2.getKey()); - expect(selectedNodes1.nodeMap[3][0]).toEqual(text2.getKey()); - expect(selectedNodes1.nodeMap[4][0]).toEqual(paragraph3.getKey()); - expect(selectedNodes1.nodeMap[5][0]).toEqual(text3.getKey()); - expect((selectedNodes1.nodeMap[5][1] as TextNode).__text).toBe('T'); - - expect(() => selectedNodes1.nodeMap[5][1].getTextContent()).toThrow(); - text1.select(1, 1); - const selection2 = $getSelection(); - - if ($isNodeSelection(selection2)) { - return; - } - - selection2.focus.set(text3.getKey(), 4, 'text'); - - const selectedNodes2 = $cloneContents($getSelection()); - - expect(selectedNodes2.range).toEqual([ - paragraph1.getKey(), - paragraph2.getKey(), - paragraph3.getKey(), - ]); - expect(selectedNodes2.nodeMap[0][0]).toEqual(text1.getKey()); - expect((selectedNodes2.nodeMap[0][1] as TextNode).__text).toBe('irst'); - expect(selectedNodes2.nodeMap[1][0]).toEqual(paragraph1.getKey()); - expect(selectedNodes2.nodeMap[2][0]).toEqual(paragraph2.getKey()); - expect(selectedNodes2.nodeMap[3][0]).toEqual(text2.getKey()); - expect(selectedNodes2.nodeMap[4][0]).toEqual(paragraph3.getKey()); - expect(selectedNodes2.nodeMap[5][0]).toEqual(text3.getKey()); - expect((selectedNodes2.nodeMap[5][1] as TextNode).__text).toBe('Thir'); - }); - }); - - test('range with excludeFromCopy nodes', async () => { - const editor = createTestEditor(); - - const element = document.createElement('div'); - - editor.setRootElement(element); - - await editor.update(() => { - const root = $getRoot(); - - const paragraph = $createParagraphNode(); - - root.append(paragraph); - - const excludeElementNode1 = $createTestExcludeFromCopyElementNode(); - - paragraph.append(excludeElementNode1); - - paragraph.select(0, 0); - - const selectedNodes1 = $cloneContents($getSelection()); - - expect(selectedNodes1.range).toEqual([]); - - const text1 = $createTextNode('1'); - - excludeElementNode1.append(text1); - - excludeElementNode1.select(0, 0); - - const selectedNodes2 = $cloneContents($getSelection()); - - expect(selectedNodes2.range).toEqual([paragraph.getKey()]); - - paragraph.select(0, 0); - - const selectedNodes3 = $cloneContents($getSelection()); - - expect(selectedNodes3.range).toEqual([paragraph.getKey()]); - - const text2 = $createTextNode('2'); - - excludeElementNode1.insertAfter(text2); - - paragraph.select(0, 2); - - const selectedNodes4 = $cloneContents($getSelection()); - - expect(selectedNodes4.range).toEqual([paragraph.getKey()]); - expect(selectedNodes4.nodeMap[0][0]).toEqual(text1.getKey()); - expect(selectedNodes4.nodeMap[1][0]).toEqual(paragraph.getKey()); - expect(selectedNodes4.nodeMap[2][0]).toEqual(text2.getKey()); - - const text3 = $createTextNode('3'); - - excludeElementNode1.append(text3); - - paragraph.select(0, 2); - - const selectedNodes5 = $cloneContents($getSelection()); - - expect(selectedNodes5.range).toEqual([paragraph.getKey()]); - expect(selectedNodes5.nodeMap[0][0]).toEqual(text1.getKey()); - expect(selectedNodes5.nodeMap[1][0]).toEqual(paragraph.getKey()); - expect(selectedNodes5.nodeMap[2][0]).toEqual(text3.getKey()); - expect(selectedNodes5.nodeMap[3][0]).toEqual(text2.getKey()); - - const testElementNode = $createTestElementNode(); - const excludeElementNode2 = $createTestExcludeFromCopyElementNode(); - const text4 = $createTextNode('4'); - - text1.insertBefore(testElementNode); - - testElementNode.append(excludeElementNode2); - excludeElementNode2.append(text4); - - paragraph.select(0, 3); - - const selectedNodes6 = $cloneContents($getSelection()); - - expect(selectedNodes6.range).toEqual([paragraph.getKey()]); - expect(selectedNodes6.nodeMap[0][0]).toEqual(text4.getKey()); - expect(selectedNodes6.nodeMap[1][0]).toEqual(testElementNode.getKey()); - expect(selectedNodes6.nodeMap[2][0]).toEqual(paragraph.getKey()); - expect(selectedNodes6.nodeMap[3][0]).toEqual(text1.getKey()); - expect(selectedNodes6.nodeMap[4][0]).toEqual(text3.getKey()); - expect(selectedNodes6.nodeMap[5][0]).toEqual(text2.getKey()); - - text4.remove(); - - paragraph.select(0, 3); - - const selectedNodes7 = $cloneContents($getSelection()); - - expect(selectedNodes7.range).toEqual([paragraph.getKey()]); - expect(selectedNodes7.nodeMap[0][0]).toEqual(testElementNode.getKey()); - expect(selectedNodes7.nodeMap[1][0]).toEqual(paragraph.getKey()); - expect(selectedNodes7.nodeMap[2][0]).toEqual(text1.getKey()); - expect(selectedNodes7.nodeMap[3][0]).toEqual(text3.getKey()); - expect(selectedNodes7.nodeMap[4][0]).toEqual(text2.getKey()); }); }); diff --git a/packages/lexical-selection/src/grid-selection.ts b/packages/lexical-selection/src/grid-selection.ts deleted file mode 100644 index 9b524acd897..00000000000 --- a/packages/lexical-selection/src/grid-selection.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import type {ICloneSelectionContent} from './lexical-node'; -import type {GridSelection, LexicalNode, NodeKey} from 'lexical'; - -import {$cloneWithProperties} from './lexical-node'; - -export function $cloneGridSelectionContent( - selection: GridSelection, -): ICloneSelectionContent { - const nodeMap = selection.getNodes().map<[NodeKey, LexicalNode]>((node) => { - const nodeKey = node.getKey(); - - const clone = $cloneWithProperties(node); - - return [nodeKey, clone]; - }); - - return { - nodeMap, - range: [selection.gridKey], - }; -} diff --git a/packages/lexical-selection/src/index.ts b/packages/lexical-selection/src/index.ts index 29d37cdfec9..4f152f9a46e 100644 --- a/packages/lexical-selection/src/index.ts +++ b/packages/lexical-selection/src/index.ts @@ -9,7 +9,6 @@ import { $addNodeStyle, - $cloneContents, $cloneWithProperties, $isAtNodeEnd, $patchStyleText, @@ -34,7 +33,6 @@ import { export { $addNodeStyle, - $cloneContents, $cloneWithProperties, $isAtNodeEnd, $patchStyleText, diff --git a/packages/lexical-selection/src/lexical-node.ts b/packages/lexical-selection/src/lexical-node.ts index 4c31e59a8ba..a6acefee15b 100644 --- a/packages/lexical-selection/src/lexical-node.ts +++ b/packages/lexical-selection/src/lexical-node.ts @@ -22,17 +22,12 @@ import { $getNodeByKey, $getPreviousSelection, $isElementNode, - $isNodeSelection, $isRangeSelection, $isTextNode, DEPRECATED_$isGridSelection, } from 'lexical'; -import invariant from 'shared/invariant'; import {CSS_TO_STYLES} from './constants'; -import {$cloneGridSelectionContent} from './grid-selection'; -import {$cloneNodeSelectionContent} from './node-selection'; -import {$cloneRangeSelectionContent} from './range-selection'; import { getCSSFromStyleObject, getStyleObjectFromCSS, @@ -43,7 +38,6 @@ function $updateElementNodeProperties( target: T, source: ElementNode, ): T { - target.__children = Array.from(source.__children); target.__first = source.__first; target.__last = source.__last; target.__size = source.__size; @@ -248,58 +242,11 @@ export function trimTextContentFromAnchor( } } -function errGetLatestOnClone(): void { - invariant(false, 'getLatest() on clone node'); -} - export interface ICloneSelectionContent { nodeMap: Array<[NodeKey, LexicalNode]>; range: Array; } -export function $cloneContents( - selection: RangeSelection | NodeSelection | GridSelection, -): ICloneSelectionContent { - let clone: ICloneSelectionContent = { - nodeMap: [], - range: [], - }; - - if ($isRangeSelection(selection)) { - clone = $cloneRangeSelectionContent(selection); - } else if (DEPRECATED_$isGridSelection(selection)) { - clone = $cloneGridSelectionContent(selection); - } else if ($isNodeSelection(selection)) { - clone = $cloneNodeSelectionContent(selection); - } - - if (__DEV__) { - const nodeMap = clone.nodeMap; - - for (let i = 0; i < nodeMap.length; i++) { - const node = nodeMap[i][1]; - - if (node.getLatest === errGetLatestOnClone) { - continue; - } - - Object.setPrototypeOf( - node, - Object.create(Object.getPrototypeOf(node), { - getLatest: { - configurable: true, - enumerable: true, - value: errGetLatestOnClone, - writable: true, - }, - }), - ); - } - } - - return clone; -} - export function $addNodeStyle(node: TextNode): void { const CSSText = node.getStyle(); const styles = getStyleObjectFromRawCSS(CSSText); diff --git a/packages/lexical-selection/src/node-selection.ts b/packages/lexical-selection/src/node-selection.ts deleted file mode 100644 index 51e496a3efd..00000000000 --- a/packages/lexical-selection/src/node-selection.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ -import type {ICloneSelectionContent} from './lexical-node'; -import type {NodeSelection} from 'lexical'; - -import invariant from 'shared/invariant'; - -export function $cloneNodeSelectionContent( - selection: NodeSelection, -): ICloneSelectionContent { - invariant(false, 'TODO'); -} diff --git a/packages/lexical-selection/src/range-selection.ts b/packages/lexical-selection/src/range-selection.ts index 2801622bc3c..4f1e96e3777 100644 --- a/packages/lexical-selection/src/range-selection.ts +++ b/packages/lexical-selection/src/range-selection.ts @@ -6,7 +6,6 @@ * */ -import type {ICloneSelectionContent} from './lexical-node'; import type { ElementNode, GridSelection, @@ -31,7 +30,6 @@ import { $setSelection, } from 'lexical'; -import {$cloneWithProperties} from './lexical-node'; import {getStyleObjectFromCSS} from './utils'; function $removeParentEmptyElements(startingNode: ElementNode): void { @@ -41,7 +39,7 @@ function $removeParentEmptyElements(startingNode: ElementNode): void { const latest = node.getLatest(); const parentNode: ElementNode | null = node.getParent(); - if (latest.__children.length === 0) { + if (latest.getChildrenSize() === 0) { node.remove(true); } @@ -389,192 +387,6 @@ export function $selectAll(selection: RangeSelection): void { } } -function $getIndexFromPossibleClone( - node: LexicalNode, - parent: ElementNode, - nodeMap: Map, -): number { - const parentClone = nodeMap.get(parent.getKey()); - - if ($isElementNode(parentClone)) { - return parentClone.__children.indexOf(node.getKey()); - } - - return node.getIndexWithinParent(); -} - -function $getParentAvoidingExcludedElements( - node: LexicalNode, -): ElementNode | null { - let parent = node.getParent(); - - while (parent !== null && parent.excludeFromCopy('clone')) { - parent = parent.getParent(); - } - - return parent; -} - -function $copyLeafNodeBranchToRoot( - leaf: LexicalNode, - startingOffset: number | undefined, - endingOffset: number | undefined, - isLeftSide: boolean, - range: Array, - nodeMap: Map, -): void { - let node = leaf; - let offset = startingOffset; - - while (node !== null) { - const parent = $getParentAvoidingExcludedElements(node); - - if (parent === null) { - break; - } - - if (!$isElementNode(node) || !node.excludeFromCopy('clone')) { - const key = node.getKey(); - let clone = nodeMap.get(key); - const needsClone = clone === undefined; - - if (needsClone) { - clone = $cloneWithProperties(node); - nodeMap.set(key, clone); - } - - if ($isTextNode(clone) && !clone.isSegmented() && !clone.isToken()) { - clone.__text = clone.__text.slice( - isLeftSide ? offset : 0, - isLeftSide ? endingOffset : offset, - ); - } else if ($isElementNode(clone)) { - clone.__children = clone.__children.slice( - isLeftSide ? offset : 0, - isLeftSide ? undefined : (offset || 0) + 1, - ); - } - - if ($isRootNode(parent)) { - if (needsClone) { - // We only want to collect a range of top level nodes. - // So if the parent is the root, we know this is a top level. - range.push(key); - } - - break; - } - } - - offset = $getIndexFromPossibleClone(node, parent, nodeMap); - node = parent; - } -} - -export function $cloneRangeSelectionContent( - selection: RangeSelection, -): ICloneSelectionContent { - const anchor = selection.anchor; - const focus = selection.focus; - const [anchorOffset, focusOffset] = selection.getCharacterOffsets(); - const nodes = selection.getNodes(); - - if ( - nodes.length === 0 || - (nodes.length === 1 && - $isElementNode(nodes[0]) && - nodes[0].excludeFromCopy('clone')) - ) { - return { - nodeMap: [], - range: [], - }; - } - - // Check if we can use the parent of the nodes, if the - // parent can't be empty, then it's important that we - // also copy that element node along with its children. - let nodesLength = nodes.length; - const firstNode = nodes[0]; - const firstNodeParent = firstNode.getParent(); - - if ( - firstNodeParent !== null && - (!firstNodeParent.canBeEmpty() || $isRootNode(firstNodeParent)) - ) { - const parentChildren = firstNodeParent.__children; - const parentChildrenLength = parentChildren.length; - - if (parentChildrenLength === nodesLength) { - let areTheSame = true; - - for (let i = 0; i < parentChildren.length; i++) { - if (parentChildren[i] !== nodes[i].__key) { - areTheSame = false; - break; - } - } - - if (areTheSame) { - nodesLength++; - nodes.push(firstNodeParent); - } - } - } - - const lastNode = nodes[nodesLength - 1]; - const isBefore = anchor.isBefore(focus); - const nodeMap = new Map(); - const range: Array = []; - const isOnlyText = $isTextNode(firstNode) && nodesLength === 1; - - // Do first node to root - $copyLeafNodeBranchToRoot( - firstNode, - isBefore ? anchorOffset : focusOffset, - isOnlyText ? (isBefore ? focusOffset : anchorOffset) : undefined, - true, - range, - nodeMap, - ); - - // Copy all nodes between - for (let i = 0; i < nodesLength; i++) { - const node = nodes[i]; - const key = node.getKey(); - - if ( - !nodeMap.has(key) && - (!$isElementNode(node) || !node.excludeFromCopy('clone')) - ) { - const clone = $cloneWithProperties(node); - - if ($isRootNode(node.getParent())) { - range.push(node.getKey()); - } - - if (key !== 'root') { - nodeMap.set(key, clone); - } - } - } - - // Do last node to root - $copyLeafNodeBranchToRoot( - lastNode, - isOnlyText ? undefined : isBefore ? focusOffset : anchorOffset, - undefined, - false, - range, - nodeMap, - ); - - return { - nodeMap: Array.from(nodeMap.entries()), - range, - }; -} - function $getNodeStyleValueForProperty( node: TextNode, styleProperty: string,