3 $createParagraphNode, $createRangeSelection,
5 $getSelection, $isBlockElementNode, $isDecoratorNode,
6 $isElementNode, $isParagraphNode,
9 BaseSelection, DecoratorNode,
10 ElementNode, LexicalEditor,
12 TextFormatType, TextNode
14 import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
15 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
16 import {$setBlocksType} from "@lexical/selection";
18 import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
19 import {CommonBlockAlignment} from "lexical/nodes/common";
21 const lastSelectionByEditor = new WeakMap<LexicalEditor, BaseSelection|null>;
23 export function getLastSelection(editor: LexicalEditor): BaseSelection|null {
24 return lastSelectionByEditor.get(editor) || null;
27 export function setLastSelection(editor: LexicalEditor, selection: BaseSelection|null): void {
28 lastSelectionByEditor.set(editor, selection);
31 export function $selectionContainsNodeType(selection: BaseSelection | null, matcher: LexicalNodeMatcher): boolean {
32 return $getNodeFromSelection(selection, matcher) !== null;
35 export function $getNodeFromSelection(selection: BaseSelection | null, matcher: LexicalNodeMatcher): LexicalNode | null {
40 for (const node of selection.getNodes()) {
45 const matchedParent = $getParentOfType(node, matcher);
54 export function $getTextNodeFromSelection(selection: BaseSelection | null): TextNode|null {
55 return $getNodeFromSelection(selection, $isTextNode) as TextNode|null;
58 export function $selectionContainsTextFormat(selection: BaseSelection | null, format: TextFormatType): boolean {
64 const nodes = selection.getNodes();
65 for (const node of nodes) {
66 if ($isTextNode(node) && node.hasFormat(format)) {
71 // If we're in an empty paragraph, check the paragraph format
72 if (nodes.length === 1 && $isParagraphNode(nodes[0]) && nodes[0].hasTextFormat(format)) {
79 export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
80 const selection = $getSelection();
81 const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
82 if (selection && matcher(blockElement)) {
83 $setBlocksType(selection, $createParagraphNode);
85 $setBlocksType(selection, creator);
89 export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
90 $insertNewBlockNodesAtSelection([node], insertAfter);
93 export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
94 const selectionNodes = $getSelection()?.getNodes() || [];
95 const blockElement = selectionNodes.length > 0 ? $getNearestNodeBlockParent(selectionNodes[0]) : null;
99 for (let i = nodes.length - 1; i >= 0; i--) {
100 blockElement.insertAfter(nodes[i]);
103 for (const node of nodes) {
104 blockElement.insertBefore(node);
108 $getRoot().append(...nodes);
112 export function $selectSingleNode(node: LexicalNode) {
113 const nodeSelection = $createNodeSelection();
114 nodeSelection.add(node.getKey());
115 $setSelection(nodeSelection);
118 function getFirstTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
119 for (const node of nodes) {
120 if ($isTextNode(node)) {
124 if ($isElementNode(node)) {
125 const children = node.getChildren();
126 const textNode = getFirstTextNodeInNodes(children);
127 if (textNode !== null) {
136 function getLastTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
137 const revNodes = [...nodes].reverse();
138 for (const node of revNodes) {
139 if ($isTextNode(node)) {
143 if ($isElementNode(node)) {
144 const children = [...node.getChildren()].reverse();
145 const textNode = getLastTextNodeInNodes(children);
146 if (textNode !== null) {
155 export function $selectNodes(nodes: LexicalNode[]) {
156 if (nodes.length === 0) {
160 const selection = $createRangeSelection();
161 const firstText = getFirstTextNodeInNodes(nodes);
162 const lastText = getLastTextNodeInNodes(nodes);
163 if (firstText && lastText) {
164 selection.setTextNodeRange(firstText, 0, lastText, lastText.getTextContentSize() || 0)
165 $setSelection(selection);
169 export function $toggleSelection(editor: LexicalEditor) {
170 const lastSelection = getLastSelection(editor);
173 window.requestAnimationFrame(() => {
174 editor.update(() => {
175 $setSelection(lastSelection.clone());
181 export function $selectionContainsNode(selection: BaseSelection | null, node: LexicalNode): boolean {
186 const key = node.getKey();
187 for (const node of selection.getNodes()) {
188 if (node.getKey() === key) {
196 export function $selectionContainsAlignment(selection: BaseSelection | null, alignment: CommonBlockAlignment): boolean {
199 ...(selection?.getNodes() || []),
200 ...$getBlockElementNodesInSelection(selection)
202 for (const node of nodes) {
203 if (nodeHasAlignment(node) && node.getAlignment() === alignment) {
211 export function $selectionContainsDirection(selection: BaseSelection | null, direction: 'rtl'|'ltr'): boolean {
214 ...(selection?.getNodes() || []),
215 ...$getBlockElementNodesInSelection(selection)
218 for (const node of nodes) {
219 if ($isBlockElementNode(node) && node.getDirection() === direction) {
227 export function $getBlockElementNodesInSelection(selection: BaseSelection | null): ElementNode[] {
232 const blockNodes: Map<string, ElementNode> = new Map();
233 for (const node of selection.getNodes()) {
234 const blockElement = $getNearestNodeBlockParent(node);
235 if ($isElementNode(blockElement)) {
236 blockNodes.set(blockElement.getKey(), blockElement);
240 return Array.from(blockNodes.values());
243 export function $getDecoratorNodesInSelection(selection: BaseSelection | null): DecoratorNode<any>[] {
248 return selection.getNodes().filter(node => $isDecoratorNode(node));