3 $createParagraphNode, $createRangeSelection,
5 $getSelection, $isBlockElementNode, $isDecoratorNode,
9 BaseSelection, DecoratorNode,
11 ElementNode, LexicalEditor,
13 TextFormatType, TextNode
15 import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
16 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
17 import {$setBlocksType} from "@lexical/selection";
19 import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
20 import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
21 import {CommonBlockAlignment} from "../nodes/_common";
23 const lastSelectionByEditor = new WeakMap<LexicalEditor, BaseSelection|null>;
25 export function getLastSelection(editor: LexicalEditor): BaseSelection|null {
26 return lastSelectionByEditor.get(editor) || null;
29 export function setLastSelection(editor: LexicalEditor, selection: BaseSelection|null): void {
30 lastSelectionByEditor.set(editor, selection);
33 export function $selectionContainsNodeType(selection: BaseSelection | null, matcher: LexicalNodeMatcher): boolean {
34 return $getNodeFromSelection(selection, matcher) !== null;
37 export function $getNodeFromSelection(selection: BaseSelection | null, matcher: LexicalNodeMatcher): LexicalNode | null {
42 for (const node of selection.getNodes()) {
47 const matchedParent = $getParentOfType(node, matcher);
56 export function $selectionContainsTextFormat(selection: BaseSelection | null, format: TextFormatType): boolean {
61 for (const node of selection.getNodes()) {
62 if ($isTextNode(node) && node.hasFormat(format)) {
70 export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
71 const selection = $getSelection();
72 const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
73 if (selection && matcher(blockElement)) {
74 $setBlocksType(selection, $createCustomParagraphNode);
76 $setBlocksType(selection, creator);
80 export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
81 $insertNewBlockNodesAtSelection([node], insertAfter);
84 export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
85 const selectionNodes = $getSelection()?.getNodes() || [];
86 const blockElement = selectionNodes.length > 0 ? $getNearestNodeBlockParent(selectionNodes[0]) : null;
90 for (let i = nodes.length - 1; i >= 0; i--) {
91 blockElement.insertAfter(nodes[i]);
94 for (const node of nodes) {
95 blockElement.insertBefore(node);
99 $getRoot().append(...nodes);
103 export function $selectSingleNode(node: LexicalNode) {
104 const nodeSelection = $createNodeSelection();
105 nodeSelection.add(node.getKey());
106 $setSelection(nodeSelection);
109 function getFirstTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
110 for (const node of nodes) {
111 if ($isTextNode(node)) {
115 if ($isElementNode(node)) {
116 const children = node.getChildren();
117 const textNode = getFirstTextNodeInNodes(children);
118 if (textNode !== null) {
127 function getLastTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
128 const revNodes = [...nodes].reverse();
129 for (const node of revNodes) {
130 if ($isTextNode(node)) {
134 if ($isElementNode(node)) {
135 const children = [...node.getChildren()].reverse();
136 const textNode = getLastTextNodeInNodes(children);
137 if (textNode !== null) {
146 export function $selectNodes(nodes: LexicalNode[]) {
147 if (nodes.length === 0) {
151 const selection = $createRangeSelection();
152 const firstText = getFirstTextNodeInNodes(nodes);
153 const lastText = getLastTextNodeInNodes(nodes);
154 if (firstText && lastText) {
155 selection.setTextNodeRange(firstText, 0, lastText, lastText.getTextContentSize() || 0)
156 $setSelection(selection);
160 export function $toggleSelection(editor: LexicalEditor) {
161 const lastSelection = getLastSelection(editor);
164 window.requestAnimationFrame(() => {
165 editor.update(() => {
166 $setSelection(lastSelection.clone());
172 export function $selectionContainsNode(selection: BaseSelection | null, node: LexicalNode): boolean {
177 const key = node.getKey();
178 for (const node of selection.getNodes()) {
179 if (node.getKey() === key) {
187 export function $selectionContainsAlignment(selection: BaseSelection | null, alignment: CommonBlockAlignment): boolean {
190 ...(selection?.getNodes() || []),
191 ...$getBlockElementNodesInSelection(selection)
193 for (const node of nodes) {
194 if (nodeHasAlignment(node) && node.getAlignment() === alignment) {
202 export function $selectionContainsDirection(selection: BaseSelection | null, direction: 'rtl'|'ltr'): boolean {
205 ...(selection?.getNodes() || []),
206 ...$getBlockElementNodesInSelection(selection)
209 for (const node of nodes) {
210 if ($isBlockElementNode(node) && node.getDirection() === direction) {
218 export function $getBlockElementNodesInSelection(selection: BaseSelection | null): ElementNode[] {
223 const blockNodes: Map<string, ElementNode> = new Map();
224 for (const node of selection.getNodes()) {
225 const blockElement = $getNearestNodeBlockParent(node);
226 if ($isElementNode(blockElement)) {
227 blockNodes.set(blockElement.getKey(), blockElement);
231 return Array.from(blockNodes.values());
234 export function $getDecoratorNodesInSelection(selection: BaseSelection | null): DecoratorNode<any>[] {
239 return selection.getNodes().filter(node => $isDecoratorNode(node));