Repository: tinymce/tinymce
Pull request #10344: TINY-12102: Fix new list item formatting
Author: login: michalnieruchalski-tiugo
Created: 2025-05-09T14:13:02Z | Merged: 2025-05-22T09:05:36Z

Related Ticket: TINY-12102

Description of Changes:
* New list item was not inserted correctly, if existing list item had <p> tag  inside.

Pre-checks:
* [x] Changelog entry added
* [x] Tests have been added (if applicable)
* [x] Branch prefixed with `feature/`, `hotfix/` or `spike/`

Review:
* [x] Milestone set

GitHub issues (if applicable):


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Resolved an issue where inserting a new list item failed when the existing item contained a block element, ensuring correct insertion in such cases.

- **Tests**
  - Added new tests to verify correct behavior when pressing Enter inside paragraphs and nested elements within list items.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml b/.changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml
new file mode 100644
index 00000000000..314bd6058b1
--- /dev/null
+++ b/.changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml
@@ -0,0 +1,6 @@
+project: tinymce
+kind: Fixed
+body: New list item was not inserted correctly when existing list item had a block element inside.
+time: 2025-05-12T15:19:38.897889+02:00
+custom:
+  Issue: TINY-12102
diff --git a/modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts b/modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts
index 73f84f961a7..45d0a7a26fc 100644
--- a/modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts
+++ b/modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts
@@ -1,5 +1,6 @@
 import { Arr, Fun, Obj, Optional, Optionals, Unicode } from '@ephox/katamari';
-import { Css, SugarElement } from '@ephox/sugar';
+import { DomDescent } from '@ephox/phoenix';
+import { Css, Insert, SugarElement } from '@ephox/sugar';
 
 import DOMUtils from '../api/dom/DOMUtils';
 import DomTreeWalker from '../api/dom/TreeWalker';
@@ -35,9 +36,13 @@ const moveToCaretPosition = (editor: Editor, root: Node): void => {
 
   if (/^(LI|DT|DD)$/.test(root.nodeName)) {
     const firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
-
     if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
       root.insertBefore(dom.doc.createTextNode(Unicode.nbsp), root.firstChild);
+    } else if (firstChild && dom.isEmpty(firstChild)) {
+      const element = DomDescent.toLeaf(SugarElement.fromDom(firstChild), 0).element;
+      if (!ElementType.isBr(element)) {
+        Insert.append(element, SugarElement.fromText(Unicode.nbsp));
+      }
     }
   }
 
diff --git a/modules/tinymce/src/core/test/ts/browser/newline/NewlineInLiTest.ts b/modules/tinymce/src/core/test/ts/browser/newline/NewlineInLiTest.ts
new file mode 100644
index 00000000000..b989fb52c71
--- /dev/null
+++ b/modules/tinymce/src/core/test/ts/browser/newline/NewlineInLiTest.ts
@@ -0,0 +1,89 @@
+import { Keys } from '@ephox/agar';
+import { describe, it } from '@ephox/bedrock-client';
+import { TinyAssertions, TinyHooks, TinySelections, TinyContentActions } from '@ephox/wrap-mcagar';
+
+import Editor from 'tinymce/core/api/Editor';
+
+describe('browser.tinymce.core.newline.NewlineInLiTest', () => {
+  const hook = TinyHooks.bddSetupLight<Editor>({
+    base_url: '/project/tinymce/js/tinymce'
+  }, [], true);
+
+  it('TINY-12102: pressing enter in p inside li should insert nbsp in new li', () => {
+    const editor = hook.editor();
+    editor.setContent(
+      '<ul>'
+        + '<li><p>One</p></li>'
+        + '<li>'
+          + '<p>Two</p>'
+          + '<ul>'
+            + '<li>One sub</li>'
+            + '<li>Two sub</li>'
+          + '</ul>'
+        + '</li>'
+      + '</ul>'
+    );
+    TinySelections.setCursor(editor, [ 0, 1, 0, 0 ], 'Two'.length);
+    TinyContentActions.keydown(editor, Keys.enter());
+
+    const expectedContent = [ '<ul>',
+      '<li>',
+      '<p>One</p>',
+      '</li>',
+      '<li>',
+      '<p>Two</p>',
+      '</li>',
+      '<li>',
+      '<p>&nbsp;</p>',
+      '<ul>',
+      '<li>One sub</li>',
+      '<li>Two sub</li>',
+      '</ul>',
+      '</li>',
+      '</ul>' ].join('\n');
+    TinyAssertions.assertContent(editor, expectedContent);
+    TinyAssertions.assertCursor(editor, [ 0, 2, 0, 0 ], 0);
+  });
+
+  it('TINY-12102: pressing enter in multiple nested spans in p inside li should insert nbsp in new li', () => {
+    const editor = hook.editor();
+    editor.setContent(
+      '<ul>'
+        + '<li><p>One</p></li>'
+        + '<li>'
+         + '<p>'
+            + '<span style="font-size: 24pt;">'
+              + '<span style="background: red;">'
+                + 'Two'
+              + '</span>'
+            + '</span>'
+          + '</p>'
+          + '<ul>'
+            + '<li>One sub</li>'
+            + '<li>Two sub</li>'
+          + '</ul>'
+        + '</li>'
+      + '</ul>'
+    );
+    TinySelections.setCursor(editor, [ 0, 1, 0, 0, 0, 0 ], 'Two'.length);
+    TinyContentActions.keydown(editor, Keys.enter());
+
+    const expectedContent = [ '<ul>',
+      '<li>',
+      '<p>One</p>',
+      '</li>',
+      '<li>',
+      '<p><span style="font-size: 24pt;"><span style="background: red;">Two</span></span></p>',
+      '</li>',
+      '<li>',
+      '<p><span style="font-size: 24pt;"><span style="background: red;">&nbsp;</span></span></p>',
+      '<ul>',
+      '<li>One sub</li>',
+      '<li>Two sub</li>',
+      '</ul>',
+      '</li>',
+      '</ul>' ].join('\n');
+    TinyAssertions.assertContent(editor, expectedContent);
+    TinyAssertions.assertCursor(editor, [ 0, 2, 0, 0, 0, 0 ], 0);
+  });
+});


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-09T14:17:51Z
user: login: spocke
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T05:17:45Z
user: login: spocke
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T05:19:14Z
user: login: TheSpyder
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T06:40:26Z
user: login: michalnieruchalski-tiugo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T10:35:17Z
user: login: spocke
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T05:43:39Z
user: login: TheSpyder
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T03:38:50Z

Review comments:
diff_hunk: @@ -10,6 +10,7 @@
 import * as NodeType from '../dom/NodeType';
 import * as ScrollIntoView from '../dom/ScrollIntoView';
 import { isCaretNode } from '../fmt/FormatContainer';
+import * as CaretFinder from '../caret/CaretFinder';
path: modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts
user: login: github-advanced-security[bot]
body: ## Unused variable, import, function or class

Unused import CaretFinder.

[Show more details](https://github.com/tinymce/tinymce/security/code-scanning/142)
created_at: 2025-05-09T14:17:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+project: tinymce
+kind: Fixed
+body: New list item was not inserted correctly, if existing list item had <p> tag
path: .changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml
user: login: spocke
body: Is this wider than `p` does it happen on other blocks like `div` etc?
If so maybe it needs to be: 
```suggestion
body: New list item was not inserted correctly when existing list item had a block element inside.
```
created_at: 2025-05-14T05:16:33Z
author_association: MEMBER
diff_hunk: @@ -33,9 +33,13 @@ const moveToCaretPosition = (editor: Editor, root: Node): void => {
 
   if (/^(LI|DT|DD)$/.test(root.nodeName)) {
     const firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
-
     if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
       root.insertBefore(dom.doc.createTextNode(Unicode.nbsp), root.firstChild);
+    } else if (firstChild && dom.isEmpty(firstChild)) {
+      const element = Traverse.leftmostLeaf(SugarElement.fromDom(firstChild));
+      if (SugarNode.name(element) !== 'br') {
path: modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts
user: login: spocke
body: There is a ElementType.isBr that you could use. There is a warning in that file to not use the predicates but I think that should be for group of things that needs to be handled by the schema.
created_at: 2025-05-14T05:19:13Z
author_association: MEMBER
diff_hunk: @@ -125,6 +125,9 @@ const leaf = (element: SugarElement<Node>, offset: number): ElementAndOffset<Nod
   return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
 };
 
+const leftmostLeaf = (element: SugarElement<Node>): SugarElement<Node> =>
+  firstChild(element).fold(Fun.constant(element), leftmostLeaf);
+
path: modules/sugar/src/main/ts/ephox/sugar/api/search/Traverse.ts
user: login: TheSpyder
body: I think `robin` already has a method for this somewhere, finding the deepest text node or something like that. Used for grabbing words.
created_at: 2025-05-15T06:40:11Z
author_association: MEMBER
diff_hunk: @@ -125,6 +125,9 @@ const leaf = (element: SugarElement<Node>, offset: number): ElementAndOffset<Nod
   return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
 };
 
+const leftmostLeaf = (element: SugarElement<Node>): SugarElement<Node> =>
+  firstChild(element).fold(Fun.constant(element), leftmostLeaf);
+
path: modules/sugar/src/main/ts/ephox/sugar/api/search/Traverse.ts
user: login: michalnieruchalski-tiugo
body: I've tried to find similar method in `robin` but I failed. 
What method did you have in mind?

created_at: 2025-05-15T10:35:17Z
author_association: COLLABORATOR
diff_hunk: @@ -125,6 +125,9 @@ const leaf = (element: SugarElement<Node>, offset: number): ElementAndOffset<Nod
   return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
 };
 
+const leftmostLeaf = (element: SugarElement<Node>): SugarElement<Node> =>
+  firstChild(element).fold(Fun.constant(element), leftmostLeaf);
+
path: modules/sugar/src/main/ts/ephox/sugar/api/search/Traverse.ts
user: login: spocke
body: This is trying to grab the left most deep element not the lest from deep text node so I think they are different. But I wonder if it's to custom for having in sugar. 🤔 
created_at: 2025-05-19T05:43:39Z
author_association: MEMBER
diff_hunk: @@ -125,6 +125,9 @@ const leaf = (element: SugarElement<Node>, offset: number): ElementAndOffset<Nod
   return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
 };
 
+const leftmostLeaf = (element: SugarElement<Node>): SugarElement<Node> =>
+  firstChild(element).fold(Fun.constant(element), leftmostLeaf);
+
path: modules/sugar/src/main/ts/ephox/sugar/api/search/Traverse.ts
user: login: TheSpyder
body: It's in `phoenix`, sorry, not `robin`. I always get those two confused. The `DomDescent` API.

It can search for the first viable text node, that's `DomDescent.freefallLtr`. But also has `DomDescent.toLeaf` which is exactly this function when the offset is zero. It returns a spot point, use the `element` property of that to get the result.
created_at: 2025-05-22T03:35:23Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-09T14:13:10Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change addresses an issue in TinyMCE where inserting a new list item failed if the current list item contained a block element, such as a paragraph. The update modifies caret positioning logic and adds new tests to verify correct behavior when pressing Enter inside complex list items.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml                                   | Added a changelog entry summarizing the fix for inserting new list items when the current item contains a block element.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| modules/tinymce/src/core/main/ts/newline/NewLineUtils.ts                                | Updated caret positioning logic in the `moveToCaretPosition` function. Now, if the first child of a list item is empty (and not a `<br>`), a non-breaking space is appended inside it. Expanded imports to include necessary Sugar modules.                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| modules/tinymce/src/core/test/ts/browser/newline/NewlineInLiTest.ts                     | Added a new test suite to verify Enter key behavior inside paragraphs and deeply nested spans within list items. Tests assert correct splitting of list items and preservation of formatting when inserting new items.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Editor
    participant NewLineUtils

    User->>Editor: Presses Enter inside a block element within <li>
    Editor->>NewLineUtils: Calls moveToCaretPosition
    NewLineUtils->>NewLineUtils: Checks if first child is empty and not <br>
    alt First child is empty and not <br>
        NewLineUtils->>NewLineUtils: Append non-breaking space to leftmost leaf
    end
    NewLineUtils-->>Editor: Updates caret and DOM
    Editor-->>User: Displays new list item with correct caret position
```

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**
**Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 475f695b6d2b3ead888b912456de32b2ea3f9b18 and 7813a1930d1c1e24414b41afcb06b378664a7c5b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `.changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* .changes/unreleased/tinymce-TINY-12102-2025-05-12.yaml

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Analyze (javascript)

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=tinymce/tinymce&utm_content=10344):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: michalnieruchalski-tiugo
created_at: 2025-05-09T14:13:19Z
body: * **#10344** <a href="https://app.graphite.dev/github/pr/tinymce/tinymce/10344?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/tinymce/tinymce/10344?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: COLLABORATOR