[lexical-playground][lexical-list] Bug Fix: Made checklist icon fully scalable, clickable, and properly spaced at large font sizes#7558
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
etrepum
left a comment
There was a problem hiding this comment.
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.
|
Made some changes, i have added a with zero-width space for caret in checklist items to fix that issue |
|
Tests are failing, and I don't think it's appropriate or necessary to change the markup to make this work correctly. |
|
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. |
|
Yes flex was causing this issue. |
etrepum
left a comment
There was a problem hiding this comment.
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;| dom.style.setProperty( | ||
| '--listitem-marker-font-size', | ||
| textStyleObj['font-size'], | ||
| ); | ||
| } else { | ||
| dom.style.removeProperty('font-size'); | ||
| dom.style.removeProperty('--listitem-marker-font-size'); |
There was a problem hiding this comment.
You don't need any of this, applyMarkerStyles manages the --listitem-marker custom properties
There was a problem hiding this comment.
Yes i removed that. Any other suggestions?
| 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 |
There was a problem hiding this comment.
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' };
etrepum
left a comment
There was a problem hiding this comment.
It doesn't look like this code was removed, at least not in what's on github right now
| // 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'); | ||
| } |
There was a problem hiding this comment.
| // 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; |
There was a problem hiding this comment.
| dom.style.cssText += nextStyle; | |
| dom.style.cssText = nextStyle; |
… scalable, clickable, and properly spaced at large font sizes (#7558)
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
Current behavior:
Changes introduced by this PR:
Closes #7554
Before
After
2025-05-22.03-59-39.mp4