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 3 commits into
base: main
Choose a base branch
from

Conversation

ivailop7
Copy link
Collaborator

@ivailop7 ivailop7 commented Jan 7, 2025

Most text editors do bullet lists with the marker color matching the font color in that list item. Investigated a few editors and how they handle different colors in the same list item.
They do differ in behaviour quite a bit apparently.

One of the popular editors, follows the color of the last text node. Another editor, follows the color only if all the text nodes inside the bullet have the same font color. A third one doesn't support it at all.

In the end, I decided to go with the first one, it's kind of the standard behaviour most people expect. Also because it's easiest to reason about and implement. I'm also unsure if we should do first node or last node. Personally, I think the first text node makes more sense vs the last.

Before:

Screen.Recording.2025-01-07.at.23.31.29.mov

After:

Screen.Recording.2025-01-12.at.22.45.22.mov

Copy link

vercel bot commented Jan 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lexical ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 14, 2025 11:39pm
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 14, 2025 11:39pm

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jan 7, 2025
Copy link

github-actions bot commented Jan 7, 2025

size-limit report 📦

Path Size
lexical - cjs 29.02 KB (0%)
lexical - esm 28.84 KB (0%)
@lexical/rich-text - cjs 37.97 KB (0%)
@lexical/rich-text - esm 30.9 KB (0%)
@lexical/plain-text - cjs 36.54 KB (0%)
@lexical/plain-text - esm 28.15 KB (0%)
@lexical/react - cjs 39.77 KB (0%)
@lexical/react - esm 32.26 KB (0%)

@ivailop7
Copy link
Collaborator Author

ivailop7 commented Jan 7, 2025

There is the pseudoclass '::marker' that can isolate the color change to the marker only, but I didn't find a nice way to set this via javascript.

@ivailop7
Copy link
Collaborator Author

ivailop7 commented Jan 7, 2025

@etrepum @zurfyx for ideas and comments

@etrepum
Copy link
Collaborator

etrepum commented Jan 8, 2025

This strategy is easy to implement but I don't think it's correct, IIRC updateDOM for parent nodes happens before the child nodes.

You couldn't do this with CSS and something like color: var(--marker-color) and set that from js (possibly from a mutation listener I guess, that would happen after everything is reconciled including children)?

@ivailop7
Copy link
Collaborator Author

ivailop7 commented Jan 8, 2025

This strategy is easy to implement but I don't think it's correct, IIRC updateDOM for parent nodes happens before the child nodes.

You couldn't do this with CSS and something like color: var(--marker-color) and set that from js (possibly from a mutation listener I guess, that would happen after everything is reconciled including children)?

This sounds like a better idea. I wanted to avoid another listener just for this, but I guess even updateDom itself is just another listener implementation. Will give it a go.

@etrepum
Copy link
Collaborator

etrepum commented Jan 8, 2025

You may also want to use getComputedStyle since the text could be colored by means other than setting the style attribute directly (although of course that's not how the playground works today, it may be a bit more robust in other settings).

@fantactuka
Copy link
Contributor

Curious why lastChild vs firstChild (given it's closer to the marker)

@etrepum
Copy link
Collaborator

etrepum commented Jan 8, 2025

In Google Docs it like the marker color is based on what the text color was when the marker was created, but you can change it if you apply a color to its entire contents at once (e.g. changing the left half from black to blue, then changing the right half to blue, will not change the marker to blue, but selecting all of the text and changing it to blue at once will). This would mean storing the color on the list item itself and updating it accordingly, rather than just doing DOM stuff.

@ivailop7
Copy link
Collaborator Author

ivailop7 commented Jan 8, 2025

Curious why lastChild vs firstChild (given it's closer to the marker)

I agree it should be the first node, other editors opted for last one, thus I went with this. Probably because of their design. Will polish this sometime over the weekend.

@ivailop7
Copy link
Collaborator Author

@etrepum @fantactuka updated the implementaion and the after video in the description. Behaviour is better, because it fixed also matching the marker size to the font-size. That said, still unsure if this is still the most efficient way. Because I have to listen to TextNode changes for the font color/font size. I'm unsure if the cost added on every TextNode 'update' is worth adding this weight. The CSS var approach didn't work, because after updating the variable, it would change the color of all bullets to the latest variable color value, and if having to override the variable value per list item in style is essentially the same overhead.

Copy link
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

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

This seems mostly reasonable but maybe it should be exposed as a separate register function so that people can choose to use this behavior or not? They might have different ideas for how this should work and it'll be very tricky to untangle it unless it's a separate call.

packages/lexical-list/src/index.ts Outdated Show resolved Hide resolved
Comment on lines 113 to 115
if ($isRangeSelection(selection)) {
listItemElement.setAttribute('style', selection.style || '');
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably check to see if it's the selection that created the ListItemNode, it could be from paste or whatever. I would probably just have the created and updated work the same way, with this selection case to cover scenarios where there is no text element child

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This should probably check to see if it's the selection that created the ListItemNode, it could be from paste or whatever. I would probably just have the created and updated work the same way, with this selection case to cover scenarios where there is no text element child

Implement the other things, but I'm not sure I'm getting the "if it's the selection that created the ListItemNode" part. Are you referring to handling PASTE and/or SELECTION_CHANGE commands or something else?

Copy link
Collaborator

Choose a reason for hiding this comment

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

A created mutation event happens the first time a node is rendered to DOM for whatever reason, so the style of the editor's current selection might not really have anything to do with that newly rendered ListItemNode. It is mostly only relevant to when you press return or some other UI action to create a new ListItemNode. It's reasonable to assume that case should pick up the style. In that case the selection will almost certainly be collapsed inside the ListItemNode.

It's hard to imagine what all of the other scenarios that would render a new ListItemNode could be, paste is certainly one of them. Selection change probably isn't.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you were to always copy the style of the selection to an empty ListItemNode so long as the selection is collapsed inside of it then I think it would also work well, it doesn't need to only be applied on create (haven't looked at the code again but that could possibly simplify some other stuff?)

@etrepum
Copy link
Collaborator

etrepum commented Jan 16, 2025

Unit tests are failing, looks like they are not expecting style="" to be on these nodes. Might be worth some special casing to avoid putting style="" in the DOM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants