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';
24 SerializedElementNode,
25 } from './LexicalElementNode';
26 import type {RangeSelection} from 'lexical';
28 import {TEXT_TYPE_TO_FORMAT} from '../LexicalConstants';
30 $applyNodeReplacement,
31 getCachedClassNameArray,
33 } from '../LexicalUtils';
34 import {ElementNode} from './LexicalElementNode';
35 import {$isTextNode, TextFormatType} from './LexicalTextNode';
37 export type SerializedParagraphNode = Spread<
46 export class ParagraphNode extends ElementNode {
47 ['constructor']!: KlassConstructor<typeof ParagraphNode>;
52 constructor(key?: NodeKey) {
54 this.__textFormat = 0;
55 this.__textStyle = '';
58 static getType(): string {
62 getTextFormat(): number {
63 const self = this.getLatest();
64 return self.__textFormat;
67 setTextFormat(type: number): this {
68 const self = this.getWritable();
69 self.__textFormat = type;
73 hasTextFormat(type: TextFormatType): boolean {
74 const formatFlag = TEXT_TYPE_TO_FORMAT[type];
75 return (this.getTextFormat() & formatFlag) !== 0;
78 getTextStyle(): string {
79 const self = this.getLatest();
80 return self.__textStyle;
83 setTextStyle(style: string): this {
84 const self = this.getWritable();
85 self.__textStyle = style;
89 static clone(node: ParagraphNode): ParagraphNode {
90 return new ParagraphNode(node.__key);
93 afterCloneFrom(prevNode: this) {
94 super.afterCloneFrom(prevNode);
95 this.__textFormat = prevNode.__textFormat;
96 this.__textStyle = prevNode.__textStyle;
101 createDOM(config: EditorConfig): HTMLElement {
102 const dom = document.createElement('p');
103 const classNames = getCachedClassNameArray(config.theme, 'paragraph');
104 if (classNames !== undefined) {
105 const domClassList = dom.classList;
106 domClassList.add(...classNames);
111 prevNode: ParagraphNode,
113 config: EditorConfig,
118 static importDOM(): DOMConversionMap | null {
120 p: (node: Node) => ({
121 conversion: $convertParagraphElement,
127 exportDOM(editor: LexicalEditor): DOMExportOutput {
128 const {element} = super.exportDOM(editor);
130 if (element && isHTMLElement(element)) {
131 if (this.isEmpty()) {
132 element.append(document.createElement('br'));
135 const formatType = this.getFormatType();
136 element.style.textAlign = formatType;
138 const indent = this.getIndent();
140 // padding-inline-start is not widely supported in email HTML, but
141 // Lexical Reconciler uses padding-inline-start. Using text-indent instead.
142 element.style.textIndent = `${indent * 20}px`;
151 static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode {
152 const node = $createParagraphNode();
153 node.setFormat(serializedNode.format);
154 node.setIndent(serializedNode.indent);
155 node.setTextFormat(serializedNode.textFormat);
159 exportJSON(): SerializedParagraphNode {
161 ...super.exportJSON(),
162 textFormat: this.getTextFormat(),
163 textStyle: this.getTextStyle(),
172 rangeSelection: RangeSelection,
173 restoreSelection: boolean,
175 const newElement = $createParagraphNode();
176 newElement.setTextFormat(rangeSelection.format);
177 newElement.setTextStyle(rangeSelection.style);
178 const direction = this.getDirection();
179 newElement.setDirection(direction);
180 newElement.setFormat(this.getFormatType());
181 newElement.setStyle(this.getTextStyle());
182 this.insertAfter(newElement, restoreSelection);
186 collapseAtStart(): boolean {
187 const children = this.getChildren();
188 // If we have an empty (trimmed) first paragraph and try and remove it,
189 // delete the paragraph as long as we have another sibling to go to
191 children.length === 0 ||
192 ($isTextNode(children[0]) && children[0].getTextContent().trim() === '')
194 const nextSibling = this.getNextSibling();
195 if (nextSibling !== null) {
200 const prevSibling = this.getPreviousSibling();
201 if (prevSibling !== null) {
202 this.selectPrevious();
211 function $convertParagraphElement(element: HTMLElement): DOMConversionOutput {
212 const node = $createParagraphNode();
214 node.setFormat(element.style.textAlign as ElementFormatType);
215 const indent = parseInt(element.style.textIndent, 10) / 20;
217 node.setIndent(indent);
223 export function $createParagraphNode(): ParagraphNode {
224 return $applyNodeReplacement(new ParagraphNode());
227 export function $isParagraphNode(
228 node: LexicalNode | null | undefined,
229 ): node is ParagraphNode {
230 return node instanceof ParagraphNode;