3 $createParagraphNode, $getRoot,
4 $getSelection, $isElementNode,
5 $isTextNode, $setSelection,
6 BaseSelection, ElementFormatType, ElementNode, LexicalEditor,
7 LexicalNode, TextFormatType
9 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
10 import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
11 import {$setBlocksType} from "@lexical/selection";
12 import {$createCustomParagraphNode} from "./nodes/custom-paragraph";
13 import {$generateNodesFromDOM} from "@lexical/html";
15 export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
16 const el = document.createElement(tag);
17 const attrKeys = Object.keys(attrs);
18 for (const attr of attrKeys) {
19 if (attrs[attr] !== null) {
20 el.setAttribute(attr, attrs[attr] as string);
24 for (const child of children) {
25 if (typeof child === 'string') {
26 el.append(document.createTextNode(child));
35 function htmlToDom(html: string): Document {
36 const parser = new DOMParser();
37 return parser.parseFromString(html, 'text/html');
40 function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
41 return nodes.map(node => {
42 if ($isTextNode(node)) {
43 const paragraph = $createCustomParagraphNode();
44 paragraph.append(node);
51 export function $htmlToBlockNodes(editor: LexicalEditor, html: string): LexicalNode[] {
52 const dom = htmlToDom(html);
53 const nodes = $generateNodesFromDOM(editor, dom);
54 return wrapTextNodes(nodes);
57 export function $selectionContainsNodeType(selection: BaseSelection|null, matcher: LexicalNodeMatcher): boolean {
58 return $getNodeFromSelection(selection, matcher) !== null;
61 export function $getNodeFromSelection(selection: BaseSelection|null, matcher: LexicalNodeMatcher): LexicalNode|null {
66 for (const node of selection.getNodes()) {
71 const matchedParent = $getParentOfType(node, matcher);
80 export function $getParentOfType(node: LexicalNode, matcher: LexicalNodeMatcher): LexicalNode|null {
81 for (const parent of node.getParents()) {
82 if (matcher(parent)) {
90 export function $selectionContainsTextFormat(selection: BaseSelection|null, format: TextFormatType): boolean {
95 for (const node of selection.getNodes()) {
96 if ($isTextNode(node) && node.hasFormat(format)) {
104 export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
105 const selection = $getSelection();
106 const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
107 if (selection && matcher(blockElement)) {
108 $setBlocksType(selection, $createParagraphNode);
110 $setBlocksType(selection, creator);
114 export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
115 $insertNewBlockNodesAtSelection([node], insertAfter);
118 export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
119 const selection = $getSelection();
120 const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
124 for (let i = nodes.length - 1; i >= 0; i--) {
125 blockElement.insertAfter(nodes[i]);
128 for (const node of nodes) {
129 blockElement.insertBefore(node);
133 $getRoot().append(...nodes);
137 export function $selectSingleNode(node: LexicalNode) {
138 const nodeSelection = $createNodeSelection();
139 nodeSelection.add(node.getKey());
140 $setSelection(nodeSelection);
143 export function $selectionContainsNode(selection: BaseSelection|null, node: LexicalNode): boolean {
148 const key = node.getKey();
149 for (const node of selection.getNodes()) {
150 if (node.getKey() === key) {
158 export function $selectionContainsElementFormat(selection: BaseSelection|null, format: ElementFormatType): boolean {
159 const nodes = $getBlockElementNodesInSelection(selection);
160 for (const node of nodes) {
161 if (node.getFormatType() === format) {
169 export function $getBlockElementNodesInSelection(selection: BaseSelection|null): ElementNode[] {
174 const blockNodes: Map<string, ElementNode> = new Map();
175 for (const node of selection.getNodes()) {
176 const blockElement = $findMatchingParent(node, (node) => {
177 return $isElementNode(node) && !node.isInline();
178 }) as ElementNode|null;
181 blockNodes.set(blockElement.getKey(), blockElement);
185 return Array.from(blockNodes.values());
189 * Get the nearest root/block level node for the given position.
191 export function $getNearestBlockNodeForCoords(editor: LexicalEditor, x: number, y: number): LexicalNode|null {
192 // TODO - Take into account x for floated blocks?
193 const rootNodes = $getRoot().getChildren();
194 for (const node of rootNodes) {
195 const nodeDom = editor.getElementByKey(node.__key);
200 const bounds = nodeDom.getBoundingClientRect();
201 if (y <= bounds.bottom) {