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

[Breaking Change][lexical-list] Fix: Preserve original format after indenting list item #6912

Merged
merged 31 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f025eb6
Add text format to listitemnode
citruscai Dec 3, 2024
aa308fa
Merge branch 'main' into fix/listitem-format-indentation-bug
citruscai Dec 3, 2024
8540c74
Revert changes in LexicalSelection file
citruscai Dec 3, 2024
e58f48d
Merge branch 'fix/listitem-format-indentation-bug' of https://github.…
citruscai Dec 3, 2024
539c8bb
Change ListItemNode to inherit from ParagraphNode
citruscai Dec 3, 2024
9f36497
Add e2e test to fix list indent format bug fix
citruscai Dec 3, 2024
99e4428
Merge branch 'main' into fix/list-indention-format-loss
citruscai Dec 5, 2024
4d745ab
remove klass constructor line from lexicallistitemnode
citruscai Dec 5, 2024
14e4262
Merge branch 'fix/list-indention-format-loss' of https://github.com/c…
citruscai Dec 5, 2024
43b95a9
Adjust LexicalListItemNode class and use of it to the change of inher…
citruscai Dec 5, 2024
6b7d0c7
remove textformat from list utils
citruscai Dec 5, 2024
e381fae
Remove getformatflags method
citruscai Dec 5, 2024
815a7c9
update lexicalserialization with listitemnode changes
citruscai Dec 5, 2024
4472c72
Update packages/lexical-list/src/LexicalListItemNode.ts
citruscai Dec 6, 2024
249a7e4
Update packages/lexical-list/src/LexicalListItemNode.ts
citruscai Dec 6, 2024
ea7b3da
Update packages/lexical-list/src/LexicalListItemNode.ts
citruscai Dec 6, 2024
975765d
ensure backwards compatiability with Lexical list item node and parag…
citruscai Dec 6, 2024
1a64f01
Merge branch 'main' into fix/list-indention-format-loss
citruscai Dec 6, 2024
211963a
Resolve type errros after changes to paragraphnode
citruscai Dec 6, 2024
6fc604b
integrity checks more
citruscai Dec 6, 2024
bb08747
trying to fix integrity errors :(
citruscai Dec 6, 2024
8e0534a
more integrity errors fix :(
citruscai Dec 7, 2024
0bea28c
adjust unit test for LexicalSelection
citruscai Dec 7, 2024
bdc7f34
unit fix attempt 2.0 jesus christ help me please
citruscai Dec 7, 2024
b969c97
Apply suggestions from code review
citruscai Dec 8, 2024
670022c
Empty-Commit
citruscai Dec 8, 2024
905ccbb
Run prettier:fix
citruscai Dec 8, 2024
7674db6
Merge branch 'main' into fix/list-indention-format-loss
citruscai Dec 9, 2024
8dd65a1
Revert changes in insertList method in formatList
citruscai Dec 9, 2024
8a6ed71
Merge branch 'main' into fix/list-indention-format-loss
citruscai Dec 9, 2024
901c3d0
Merge branch 'main' into fix/list-indention-format-loss
zurfyx Dec 10, 2024
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
95 changes: 46 additions & 49 deletions packages/lexical-list/src/LexicalListItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,6 @@
*/

import type {ListNode, ListType} from './';
import type {
BaseSelection,
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
EditorThemeClasses,
LexicalNode,
NodeKey,
ParagraphNode,
RangeSelection,
SerializedElementNode,
Spread,
} from 'lexical';

import {
addClassNamesToElement,
Expand All @@ -29,11 +15,24 @@ import {
import {
$applyNodeReplacement,
$createParagraphNode,
$getSelection,
$isElementNode,
$isParagraphNode,
$isRangeSelection,
BaseSelection,
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
EditorThemeClasses,
ElementNode,
LexicalEditor,
LexicalNode,
NodeKey,
ParagraphNode,
RangeSelection,
SerializedParagraphNode,
Spread,
} from 'lexical';
import invariant from 'shared/invariant';
import normalizeClassNames from 'shared/normalizeClassNames';
Expand All @@ -47,11 +46,11 @@ export type SerializedListItemNode = Spread<
checked: boolean | undefined;
value: number;
},
SerializedElementNode
SerializedParagraphNode
>;

/** @noInheritDoc */
export class ListItemNode extends ElementNode {
export class ListItemNode extends ParagraphNode {
/** @internal */
__value: number;
/** @internal */
Expand Down Expand Up @@ -81,20 +80,18 @@ export class ListItemNode extends ElementNode {
$setListItemThemeClassNames(element, config.theme, this);
return element;
}
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
if (super.updateDOM(prevNode, dom, config)) {
return true;
}

updateDOM(
prevNode: ListItemNode,
dom: HTMLElement,
config: EditorConfig,
): boolean {
const parent = this.getParent();
if ($isListNode(parent) && parent.getListType() === 'check') {
updateListItemChecked(dom, this, prevNode, parent);
}
// @ts-expect-error - this is always HTMLListItemElement
dom.value = this.__value;
$setListItemThemeClassNames(dom, config.theme, this);

return false;
}

Expand Down Expand Up @@ -128,6 +125,12 @@ export class ListItemNode extends ElementNode {
node.setValue(serializedNode.value);
node.setFormat(serializedNode.format);
node.setDirection(serializedNode.direction);
if (typeof serializedNode.textFormat === 'number') {
node.setTextFormat(serializedNode.textFormat);
}
if (typeof serializedNode.textStyle === 'string') {
node.setTextStyle(serializedNode.textStyle);
}
return node;
}

Expand Down Expand Up @@ -224,15 +227,11 @@ export class ListItemNode extends ElementNode {
}

const siblings = this.getNextSiblings();

// Split the lists and insert the node in between them
listNode.insertAfter(node, restoreSelection);

if (siblings.length !== 0) {
const newListNode = $createListNode(listNode.getListType());

siblings.forEach((sibling) => newListNode.append(sibling));

node.insertAfter(newListNode, restoreSelection);
}

Expand All @@ -256,51 +255,49 @@ export class ListItemNode extends ElementNode {
}

insertNewAfter(
_: RangeSelection,
selection: RangeSelection,
restoreSelection = true,
): ListItemNode | ParagraphNode {
const newElement = $createListItemNode(
this.__checked == null ? undefined : false,
);

const format = selection.format;
newElement.setTextFormat(format);

newElement.setFormat(this.getFormatType());
this.insertAfter(newElement, restoreSelection);

return newElement;
}

collapseAtStart(selection: RangeSelection): true {
collapseAtStart(): boolean {
const selection = $getSelection();

if (!$isRangeSelection(selection)) {
return false;
}

const paragraph = $createParagraphNode();
const children = this.getChildren();
children.forEach((child) => paragraph.append(child));

const listNode = this.getParentOrThrow();
const listNodeParent = listNode.getParentOrThrow();
const isIndented = $isListItemNode(listNodeParent);
const listNodeParent = listNode.getParent();

if (!$isListNode(listNode)) {
return false;
}

if (listNode.getChildrenSize() === 1) {
if (isIndented) {
// if the list node is nested, we just want to remove it,
// effectively unindenting it.
if ($isListItemNode(listNodeParent)) {
listNode.remove();
listNodeParent.select();
} else {
listNode.insertBefore(paragraph);
listNode.remove();
// If we have selection on the list item, we'll need to move it
// to the paragraph
const anchor = selection.anchor;
const focus = selection.focus;
const key = paragraph.getKey();

if (anchor.type === 'element' && anchor.getNode().is(this)) {
anchor.set(key, anchor.offset, 'element');
}

if (focus.type === 'element' && focus.getNode().is(this)) {
focus.set(key, focus.offset, 'element');
}
paragraph.select();
}
} else {
listNode.insertBefore(paragraph);
this.remove();
}

return true;
Expand Down
11 changes: 4 additions & 7 deletions packages/lexical-list/src/formatList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ export function removeList(editor: LexicalEditor): void {

if ($isLeafNode(node)) {
const listItemNode = $getNearestNodeOfType(node, ListItemNode);

if (listItemNode != null) {
listNodes.add($getTopListNode(listItemNode));
}
Expand Down Expand Up @@ -479,11 +478,13 @@ export function $handleListInsertParagraph(): boolean {
return false;
}
// Only run this code on empty list items

const anchor = selection.anchor.getNode();

if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) {
return false;
}

const topListNode = $getTopListNode(anchor);
const parent = anchor.getParent();

Expand All @@ -493,8 +494,7 @@ export function $handleListInsertParagraph(): boolean {
);

const grandparent = parent.getParent();

let replacementNode;
let replacementNode: ElementNode;

if ($isRootOrShadowRoot(grandparent)) {
replacementNode = $createParagraphNode();
Expand All @@ -505,13 +505,12 @@ export function $handleListInsertParagraph(): boolean {
} else {
return false;
}

replacementNode.select();

const nextSiblings = anchor.getNextSiblings();

if (nextSiblings.length > 0) {
const newList = $createListNode(parent.getListType());

if ($isParagraphNode(replacementNode)) {
replacementNode.insertAfter(newList);
} else {
Expand All @@ -524,9 +523,7 @@ export function $handleListInsertParagraph(): boolean {
newList.append(sibling);
});
}

// Don't leave hanging nested empty lists
$removeHighestEmptyListParent(anchor);

return true;
}
3 changes: 1 addition & 2 deletions packages/lexical-list/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
*
*/

import type {LexicalNode, Spread} from 'lexical';

import {$findMatchingParent} from '@lexical/utils';
import {type LexicalNode, type Spread} from 'lexical';
import invariant from 'shared/invariant';

import {
Expand Down
45 changes: 45 additions & 0 deletions packages/lexical-playground/__tests__/e2e/List.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
redo,
selectAll,
selectCharacters,
toggleBold,
undo,
} from '../keyboardShortcuts/index.mjs';
import {
Expand Down Expand Up @@ -1881,4 +1882,48 @@ test.describe.parallel('Nested List', () => {
});
},
);
test('new list item should preserve format from previous list item even after new list item is indented', async ({
page,
}) => {
await focusEditor(page);
await toggleBulletList(page);
await toggleBold(page);
await page.keyboard.type('MLH Fellowship');
await page.keyboard.press('Enter');
await clickIndentButton(page);
await page.keyboard.type('Fall 2024');
await assertHTML(
page,
html`
<ul class="PlaygroundEditorTheme__ul">
<li
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
dir="ltr"
value="1">
<strong
class="PlaygroundEditorTheme__textBold"
data-lexical-text="true">
MLH Fellowship
</strong>
</li>
<li
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__nestedListItem"
value="2">
<ul class="PlaygroundEditorTheme__ul">
<li
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
dir="ltr"
value="1">
<strong
class="PlaygroundEditorTheme__textBold"
data-lexical-text="true">
Fall 2024
</strong>
</li>
</ul>
</li>
</ul>
`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,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,"textStyle":""}],"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,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
init();
const contentEditable = editor.getRootElement();
editor.setEditable(editable);
Expand Down
Loading
Loading