diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts index a5114412de0..962291b3479 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts @@ -44,7 +44,7 @@ export const handleBlockGroupChildren: ContentModelHandler 0) { // Clear list stack, only run to nodeStack[1] because nodeStack[0] is the parent node for (let i = nodeStack.length - 1; i > 0; i--) { @@ -61,5 +65,13 @@ function cleanUpNodeStack(nodeStack: ModelToDomListStackItem[], context: ModelTo cleanUpRestNodes(node, context.rewriteFromModel); } + + if (leavingParent && nodeStack[0].node == leavingParent) { + // When leaving a parent node that is the same with the root of node stack + // It means the whole list node stack is being invalidated, so we clear it + while (nodeStack.length > 0) { + nodeStack.pop(); + } + } } } diff --git a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts index ed79cbfaa9a..774ad5ed7f3 100644 --- a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts @@ -507,4 +507,55 @@ describe('handleBlockGroupChildren', () => { expect(node1.parentNode).toBeNull(); expect(node2.parentNode).toBeNull(); }); + + it('Allow list cache: clear whole node stack when leaving parent that is root of node stack', () => { + const group = createContentModelDocument(); + const listItem = createListItem([ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ]); + + // The list item is the only (and last) block, and its list element is a direct + // child of parent. So while handling it, handleList pushes parent as the root of + // the node stack (nodeStack[0].node === parent). When the loop finishes and we + // leave parent, the whole node stack should be cleared instead of keeping the root. + group.blocks.push(listItem); + + handleBlockGroupChildren(document, parent, group, context); + + expect(context.listFormat.nodeStack).toEqual([]); + expect(parent.outerHTML).toBe('
'); + expect(context.rewriteFromModel).toEqual({ + addedBlockElements: [parent.firstChild!.firstChild as HTMLElement], + removedBlockElements: [], + }); + }); + + it('Allow list cache: keep node stack root when leaving a different parent', () => { + const group = createContentModelDocument(); + const listItem = createListItem([ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ]); + + // The root of the node stack is some other node, not the parent we are leaving. + // In this case the cleanup should keep the root entry (nodeStack[0]) and only + // pop the inner list levels. + const otherRoot = document.createElement('div'); + const nodeStack = [{ node: otherRoot, refNode: null } as any]; + + group.blocks.push(listItem); + context.listFormat.nodeStack = nodeStack; + + handleBlockGroupChildren(document, parent, group, context); + + expect(context.listFormat.nodeStack).toBe(nodeStack); + expect(context.listFormat.nodeStack).toEqual([{ node: otherRoot, refNode: null } as any]); + }); });