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-list] Bullet item color matches text color #7024

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 0 additions & 1 deletion packages/lexical-list/src/LexicalListItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export class ListItemNode extends ElementNode {
// @ts-expect-error - this is always HTMLListItemElement
dom.value = this.__value;
$setListItemThemeClassNames(dom, config.theme, this);

return false;
}

Expand Down
49 changes: 48 additions & 1 deletion packages/lexical-list/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@

import type {SerializedListItemNode} from './LexicalListItemNode';
import type {ListType, SerializedListNode} from './LexicalListNode';
import type {LexicalCommand, LexicalEditor} from 'lexical';
import type {LexicalCommand, LexicalEditor, LexicalNode} from 'lexical';

import {mergeRegister} from '@lexical/utils';
import {
$getNodeByKey,
$getSelection,
$isRangeSelection,
COMMAND_PRIORITY_LOW,
createCommand,
INSERT_PARAGRAPH_COMMAND,
TextNode,
} from 'lexical';

import {
Expand Down Expand Up @@ -97,6 +101,49 @@ export function registerList(editor: LexicalEditor): () => void {
},
COMMAND_PRIORITY_LOW,
),
editor.registerMutationListener(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an editor.update in a mutation listener causes an update cascade, if this were directly updating the DOM style and not the node's state it wouldn't be a problem but this will cause a second reconciliation as implemented. Is there a reason why this can't work as a transform? I think it should be fine so long as you check to make sure that the style actually changed before setting it (and thus marking the node dirty again, causing a second iteration of the transform)

Copy link
Collaborator Author

@ivailop7 ivailop7 Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I did remove it, but it was throwing me the error that it couldn't find an activeEditor, I'll try again, in case it was an HMR thing, but I tried a few times. OK, will look into making this a transform vs mutationListener.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well if you are reading or updating the editor from a mutation listener then you do need a read or update call, it doesn't put you in a specific editor state because there are two to choose from. Updating is something that shouldn't happen in a mutation listener for the same reason that it shouldn't happen in an update listener (an update listener is basically a mutation listener for the whole editor state)

ListItemNode,
(mutations) => {
editor.update(() => {
for (const [key, type] of mutations) {
if (type !== 'destroyed') {
const node = $getNodeByKey<ListItemNode>(key);
const listItemElement = editor.getElementByKey(key);
if (node && listItemElement) {
const firstChild = node.getFirstChild<LexicalNode>();
if (firstChild) {
const textElement = editor.getElementByKey(
firstChild.getKey(),
);
if (textElement && textElement.style.cssText) {
listItemElement.setAttribute(
'style',
textElement.style.cssText,
);
}
} else {
const selection = $getSelection();
if (
$isRangeSelection(selection) &&
selection.isCollapsed() &&
selection.style
) {
listItemElement.setAttribute('style', selection.style);
}
}
}
}
}
});
},
{skipInitialization: false},
),
editor.registerNodeTransform(TextNode, (node) => {
const listItemParentNode = node.getParent();
if ($isListItemNode(listItemParentNode)) {
listItemParentNode.markDirty();
}
}),
);
return removeListener;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-playground/__tests__/e2e/List.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ test.describe.parallel('Nested List', () => {

await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul"><li value="1" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr" dir="ltr"><strong class="PlaygroundEditorTheme__textBold" style="color: rgb(208, 2, 27)" data-lexical-text="true">Hello</strong></li></ul><p class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" dir="ltr"><strong class="PlaygroundEditorTheme__textBold" style="color: rgb(208, 2, 27)" data-lexical-text="true">World</strong></p>',
'<ul class="PlaygroundEditorTheme__ul"><li value="1" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr" dir="ltr" style="color: rgb(208, 2, 27)"><strong class="PlaygroundEditorTheme__textBold" style="color: rgb(208, 2, 27)" data-lexical-text="true">Hello</strong></li></ul><p class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" dir="ltr"><strong class="PlaygroundEditorTheme__textBold" style="color: rgb(208, 2, 27)" data-lexical-text="true">World</strong></p>',
);
});

Expand Down
Loading