2 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
14 } from '../LexicalEditor';
21 } from '../LexicalNode';
22 import type {RangeSelection} from 'lexical';
25 $applyNodeReplacement,
26 getCachedClassNameArray,
28 } from '../LexicalUtils';
29 import {$isTextNode} from './LexicalTextNode';
31 commonPropertiesDifferent, deserializeCommonBlockNode,
32 SerializedCommonBlockNode, setCommonBlockPropsFromElement,
33 updateElementWithCommonBlockProps
34 } from "../../../nodes/_common";
35 import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode";
37 export type SerializedParagraphNode = Spread<
41 SerializedCommonBlockNode
45 export class ParagraphNode extends CommonBlockNode {
46 ['constructor']!: KlassConstructor<typeof ParagraphNode>;
50 constructor(key?: NodeKey) {
52 this.__textStyle = '';
55 static getType(): string {
59 getTextStyle(): string {
60 const self = this.getLatest();
61 return self.__textStyle;
64 setTextStyle(style: string): this {
65 const self = this.getWritable();
66 self.__textStyle = style;
70 static clone(node: ParagraphNode): ParagraphNode {
71 return new ParagraphNode(node.__key);
74 afterCloneFrom(prevNode: this) {
75 super.afterCloneFrom(prevNode);
76 this.__textStyle = prevNode.__textStyle;
77 copyCommonBlockProperties(prevNode, this);
82 createDOM(config: EditorConfig): HTMLElement {
83 const dom = document.createElement('p');
84 const classNames = getCachedClassNameArray(config.theme, 'paragraph');
85 if (classNames !== undefined) {
86 const domClassList = dom.classList;
87 domClassList.add(...classNames);
90 updateElementWithCommonBlockProps(dom, this);
95 prevNode: ParagraphNode,
99 return commonPropertiesDifferent(prevNode, this);
102 static importDOM(): DOMConversionMap | null {
104 p: (node: Node) => ({
105 conversion: $convertParagraphElement,
111 exportDOM(editor: LexicalEditor): DOMExportOutput {
112 const {element} = super.exportDOM(editor);
114 if (element && isHTMLElement(element)) {
115 if (this.isEmpty()) {
116 element.append(document.createElement('br'));
125 static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode {
126 const node = $createParagraphNode();
127 deserializeCommonBlockNode(serializedNode, node);
131 exportJSON(): SerializedParagraphNode {
133 ...super.exportJSON(),
134 textStyle: this.getTextStyle(),
143 rangeSelection: RangeSelection,
144 restoreSelection: boolean,
146 const newElement = $createParagraphNode();
147 newElement.setTextStyle(rangeSelection.style);
148 const direction = this.getDirection();
149 newElement.setDirection(direction);
150 newElement.setStyle(this.getTextStyle());
151 this.insertAfter(newElement, restoreSelection);
155 collapseAtStart(): boolean {
156 const children = this.getChildren();
157 // If we have an empty (trimmed) first paragraph and try and remove it,
158 // delete the paragraph as long as we have another sibling to go to
160 children.length === 0 ||
161 ($isTextNode(children[0]) && children[0].getTextContent().trim() === '')
163 const nextSibling = this.getNextSibling();
164 if (nextSibling !== null) {
169 const prevSibling = this.getPreviousSibling();
170 if (prevSibling !== null) {
171 this.selectPrevious();
180 function $convertParagraphElement(element: HTMLElement): DOMConversionOutput {
181 const node = $createParagraphNode();
182 setCommonBlockPropsFromElement(element, node);
186 export function $createParagraphNode(): ParagraphNode {
187 return $applyNodeReplacement(new ParagraphNode());
190 export function $isParagraphNode(
191 node: LexicalNode | null | undefined,
192 ): node is ParagraphNode {
193 return node instanceof ParagraphNode;