Skip to content

Commit fae88b3

Browse files
JiuqingSongjuliaroldidependabot[bot]CopilotBryanValverdeU
authored
Version bump to 9.48.0 (#3307)
* Fix publish file (#3267) * fix publish file * remove file * [Table Improvements] Add undoSnapshot when tab on a table cell (#3265) Add undoSnapshot after pressing Tab key in a table that has new content, otherwise if the user type content in a table and press tab to move to another cell and then undo the content, all the typed content will be removed. * [Table Improvements] Use keyboard to delete rows and columns (#3270) When press backspace or shift + delete when an entire row or column, delete the column and row. * [Table Improvements] Add Shift Cells Table Operation (#3271) Add new shift cells up and shift cells left table operations. These operations move the table cell content to the cells at left or above. * align table cell list (#3275) When apply alignment in table cells that has list items, also apply the alignment to the list items. * fill gaps (#3272) * Bump lodash from 4.17.21 to 4.17.23 (#3266) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jiuqing Song <[email protected]> * fix table format (#3277) When triggering clearFormat on table cells, do not clear the cell or the table format. * [Table Improvements] Add preview for table cell selection (#3274) When start shadow edit, check if table cells are selected, if they are selected, remove the background color to make the styles changes visible in the table. * Fix outdated JSDoc comments in setTableCellsStyle.ts (#3278) Fix JSDoc comments for removeTableCellsStyle function to match actual parameters Fix JSDoc comments for setTableCellsStyle function to match actual parameters * Fix 329516 (#3276) Co-authored-by: Bryan Valverde U <[email protected]> * [Table Improvements] Insert table content (#3258) When inserting a table in a range selection, insert the selected content inside the table. * Bump webpack from 5.94.0 to 5.104.1 (#3285) Bumps [webpack](https://github.com/webpack/webpack) from 5.94.0 to 5.104.1. - [Release notes](https://github.com/webpack/webpack/releases) - [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) - [Commits](webpack/webpack@v5.94.0...v5.104.1) --- updated-dependencies: - dependency-name: webpack dependency-version: 5.104.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Filter temporary EOP elements in Word Online paste and add test pattern support (#3283) * Filter temporary EOP elements in Word Online paste and add test pattern support - Skip elements with both 'Selected' and 'EOP' classes during WAC paste processing to remove temporary End of Paragraph markers - Add unit tests for EOP element filtering behavior (3 test cases) - Enhance test runner with --testPathPattern and --testNamePattern flags for faster targeted test execution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]> * Update packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Claude Sonnet 4.5 <[email protected]> Co-authored-by: Copilot <[email protected]> * Dark color improvement (#3279) * Dark color improvement * improve * Fix #3280 (#3282) * [Table Improvements] Ignore span cells when merge table cells (#3281) When merging table cells, count table that are span as one cell, so two or more cells cannot be merge to one single span cell. * Preserve cursor position when navigating tables (#3284) When pressing key up or key down in table cells that have text, preserve the cursor position. * Add adjustWordListMarginParser to fix duplicate list indentation when pasting from Word Desktop (#3288) Word Desktop pastes list items with full indentation as marginLeft on MsoListParagraph elements, which duplicates the paddingInlineStart (40px) already applied by the browser's default list styling. This parser subtracts the default 40px from the marginLeft of list items with the MsoListParagraph class to correct the indentation. - Add adjustWordListMarginParser using parseValueWithUnit for unit conversion - Register parser for listItemElement in processPastedContentFromWordDesktop - Update addParser call count expectations in pasteTest and ContentModelPastePluginTest - Add dedicated test case for the margin adjustment behavior * Remove mutations that is not under editor (#3290) Co-authored-by: Bryan Valverde U <[email protected]> * Preserve trailing space in last paragraph segment by converting to nbsp (#3235) (#3287) * Preserve trailing space in last paragraph segment by converting to nbsp When the last text segment in a paragraph ends with a regular space, browsers collapse it during rendering. This change detects that case in handleText and replaces the trailing space with a non-breaking space (\u00A0) so it is preserved in the output. To support this, a new ModelToDomSegmentContext interface is introduced that extends ModelToDomContext with an isLastSegment flag. handleParagraph sets this flag for each segment before dispatching, and ContentModelSegmentHandler is updated to use ModelToDomSegmentContext as its context type, eliminating the need for type casts in the handlers. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Improve trailing space to nbsp conversion with noFollowingTextSegmentOrLast Refactor handleParagraph to track whether a text segment is the last in the paragraph or has no following text segment (excluding SelectionMarkers). This ensures trailing spaces are converted to &nbsp; not only for the very last segment, but also when the next non-marker segment is not a Text segment. - Convert forEach to for loop in handleParagraph for segment iteration - Extract hasTextSegmentAfter helper to check for upcoming text segments - Add noFollowingTextSegmentOrLast property to ModelToDomSegmentContext - Update handleText to use the new property name - Fix stale isLastSegment references in handleTextTest - Add comprehensive tests for noFollowingTextSegmentOrLast in handleParagraphTest --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Copilot <[email protected]> * Do not invalidate cached model for ContentChangedEvent (#3291) * Bump minimatch from 3.1.2 to 3.1.5 (#3295) Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [Table Improvements] Add new customization properties for table format (#3293) * merge model * new table styles * fixes * add test * nit * nit * Bump immutable from 4.0.0 to 4.3.8 (#3298) Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.0.0 to 4.3.8. - [Release notes](https://github.com/immutable-js/immutable-js/releases) - [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md) - [Commits](immutable-js/immutable-js@v4.0.0...v4.3.8) --- updated-dependencies: - dependency-name: immutable dependency-version: 4.3.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix merge table error (#3302) When pasting table cells that are merged, counts only the logical cells and not all physical cells, otherwise, the number of pasted cells will appear bigger than then the actually copied * Add 'resolveImageSource' option to support cid image source for canvas editing (#3301) * Feature: support cid: image sources via resolveImageSource for reliable canvas editing * Fix format --------- Co-authored-by: Liang Meng <[email protected]> Co-authored-by: Jiuqing Song <[email protected]> * Fix #3292 Put B/I/U/S under hyperlink (#3297) * Fix #3292 * Update packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelFormatMap.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]> * Keep Margins in the List items, and set the padding of list elements for better paste interop with Wac Components (#3304) * Keep Margins in the List items, and set the padding of list elements for better paste interop with Word Components * Remove obsolete test * Fix cursor position for arrow up (#3305) * merge model * move table cursor * Version bump to 9.48.0 --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Julia Roldi <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]> Co-authored-by: Bryan Valverde U <[email protected]> Co-authored-by: Claude Sonnet 4.5 <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Liang <[email protected]> Co-authored-by: Liang Meng <[email protected]>
1 parent d98c5b3 commit fae88b3

File tree

29 files changed

+2301
-362
lines changed

29 files changed

+2301
-362
lines changed

demo/scripts/controlsV2/demoButtons/formatTableButton.ts

Lines changed: 269 additions & 104 deletions
Large diffs are not rendered by default.

packages/roosterjs-content-model-core/lib/corePlugin/cache/CachePlugin.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@ class CachePlugin implements PluginWithState<CachePluginState> {
115115

116116
if (contentModel) {
117117
updateCache(this.state, contentModel, selection);
118-
} else {
119-
this.invalidateCache();
120118
}
121119

122120
break;
@@ -165,8 +163,13 @@ class CachePlugin implements PluginWithState<CachePluginState> {
165163

166164
private invalidateCache() {
167165
if (!this.editor?.isInShadowEdit()) {
168-
this.state.cachedModel = undefined;
169-
this.state.cachedSelection = undefined;
166+
if (this.state.cachedModel) {
167+
this.state.cachedModel = undefined;
168+
}
169+
170+
if (this.state.cachedSelection) {
171+
this.state.cachedSelection = undefined;
172+
}
170173

171174
// Clear paragraph indexer to prevent stale references to old paragraphs
172175
// It will be rebuild next time when we create a new Content Model

packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
584584
doc,
585585
editor.getDOMHelper(),
586586
cursorRect.left,
587-
isKeyUp ? rect.top - 1 : rect.top + 1
587+
isKeyUp ? rect.top : rect.top + 1
588588
)
589589
: null;
590590
return textOffset;

packages/roosterjs-content-model-core/test/corePlugin/cache/CachePluginTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,14 +501,14 @@ describe('CachePlugin', () => {
501501
});
502502

503503
expect(state).toEqual({
504-
cachedModel: undefined,
504+
cachedModel: model,
505505
cachedSelection: undefined,
506506
paragraphMap: mockedParagraphMap,
507507
domIndexer: new DomIndexerImpl(),
508508
textMutationObserver: mockedObserver,
509509
});
510510
expect(reconcileSelectionSpy).not.toHaveBeenCalled();
511-
expect(resetMapSpy).toHaveBeenCalledTimes(1);
511+
expect(resetMapSpy).not.toHaveBeenCalled();
512512
});
513513

514514
it('No domIndexer, has model in event', () => {

packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2144,18 +2144,71 @@ describe('SelectionPlugin handle table selection', () => {
21442144
});
21452145

21462146
it('From Range, Press Up - preserves cursor horizontal position', () => {
2147-
// Setup: cursor is at position in td4, moving up to td2
2147+
// Setup: Create a 3x3 table for this test
2148+
// Cursor is at position in td9 (bottom-right), moving up to td6 (middle-right)
2149+
const testTable = document.createElement('table');
2150+
testTable.setAttribute('contenteditable', 'true');
2151+
const testTr1 = document.createElement('tr');
2152+
const testTr2 = document.createElement('tr');
2153+
const testTr3 = document.createElement('tr');
2154+
2155+
const testTd1 = document.createElement('td');
2156+
const testTd2 = document.createElement('td');
2157+
const testTd3 = document.createElement('td');
2158+
const testTd4 = document.createElement('td');
2159+
const testTd5 = document.createElement('td');
2160+
const testTd6 = document.createElement('td');
2161+
const testTd7 = document.createElement('td');
2162+
const testTd8 = document.createElement('td');
2163+
const testTd9 = document.createElement('td');
2164+
2165+
// Create text nodes for each cell
2166+
const testTd1_text = document.createTextNode('1');
2167+
const testTd2_text = document.createTextNode('2');
2168+
const testTd3_text = document.createTextNode('3');
2169+
const testTd4_text = document.createTextNode('4');
2170+
const testTd5_text = document.createTextNode('5');
2171+
const testTd6_text = document.createTextNode('6');
2172+
const testTd7_text = document.createTextNode('7');
2173+
const testTd8_text = document.createTextNode('8');
2174+
const testTd9_text = document.createTextNode('9');
2175+
2176+
// Add text to cells
2177+
testTd1.appendChild(testTd1_text);
2178+
testTd2.appendChild(testTd2_text);
2179+
testTd3.appendChild(testTd3_text);
2180+
testTd4.appendChild(testTd4_text);
2181+
testTd5.appendChild(testTd5_text);
2182+
testTd6.appendChild(testTd6_text);
2183+
testTd7.appendChild(testTd7_text);
2184+
testTd8.appendChild(testTd8_text);
2185+
testTd9.appendChild(testTd9_text);
2186+
2187+
// Build table structure
2188+
testTr1.appendChild(testTd1);
2189+
testTr1.appendChild(testTd2);
2190+
testTr1.appendChild(testTd3);
2191+
testTr2.appendChild(testTd4);
2192+
testTr2.appendChild(testTd5);
2193+
testTr2.appendChild(testTd6);
2194+
testTr3.appendChild(testTd7);
2195+
testTr3.appendChild(testTd8);
2196+
testTr3.appendChild(testTd9);
2197+
testTable.appendChild(testTr1);
2198+
testTable.appendChild(testTr2);
2199+
testTable.appendChild(testTr3);
2200+
contentDiv.appendChild(testTable);
21482201

21492202
// Mock getDOMInsertPointRect to return a cursor rect so getNodePositionFromEvent gets called
21502203
spyOn(getDOMInsertPointRectFile, 'getDOMInsertPointRect').and.returnValue({
2151-
left: 50,
2152-
right: 60,
2153-
top: 30,
2154-
bottom: 40,
2204+
left: 150,
2205+
right: 160,
2206+
top: 70,
2207+
bottom: 80,
21552208
});
21562209

2157-
// Mock getNodePositionFromEvent to return a specific position
2158-
const targetNode = td2_text;
2210+
// Mock getNodePositionFromEvent to return a specific position in td6 (middle row, third column)
2211+
const targetNode = testTd6_text;
21592212
const targetOffset = 1;
21602213
spyOn(getNodePositionFromEventFile, 'getNodePositionFromEvent').and.returnValue({
21612214
node: targetNode,
@@ -2165,25 +2218,26 @@ describe('SelectionPlugin handle table selection', () => {
21652218
getDOMSelectionSpy.and.returnValue({
21662219
type: 'range',
21672220
range: {
2168-
startContainer: td4_text,
2221+
startContainer: testTd9_text,
21692222
startOffset: 1,
2170-
endContainer: td4_text,
2223+
endContainer: testTd9_text,
21712224
endOffset: 1,
2172-
commonAncestorContainer: tr2,
2225+
commonAncestorContainer: testTr3,
21732226
collapsed: true,
21742227
},
21752228
isReverted: false,
21762229
});
21772230

21782231
requestAnimationFrameSpy.and.callFake((func: Function) => {
2232+
// After ArrowUp, browser might move to td4 (first column of middle row)
21792233
getDOMSelectionSpy.and.returnValue({
21802234
type: 'range',
21812235
range: {
2182-
startContainer: td1,
2236+
startContainer: testTd4,
21832237
startOffset: 0,
2184-
endContainer: td1,
2238+
endContainer: testTd4,
21852239
endOffset: 0,
2186-
commonAncestorContainer: tr1,
2240+
commonAncestorContainer: testTr2,
21872241
collapsed: true,
21882242
},
21892243
isReverted: false,
@@ -2197,24 +2251,24 @@ describe('SelectionPlugin handle table selection', () => {
21972251
const collapseSpy = jasmine.createSpy('collapse');
21982252
const getBoundingClientRectSpy = jasmine
21992253
.createSpy('getBoundingClientRect')
2200-
.and.returnValue({ left: 50, right: 60, top: 30, bottom: 40 });
2254+
.and.returnValue({ left: 150, right: 160, top: 70, bottom: 80 });
22012255
const mockedRange = {
22022256
setStart: setStartSpy,
22032257
setEnd: setEndSpy,
22042258
collapse: collapseSpy,
22052259
getBoundingClientRect: getBoundingClientRectSpy,
2206-
startContainer: td4_text,
2260+
startContainer: testTd9_text,
22072261
startOffset: 1,
22082262
} as any;
22092263

22102264
createRangeSpy.and.returnValue(mockedRange);
22112265

2212-
// Mock td2's getBoundingClientRect
2213-
spyOn(td2, 'getBoundingClientRect').and.returnValue({
2214-
left: 40,
2215-
right: 100,
2216-
top: 5,
2217-
bottom: 25,
2266+
// Mock td6's getBoundingClientRect (target cell in middle row, third column)
2267+
spyOn(testTd6, 'getBoundingClientRect').and.returnValue({
2268+
left: 140,
2269+
right: 200,
2270+
top: 35,
2271+
bottom: 55,
22182272
} as DOMRect);
22192273

22202274
plugin.onPluginEvent!({
@@ -2227,6 +2281,7 @@ describe('SelectionPlugin handle table selection', () => {
22272281
expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(1);
22282282
expect(setDOMSelectionSpy).toHaveBeenCalledTimes(1);
22292283
// Verify that setStart is called with the position returned by getNodePositionFromEvent
2284+
// Cursor should be placed in td6 (same column as td9) preserving horizontal position
22302285
expect(setStartSpy).toHaveBeenCalledWith(targetNode, targetOffset);
22312286
});
22322287

packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,17 @@ const styleBasedSegmentFormats: (keyof FormatHandlerTypeMap)[] = [
109109
'fontSize',
110110
];
111111

112-
const elementBasedSegmentFormats: (keyof FormatHandlerTypeMap)[] = [
112+
const sizeNeutralElementBasedSegmentFormats: (keyof FormatHandlerTypeMap)[] = [
113113
'strike',
114114
'underline',
115-
'superOrSubScript',
116115
'italic',
117116
'bold',
118117
];
118+
119+
const elementBasedSegmentFormats: (keyof FormatHandlerTypeMap)[] = [
120+
...sizeNeutralElementBasedSegmentFormats,
121+
'superOrSubScript',
122+
];
119123
const sharedBlockFormats: (keyof FormatHandlerTypeMap)[] = [
120124
'direction',
121125
'textAlign',
@@ -142,7 +146,8 @@ export const defaultFormatKeysPerCategory: {
142146
listItemElement: [...sharedBlockFormats, 'listItemAlign', 'margin', 'listStyle'],
143147
listLevel: ['direction', 'textAlign', 'margin', 'padding', 'listStyle', 'backgroundColor'],
144148
styleBasedSegment: [...styleBasedSegmentFormats, 'textColor', 'backgroundColor', 'lineHeight'],
145-
elementBasedSegment: elementBasedSegmentFormats,
149+
elementBasedSegment: sizeNeutralElementBasedSegmentFormats,
150+
superOrSubScript: ['superOrSubScript'],
146151
segment: [
147152
...styleBasedSegmentFormats,
148153
...elementBasedSegmentFormats,

0 commit comments

Comments
 (0)