Skip to content

[lexical-playground][lexical-list] Bug Fix: Made checklist icon fully scalable, clickable, and properly spaced at large font sizes#7558

Merged
etrepum merged 17 commits intofacebook:mainfrom
swrsaini:main
May 29, 2025
Merged

[lexical-playground][lexical-list] Bug Fix: Made checklist icon fully scalable, clickable, and properly spaced at large font sizes#7558
etrepum merged 17 commits intofacebook:mainfrom
swrsaini:main

Conversation

@swrsaini
Copy link
Copy Markdown
Contributor

Current behavior:

  • The checklist icon (checkbox) does not scale properly with large font sizes.
  • The clickable area of the checkbox does not cover the entire icon at larger sizes, making it hard to toggle.
  • The checkmark inside the icon remains small and is not visually balanced.
  • The margin between the icon and the checklist item text is inconsistent at larger sizes.

Changes introduced by this PR:

  • Ensured the checklist icon and its checkmark scale proportionally with the font size of the checklist item.
  • Made the entire icon area fully clickable, regardless of size.
  • Improved the visual alignment and thickness of the checkmark for better visibility.
  • Fixed the margin between the icon and the item text for consistent spacing at all sizes.

Closes #7554

Before

  • Checklist icon and checkmark did not scale with large font sizes.
  • Only part of the icon was clickable at large sizes.
  • Margin between icon and text was inconsistent.
    Screenshot 2025-05-22 040524

After

  • Checklist icon and checkmark scale smoothly with font size.
  • The entire icon is clickable, even at large sizes.
  • Margin between icon and text is consistent and visually balanced.
2025-05-22.03-59-39.mp4

@vercel
Copy link
Copy Markdown

vercel bot commented May 21, 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 May 28, 2025 7:35pm
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 28, 2025 7:35pm

Copy link
Copy Markdown
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.

The CSS changes here make the text caret invisible when there is no text in the list item (at least in Chrome 136.0.7103.114 macOS). Probably the flex? If you turn off flex the cursor is there but in the wrong place, because without a span element there's no margin to move it away from the checkbox.

@swrsaini
Copy link
Copy Markdown
Contributor Author

Made some changes, i have added a with zero-width space for caret in checklist items to fix that issue

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented May 23, 2025

Tests are failing, and I don't think it's appropriate or necessary to change the markup to make this work correctly.

@swrsaini
Copy link
Copy Markdown
Contributor Author

I removed the span tag that was causing DOM manipulation and replaced it with a zero-width space to handle the caret position. All test cases have passed successfully.

@swrsaini
Copy link
Copy Markdown
Contributor Author

Yes flex was causing this issue.

Copy link
Copy Markdown
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 works well in the playground but there are going to be edge cases with the way that the css is manipulated here, the custom properties I originally suggested would be a more robust approach. Here's a diff you can try:

commit a351b7aceac9c97f4c8a9ca47dd5aa35dce8f1e0
Author: Bob Ippolito <bob@redivi.com>
Date:   Wed May 28 10:51:18 2025 -0700

    Use css custom property instead of style text

diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts
index 25b28cc92..253163258 100644
--- a/packages/lexical-list/src/LexicalListItemNode.ts
+++ b/packages/lexical-list/src/LexicalListItemNode.ts
@@ -95,58 +95,41 @@ export class ListItemNode extends ElementNode {
 
   createDOM(config: EditorConfig): HTMLElement {
     const element = document.createElement('li');
-    const parent = this.getParent();
-    if ($isListNode(parent) && parent.getListType() === 'check') {
-      updateListItemChecked(element, this, null, parent);
-    }
-    element.value = this.__value;
-    $setListItemThemeClassNames(element, config.theme, this);
-    const nextStyle = this.__style;
-
-    // Merge font-size from textStyle into <li> style if present
-    const textStyleObj = getStyleObjectFromCSS(this.__textStyle);
-    if (textStyleObj['font-size']) {
-      element.style.fontSize = textStyleObj['font-size'];
-    }
-    if (nextStyle) {
-      element.style.cssText += nextStyle;
-    }
-
-    applyMarkerStyles(element, this, null);
-
+    this.updateListItemDOM(null, element, config);
     return element;
   }
 
-  updateDOM(
-    prevNode: ListItemNode,
-    dom: HTMLElement,
+  updateListItemDOM(
+    prevNode: ListItemNode | null,
+    dom: HTMLLIElement,
     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);
-    const prevStyle = prevNode.__style;
+    const prevStyle = prevNode ? prevNode.__style : '';
     const nextStyle = this.__style;
-
-    // Merge font-size from textStyle into <li> style if present
-    const textStyleObj = getStyleObjectFromCSS(this.__textStyle);
-    if (textStyleObj['font-size']) {
-      dom.style.fontSize = textStyleObj['font-size'];
-    } else {
-      dom.style.removeProperty('font-size');
-    }
     if (prevStyle !== nextStyle) {
       if (nextStyle === '') {
         dom.removeAttribute('style');
       } else {
-        dom.style.cssText += nextStyle;
+        dom.style.cssText = nextStyle;
       }
     }
     applyMarkerStyles(dom, this, prevNode);
+  }
+
+  updateDOM(
+    prevNode: ListItemNode,
+    dom: HTMLElement,
+    config: EditorConfig,
+  ): boolean {
+    // @ts-expect-error - this is always HTMLListItemElement
+    const element: HTMLLIElement = dom;
+    this.updateListItemDOM(prevNode, element, config);
     return false;
   }
 
diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
index d82d1aad3..d68b9dcbc 100644
--- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
+++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
@@ -453,6 +453,9 @@
 }
 .PlaygroundEditorTheme__listItem {
   margin: 0 32px;
+  font-family: var(--listitem-marker-font-family);
+  font-size: var(--listitem-marker-font-size);
+  background-color: var(--listitem-marker-background-color);
 }
 .PlaygroundEditorTheme__listItem::marker {
   color: var(--listitem-marker-color);
@@ -462,7 +465,6 @@
 }
 .PlaygroundEditorTheme__listItemChecked,
 .PlaygroundEditorTheme__listItemUnchecked {
-  font-size: inherit;
   position: relative;
   margin-left: 0.5em;
   margin-right: 0.5em;

Comment on lines +121 to +126
dom.style.setProperty(
'--listitem-marker-font-size',
textStyleObj['font-size'],
);
} else {
dom.style.removeProperty('font-size');
dom.style.removeProperty('--listitem-marker-font-size');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You don't need any of this, applyMarkerStyles manages the --listitem-marker custom properties

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes i removed that. Any other suggestions?

Comment on lines +185 to +214
const beforeStyles = window.getComputedStyle(target, '::before');

const beforeWidth = beforeStyles.width;
const beforeWidthInPixels = parseFloat(beforeWidth);
let iconRect = rect;
if (window.getComputedStyle) {
const style = window.getComputedStyle(target, '::before');
const width = parseFloat(style.width);
const height = parseFloat(style.height);
if (!isNaN(width) && !isNaN(height)) {
iconRect = {
bottom: rect.top + height,
height,
left: rect.left,
right: rect.left + width,
toJSON: () => ({
bottom: rect.top + height,
height,
left: rect.left,
right: rect.left + width,
top: rect.top,
width,
}),
top: rect.top,
width,
x: rect.left,
y: rect.top,
};
}
}
if (
target.dir === 'rtl'
? pageX < rect.right && pageX > rect.right - beforeWidthInPixels
: pageX > rect.left && pageX < rect.left + beforeWidthInPixels
? pageX < iconRect.right && pageX > iconRect.left
: pageX > iconRect.left && pageX < iconRect.right
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The previous code in here was simpler (by not computing unnecessary things) and had the same purpose, if you needed the window.getComputedStyle check you could do something like:

const beforeStyles = window.getComputedStyle ? window.getComputedStyle(target, '::before') : { width: '0px' };

Copy link
Copy Markdown
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.

It doesn't look like this code was removed, at least not in what's on github right now

Comment on lines +118 to +127
// Set custom property for font-size from textStyle if present
const textStyleObj = getStyleObjectFromCSS(this.__textStyle);
if (textStyleObj['font-size']) {
dom.style.setProperty(
'--listitem-marker-font-size',
textStyleObj['font-size'],
);
} else {
dom.style.removeProperty('--listitem-marker-font-size');
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Set custom property for font-size from textStyle if present
const textStyleObj = getStyleObjectFromCSS(this.__textStyle);
if (textStyleObj['font-size']) {
dom.style.setProperty(
'--listitem-marker-font-size',
textStyleObj['font-size'],
);
} else {
dom.style.removeProperty('--listitem-marker-font-size');
}

dom.removeAttribute('style');
} else {
dom.style.cssText = nextStyle;
dom.style.cssText += nextStyle;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
dom.style.cssText += nextStyle;
dom.style.cssText = nextStyle;

Copy link
Copy Markdown
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.

Looks good!

@etrepum etrepum added this pull request to the merge queue May 29, 2025
Merged via the queue into facebook:main with commit 3ee0245 May 29, 2025
38 checks passed
fantactuka pushed a commit that referenced this pull request Aug 11, 2025
… scalable, clickable, and properly spaced at large font sizes (#7558)
GermanJablo added a commit to payloadcms/payload that referenced this pull request Sep 3, 2025
Fixes #13386

Below I write a clarification to copy and paste into the release note,
based on our latest upgrade of Lexical [in
v3.29.0](https://github.com/payloadcms/payload/releases/tag/v3.29.0).

## Important
This release upgrades the lexical dependency from 0.28.0 to 0.34.0.

If you installed lexical manually, update it to 0.34.0. Installing
lexical manually is not recommended, as it may break between updates,
and our re-exported versions should be used. See the [yellow banner
box](https://payloadcms.com/docs/rich-text/custom-features) for details.

If you still encounter richtext-lexical errors, do the following, in
this order:

- Delete node_modules
- Delete your lockfile (e.g. pnpm-lock.json)
- Reinstall your dependencies (e.g. pnpm install)

### Lexical Breaking Changes

The following Lexical releases describe breaking changes. We recommend
reading them if you're using Lexical APIs directly
(`@payloadcms/richtext-lexical/lexical/*`).

- [v.0.33.0](https://github.com/facebook/lexical/releases/tag/v0.33.0)
- [v.0.30.0](https://github.com/facebook/lexical/releases/tag/v0.30.0)
- [v.0.29.0](https://github.com/facebook/lexical/releases/tag/v0.29.0)

___

TODO:
- [x] facebook/lexical#7719
- [x] facebook/lexical#7362
- [x] facebook/lexical#7707
- [x] facebook/lexical#7388
- [x] facebook/lexical#7357
- [x] facebook/lexical#7352
- [x] facebook/lexical#7472
- [x] facebook/lexical#7556
- [x] facebook/lexical#7417
- [x] facebook/lexical#1036
- [x] facebook/lexical#7509
- [x] facebook/lexical#7693
- [x] facebook/lexical#7408
- [x] facebook/lexical#7450
- [x] facebook/lexical#7415
- [x] facebook/lexical#7368
- [x] facebook/lexical#7372
- [x] facebook/lexical#7572
- [x] facebook/lexical#7558
- [x] facebook/lexical#7613
- [x] facebook/lexical#7405
- [x] facebook/lexical#7420
- [x] facebook/lexical#7662

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211202581885926
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. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: checklist size doesnt increase with font size

3 participants