From 93473ca7552fff0d97a8778b9a3f6f25046d7aa9 Mon Sep 17 00:00:00 2001 From: Bryan Valverde Date: Wed, 14 Oct 2026 14:31:54 -0600 Subject: [PATCH 1/4] Return false for Shift+Tab outdent when only entities precede the marker handleTabOnParagraph previously fell through without returning false when the segment immediately before the collapsed selection marker was not a Text segment (e.g. an Entity), causing preventDefault() to be called without actually outdenting. Now it checks whether all segments before the marker are entities and returns false so the browser's default Shift+Tab behavior runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a6a96dae-ddfc-4128-974c-c3d69eba91d2 --- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 10 +++++ .../edit/tabUtils/handleTabOnParagraphTest.ts | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts index 2fc802337679..524232749257 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -102,6 +102,16 @@ export function handleTabOnParagraph( if (markerIndex <= 0) { return false; } + + const segmentsBeforeMarker = paragraph.segments.slice(0, markerIndex); + const hasOnlyEntitiesBeforeMarker = segmentsBeforeMarker.every( + segment => segment.segmentType === 'Entity' + ); + + if (hasOnlyEntitiesBeforeMarker) { + return false; + } + const tabText = paragraph.segments[markerIndex - 1]; const tabSpacesLength = tabSpaces.length; diff --git a/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts index f770a1961107..55e647402a2f 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts @@ -332,6 +332,44 @@ describe('handleTabOnParagraph', () => { runTest(model, paragraph, rawEvent, true); }); + it('Outdent - collapsed range should return false when only entities exist before the marker', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Entity', + format: {}, + isReadonly: true, + wrapper: document.createElement('span'), + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + + runTest(model, paragraph, rawEvent, false); + }); + it('Indent - expanded range should return true', () => { const model: ContentModelDocument = { blockGroupType: 'Document', From 64d77af245bfc42567d73ba53550675142eee1c0 Mon Sep 17 00:00:00 2001 From: Bryan Valverde Date: Wed, 14 Oct 2026 16:02:45 -0600 Subject: [PATCH 2/4] Simplify entity-before-marker check to generic non-Text fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a6a96dae-ddfc-4128-974c-c3d69eba91d2 --- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts index 524232749257..6f3e3570a5d1 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -103,15 +103,6 @@ export function handleTabOnParagraph( return false; } - const segmentsBeforeMarker = paragraph.segments.slice(0, markerIndex); - const hasOnlyEntitiesBeforeMarker = segmentsBeforeMarker.every( - segment => segment.segmentType === 'Entity' - ); - - if (hasOnlyEntitiesBeforeMarker) { - return false; - } - const tabText = paragraph.segments[markerIndex - 1]; const tabSpacesLength = tabSpaces.length; @@ -127,6 +118,8 @@ export function handleTabOnParagraph( } else { return false; } + } else { + return false; } } } From f38380c5681b193e4bc13b8a4b74dd6f5e8a16d7 Mon Sep 17 00:00:00 2001 From: Bryan Valverde Date: Wed, 14 Oct 2026 16:08:09 -0600 Subject: [PATCH 3/4] Fix test type error: provide valid ContentModelEntity shape in handleTabOnParagraphTest Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a6a96dae-ddfc-4128-974c-c3d69eba91d2 --- .../test/edit/tabUtils/handleTabOnParagraphTest.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts index 55e647402a2f..a3ca2592b7c0 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts @@ -340,9 +340,14 @@ describe('handleTabOnParagraph', () => { blockType: 'Paragraph', segments: [ { + blockType: 'Entity', segmentType: 'Entity', format: {}, - isReadonly: true, + entityFormat: { + id: 'entity-1', + entityType: 'test', + isReadonly: true, + }, wrapper: document.createElement('span'), }, { From 2a79bf3654b2ee91819e18aa0133ba4ca1736317 Mon Sep 17 00:00:00 2001 From: Bryan Valverde Date: Thu, 15 Oct 2026 11:27:39 -0600 Subject: [PATCH 4/4] Fix browser-dependent font expectation in PowerPoint paste test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2253ec0d-4994-4f43-b8f7-bead705407aa --- .../test/paste/e2e/cmPasteFromPowerPointDesktopTest.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromPowerPointDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromPowerPointDesktopTest.ts index a514c3cbc56d..9e7b323a0512 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromPowerPointDesktopTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromPowerPointDesktopTest.ts @@ -34,6 +34,9 @@ describe('PowerPoint', () => { }); it('PowerPoint 1', () => { + const fontStyle = document.createElement('span').style; + fontStyle.fontFamily = 'Courier New'; + paste(editor!, pptClipboardContent1, 'normal'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(2); @@ -106,7 +109,7 @@ describe('PowerPoint', () => { isSelected: false, segmentType: 'SelectionMarker', format: { - fontFamily: '"Courier New"', + fontFamily: fontStyle.fontFamily, fontSize: '7pt', textColor: 'rgb(0, 0, 0)', },