Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lexical] [lexical-selection] Preserve paragraph styles between lines #6437

Merged
merged 10 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/lexical-devtools-core/src/generateContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ function printNode(
return `ids: [ ${node.getIDs().join(', ')} ]`;
} else if ($isParagraphNode(node)) {
const formatText = printTextFormatProperties(node);
return formatText !== '' ? `{ ${formatText} }` : '';
let paragraphData = formatText !== '' ? `{ ${formatText} }` : '';
paragraphData += node.__style ? `(${node.__style})` : '';
return paragraphData;
} else {
return '';
}
Expand Down
45 changes: 45 additions & 0 deletions packages/lexical-playground/__tests__/e2e/Selection.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,51 @@ test.describe.parallel('Selection', () => {
);
});

test('Can persist the text style (color) from the paragraph', async ({
page,
isPlainText,
}) => {
test.skip(isPlainText);
await focusEditor(page);
await click(page, '.color-picker');
await click(page, '.color-picker-basic-color > button');
await click(page, '.PlaygroundEditorTheme__paragraph');
await page.keyboard.type('Line1');
await page.keyboard.press('Enter');
await page.keyboard.press('Enter');
await page.keyboard.press('Enter');
await page.keyboard.type('Line2');
await page.keyboard.press('ArrowUp');
await page.keyboard.type('Line3');
await assertHTML(
page,
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span style="color: rgb(208, 2, 27)" data-lexical-text="true">
Line1
</span>
</p>
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span style="color: rgb(208, 2, 27)" data-lexical-text="true">
Line3
</span>
</p>
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span style="color: rgb(208, 2, 27)" data-lexical-text="true">
Line2
</span>
</p>
`,
);
});

test('shift+arrowdown into a table selects the whole table', async ({
page,
isPlainText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ describe('table selection', () => {
__parent: null,
__prev: null,
__size: 1,
__style: '',
__type: 'root',
});
expect(parsedParagraph).toEqual({
Expand All @@ -147,7 +148,9 @@ describe('table selection', () => {
__parent: 'root',
__prev: null,
__size: 1,
__style: '',
__textFormat: 0,
__textStyle: '',
__type: 'paragraph',
});
expect(parsedText).toEqual({
Expand Down
3 changes: 2 additions & 1 deletion packages/lexical/src/LexicalEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,16 @@ function onSelectionChange(
selection.style = anchorNode.getStyle();
} else if (anchor.type === 'element' && !isRootTextContentEmpty) {
const lastNode = anchor.getNode();
selection.style = '';
if (
lastNode instanceof ParagraphNode &&
lastNode.getChildrenSize() === 0
) {
selection.format = lastNode.getTextFormat();
selection.style = lastNode.getTextStyle();
} else {
selection.format = 0;
}
selection.style = '';
}
}
} else {
Expand Down
43 changes: 36 additions & 7 deletions packages/lexical/src/LexicalReconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type IntentionallyMarkedAsDirtyElement = boolean;
let subTreeTextContent = '';
let subTreeDirectionedTextContent = '';
let subTreeTextFormat: number | null = null;
let subTreeTextStyle: string = '';
let editorTextContent = '';
let activeEditorConfig: EditorConfig;
let activeEditor: LexicalEditor;
Expand Down Expand Up @@ -288,8 +289,13 @@ function $createChildren(
for (; startIndex <= endIndex; ++startIndex) {
$createNode(children[startIndex], dom, insertDOM);
const node = activeNextNodeMap.get(children[startIndex]);
if (node !== null && subTreeTextFormat === null && $isTextNode(node)) {
subTreeTextFormat = node.getFormat();
if (node !== null && $isTextNode(node)) {
if (subTreeTextFormat === null) {
subTreeTextFormat = node.getFormat();
}
if (subTreeTextStyle === '') {
subTreeTextStyle = node.getStyle();
}
}
}
if ($textContentRequiresDoubleLinebreakAtEnd(element)) {
Expand Down Expand Up @@ -356,6 +362,18 @@ function reconcileParagraphFormat(element: ElementNode): void {
!activeEditorStateReadOnly
) {
element.setTextFormat(subTreeTextFormat);
element.setTextStyle(subTreeTextStyle);
}
}

function reconcileParagraphStyle(element: ElementNode): void {
if (
$isParagraphNode(element) &&
subTreeTextStyle !== '' &&
subTreeTextStyle !== element.__textStyle &&
!activeEditorStateReadOnly
) {
element.setTextStyle(subTreeTextStyle);
}
}

Expand Down Expand Up @@ -440,11 +458,12 @@ function $reconcileChildrenWithDirection(
const previousSubTreeDirectionTextContent = subTreeDirectionedTextContent;
subTreeDirectionedTextContent = '';
subTreeTextFormat = null;
subTreeTextStyle = '';
$reconcileChildren(prevElement, nextElement, dom);
reconcileBlockDirection(nextElement, dom);
reconcileParagraphFormat(nextElement);
reconcileParagraphStyle(nextElement);
subTreeDirectionedTextContent = previousSubTreeDirectionTextContent;
subTreeTextFormat = null;
}

function createChildrenArray(
Expand Down Expand Up @@ -486,8 +505,13 @@ function $reconcileChildren(
destroyNode(prevFirstChildKey, null);
}
const nextChildNode = activeNextNodeMap.get(nextFrstChildKey);
if (subTreeTextFormat === null && $isTextNode(nextChildNode)) {
subTreeTextFormat = nextChildNode.getFormat();
if ($isTextNode(nextChildNode)) {
if (subTreeTextFormat === null) {
subTreeTextFormat = nextChildNode.getFormat();
}
if (subTreeTextStyle === '') {
subTreeTextStyle = nextChildNode.getStyle();
}
}
} else {
const prevChildren = createChildrenArray(prevElement, activePrevNodeMap);
Expand Down Expand Up @@ -777,8 +801,13 @@ function $reconcileNodeChildren(
}

const node = activeNextNodeMap.get(nextKey);
if (node !== null && subTreeTextFormat === null && $isTextNode(node)) {
subTreeTextFormat = node.getFormat();
if (node !== null && $isTextNode(node)) {
if (subTreeTextFormat === null) {
subTreeTextFormat = node.getFormat();
}
if (subTreeTextStyle === '') {
subTreeTextStyle = node.getStyle();
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/lexical/src/LexicalUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1751,12 +1751,14 @@ export function $cloneWithProperties<T extends LexicalNode>(latestNode: T): T {
if ($isElementNode(latestNode) && $isElementNode(mutableNode)) {
if ($isParagraphNode(latestNode) && $isParagraphNode(mutableNode)) {
mutableNode.__textFormat = latestNode.__textFormat;
mutableNode.__textStyle = latestNode.__textStyle;
}
mutableNode.__first = latestNode.__first;
mutableNode.__last = latestNode.__last;
mutableNode.__size = latestNode.__size;
mutableNode.__indent = latestNode.__indent;
mutableNode.__format = latestNode.__format;
mutableNode.__style = latestNode.__style;
mutableNode.__dir = latestNode.__dir;
} else if ($isTextNode(latestNode) && $isTextNode(mutableNode)) {
mutableNode.__format = latestNode.__format;
Expand Down
11 changes: 10 additions & 1 deletion packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ describe('LexicalEditor tests', () => {
editable ? 'editable' : 'non-editable'
})`, async () => {
const JSON_EDITOR_STATE =
'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
init();
const contentEditable = editor.getRootElement();
editor.setEditable(editable);
Expand Down Expand Up @@ -1188,6 +1188,7 @@ describe('LexicalEditor tests', () => {
__parent: null,
__prev: null,
__size: 1,
__style: '',
__type: 'root',
});
expect(paragraph).toEqual({
Expand All @@ -1201,7 +1202,9 @@ describe('LexicalEditor tests', () => {
__parent: 'root',
__prev: null,
__size: 0,
__style: '',
__textFormat: 0,
__textStyle: '',
__type: 'paragraph',
});
});
Expand Down Expand Up @@ -1272,6 +1275,7 @@ describe('LexicalEditor tests', () => {
__parent: null,
__prev: null,
__size: 1,
__style: '',
__type: 'root',
});
expect(parsedParagraph).toEqual({
Expand All @@ -1285,7 +1289,9 @@ describe('LexicalEditor tests', () => {
__parent: 'root',
__prev: null,
__size: 1,
__style: '',
__textFormat: 0,
__textStyle: '',
__type: 'paragraph',
});
expect(parsedText).toEqual({
Expand Down Expand Up @@ -1351,6 +1357,7 @@ describe('LexicalEditor tests', () => {
__parent: null,
__prev: null,
__size: 1,
__style: '',
__type: 'root',
});
expect(parsedParagraph).toEqual({
Expand All @@ -1364,7 +1371,9 @@ describe('LexicalEditor tests', () => {
__parent: 'root',
__prev: null,
__size: 1,
__style: '',
__textFormat: 0,
__textStyle: '',
__type: 'paragraph',
});
expect(parsedText).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('LexicalEditorState tests', () => {
__parent: null,
__prev: null,
__size: 1,
__style: '',
__type: 'root',
});
expect(paragraph).toEqual({
Expand All @@ -75,7 +76,9 @@ describe('LexicalEditorState tests', () => {
__parent: 'root',
__prev: null,
__size: 1,
__style: '',
__textFormat: 0,
__textStyle: '',
__type: 'paragraph',
});
expect(text).toEqual({
Expand Down Expand Up @@ -110,7 +113,7 @@ describe('LexicalEditorState tests', () => {
});

expect(JSON.stringify(editor.getEditorState().toJSON())).toEqual(
`{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}`,
`{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}`,
);
});

Expand Down Expand Up @@ -145,6 +148,7 @@ describe('LexicalEditorState tests', () => {
__parent: null,
__prev: null,
__size: 0,
__style: '',
__type: 'root',
},
],
Expand Down
Loading
Loading