Lexical: Fixed strange paragraph formatting behaviour
Some checks failed
test-js / build (push) Has been cancelled

Formatting was not persisted on empty paragraphs, and was instead based
upon last format encountered in selection.
This was due to overly-hasty removal of other formatting code, which
this got caught it.
Restored required parts from prior codebase.

Also updated inline format button active indicator to reflect formats
using the above, so correct buttons are shown as active even when just
in an empty paragraph.
This commit is contained in:
Dan Brown
2025-06-13 19:40:13 +01:00
parent 717b516341
commit 8b062d4795
4 changed files with 35 additions and 4 deletions

View File

@ -84,7 +84,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
// @ts-ignore
window.debugEditorState = () => {
console.log(editor.getEditorState().toJSON());
return editor.getEditorState().toJSON();
};
registerCommonNodeMutationListeners(context);

View File

@ -355,6 +355,7 @@ function onSelectionChange(
lastNode instanceof ParagraphNode &&
lastNode.getChildrenSize() === 0
) {
selection.format = lastNode.getTextFormat();
selection.style = lastNode.getTextStyle();
} else {
selection.format = 0;

View File

@ -19,7 +19,7 @@ import type {
LexicalNode,
NodeKey,
} from '../LexicalNode';
import type {RangeSelection} from 'lexical';
import {RangeSelection, TEXT_TYPE_TO_FORMAT, TextFormatType} from 'lexical';
import {
$applyNodeReplacement,
@ -36,6 +36,7 @@ import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} f
export type SerializedParagraphNode = Spread<
{
textFormat: number;
textStyle: string;
},
SerializedCommonBlockNode
@ -45,10 +46,12 @@ export type SerializedParagraphNode = Spread<
export class ParagraphNode extends CommonBlockNode {
['constructor']!: KlassConstructor<typeof ParagraphNode>;
/** @internal */
__textFormat: number;
__textStyle: string;
constructor(key?: NodeKey) {
super(key);
this.__textFormat = 0;
this.__textStyle = '';
}
@ -56,6 +59,22 @@ export class ParagraphNode extends CommonBlockNode {
return 'paragraph';
}
getTextFormat(): number {
const self = this.getLatest();
return self.__textFormat;
}
setTextFormat(type: number): this {
const self = this.getWritable();
self.__textFormat = type;
return self;
}
hasTextFormat(type: TextFormatType): boolean {
const formatFlag = TEXT_TYPE_TO_FORMAT[type];
return (this.getTextFormat() & formatFlag) !== 0;
}
getTextStyle(): string {
const self = this.getLatest();
return self.__textStyle;
@ -73,6 +92,7 @@ export class ParagraphNode extends CommonBlockNode {
afterCloneFrom(prevNode: this) {
super.afterCloneFrom(prevNode);
this.__textFormat = prevNode.__textFormat;
this.__textStyle = prevNode.__textStyle;
copyCommonBlockProperties(prevNode, this);
}
@ -125,12 +145,14 @@ export class ParagraphNode extends CommonBlockNode {
static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode {
const node = $createParagraphNode();
deserializeCommonBlockNode(serializedNode, node);
node.setTextFormat(serializedNode.textFormat);
return node;
}
exportJSON(): SerializedParagraphNode {
return {
...super.exportJSON(),
textFormat: this.getTextFormat(),
textStyle: this.getTextStyle(),
type: 'paragraph',
version: 1,
@ -144,6 +166,7 @@ export class ParagraphNode extends CommonBlockNode {
restoreSelection: boolean,
): ParagraphNode {
const newElement = $createParagraphNode();
newElement.setTextFormat(rangeSelection.format);
newElement.setTextStyle(rangeSelection.style);
const direction = this.getDirection();
newElement.setDirection(direction);

View File

@ -3,7 +3,7 @@ import {
$createParagraphNode, $createRangeSelection,
$getRoot,
$getSelection, $isBlockElementNode, $isDecoratorNode,
$isElementNode,
$isElementNode, $isParagraphNode,
$isTextNode,
$setSelection,
BaseSelection, DecoratorNode,
@ -60,12 +60,19 @@ export function $selectionContainsTextFormat(selection: BaseSelection | null, fo
return false;
}
for (const node of selection.getNodes()) {
// Check text nodes
const nodes = selection.getNodes();
for (const node of nodes) {
if ($isTextNode(node) && node.hasFormat(format)) {
return true;
}
}
// If we're in an empty paragraph, check the paragraph format
if (nodes.length === 1 && $isParagraphNode(nodes[0]) && nodes[0].hasTextFormat(format)) {
return true;
}
return false;
}