16 import {addClassNamesToElement} from "@lexical/utils";
17 import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode";
19 commonPropertiesDifferent, deserializeCommonBlockNode,
20 setCommonBlockPropsFromElement,
21 updateElementWithCommonBlockProps
22 } from "lexical/nodes/common";
24 export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
26 export type SerializedHeadingNode = Spread<
28 tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
30 SerializedCommonBlockNode
34 export class HeadingNode extends CommonBlockNode {
36 __tag: HeadingTagType;
38 static getType(): string {
42 static clone(node: HeadingNode): HeadingNode {
43 const clone = new HeadingNode(node.__tag, node.__key);
44 copyCommonBlockProperties(node, clone);
48 constructor(tag: HeadingTagType, key?: NodeKey) {
53 getTag(): HeadingTagType {
59 createDOM(config: EditorConfig): HTMLElement {
60 const tag = this.__tag;
61 const element = document.createElement(tag);
62 const theme = config.theme;
63 const classNames = theme.heading;
64 if (classNames !== undefined) {
65 const className = classNames[tag];
66 addClassNamesToElement(element, className);
68 updateElementWithCommonBlockProps(element, this);
72 updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean {
73 return commonPropertiesDifferent(prevNode, this);
76 static importDOM(): DOMConversionMap | null {
78 h1: (node: Node) => ({
79 conversion: $convertHeadingElement,
82 h2: (node: Node) => ({
83 conversion: $convertHeadingElement,
86 h3: (node: Node) => ({
87 conversion: $convertHeadingElement,
90 h4: (node: Node) => ({
91 conversion: $convertHeadingElement,
94 h5: (node: Node) => ({
95 conversion: $convertHeadingElement,
98 h6: (node: Node) => ({
99 conversion: $convertHeadingElement,
105 exportDOM(editor: LexicalEditor): DOMExportOutput {
106 const {element} = super.exportDOM(editor);
108 if (element && isHTMLElement(element)) {
109 if (this.isEmpty()) {
110 element.append(document.createElement('br'));
119 static importJSON(serializedNode: SerializedHeadingNode): HeadingNode {
120 const node = $createHeadingNode(serializedNode.tag);
121 deserializeCommonBlockNode(serializedNode, node);
125 exportJSON(): SerializedHeadingNode {
127 ...super.exportJSON(),
136 selection?: RangeSelection,
137 restoreSelection = true,
138 ): ParagraphNode | HeadingNode {
139 const anchorOffet = selection ? selection.anchor.offset : 0;
140 const lastDesc = this.getLastDescendant();
144 selection.anchor.key === lastDesc.getKey() &&
145 anchorOffet === lastDesc.getTextContentSize());
147 isAtEnd || !selection
148 ? $createParagraphNode()
149 : $createHeadingNode(this.getTag());
150 const direction = this.getDirection();
151 newElement.setDirection(direction);
152 this.insertAfter(newElement, restoreSelection);
153 if (anchorOffet === 0 && !this.isEmpty() && selection) {
154 const paragraph = $createParagraphNode();
156 this.replace(paragraph, true);
161 collapseAtStart(): true {
162 const newElement = !this.isEmpty()
163 ? $createHeadingNode(this.getTag())
164 : $createParagraphNode();
165 const children = this.getChildren();
166 children.forEach((child) => newElement.append(child));
167 this.replace(newElement);
171 extractWithChild(): boolean {
176 function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
177 const nodeName = element.nodeName.toLowerCase();
187 node = $createHeadingNode(nodeName);
188 setCommonBlockPropsFromElement(element, node);
193 export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode {
194 return $applyNodeReplacement(new HeadingNode(headingTag));
197 export function $isHeadingNode(
198 node: LexicalNode | null | undefined,
199 ): node is HeadingNode {
200 return node instanceof HeadingNode;