diff --git a/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts index 57ff4c1bfd5..ed4107097f5 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts @@ -1,22 +1,32 @@ import { getClosestAncestorBlockGroupIndex } from 'roosterjs-content-model-dom'; -import type { DeleteSelectionStep } from 'roosterjs-content-model-types'; +import type { DeleteSelectionStep, ContentModelListItem } from 'roosterjs-content-model-types'; /** * @internal */ export const deleteList: DeleteSelectionStep = context => { + if (context.deleteResult != 'notDeleted') { + return; + } + const { paragraph, marker, path } = context.insertPoint; - if (context.deleteResult == 'nothingToDelete' || context.deleteResult == 'notDeleted') { - const index = getClosestAncestorBlockGroupIndex(path, ['ListItem', 'TableCell']); - const item = path[index]; - if ( - item && - index >= 0 && - paragraph.segments[0] == marker && - item.blockGroupType == 'ListItem' - ) { - item.levels = []; + if (paragraph.segments[0] == marker) { + const index = getClosestAncestorBlockGroupIndex( + path, + ['ListItem'], + ['TableCell', 'FormatContainer'] + ); + const item = path[index] as ContentModelListItem | undefined; + const lastLevel = item?.levels[item.levels.length - 1]; + + if (lastLevel && item?.blocks[0] == paragraph) { + if (lastLevel.format.displayForDummyItem == 'block') { + item.levels.pop(); + } else { + lastLevel.format.displayForDummyItem = 'block'; + } + context.deleteResult = 'range'; } } diff --git a/packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts b/packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts index e5b487781f5..46d08fc71da 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts @@ -115,6 +115,7 @@ const createNewListLevel = (listItem: ContentModelListItem) => { { ...level.format, startNumberOverride: undefined, + displayForDummyItem: undefined, // When ENTER, we should create a new regular list item, so force its dummy item display to undefined }, level.dataset ); diff --git a/packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts b/packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts index b72cb8d0cff..e4fadcc1a1b 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts @@ -1,12 +1,12 @@ +import { deleteAllSegmentBefore } from './deleteSteps/deleteAllSegmentBefore'; +import { deleteEmptyQuote } from './deleteSteps/deleteEmptyQuote'; +import { deleteList } from './deleteSteps/deleteList'; import { ChangeSource, deleteSelection, isModifierKey, isNodeOfType, } from 'roosterjs-content-model-dom'; -import { deleteAllSegmentBefore } from './deleteSteps/deleteAllSegmentBefore'; -import { deleteEmptyQuote } from './deleteSteps/deleteEmptyQuote'; -import { deleteList } from './deleteSteps/deleteList'; import { handleKeyboardEventResult, shouldDeleteAllSegmentsBefore, @@ -73,8 +73,8 @@ function getDeleteSteps(rawEvent: KeyboardEvent, isMac: boolean): (DeleteSelecti return [ deleteAllSegmentBeforeStep, deleteWordSelection, + isForward ? null : deleteList, deleteCollapsedSelection, - deleteList, deleteQuote, ]; } diff --git a/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts index 721a853d875..4e98c4bc154 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts @@ -50,6 +50,81 @@ describe('deleteList', () => { normalizeContentModel(model); expect(result.deleteResult).toEqual('range'); + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + format: { + listStyleType: '"1. "', + }, + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + isImplicit: true, + }, + ], + levels: [ + { + listType: 'OL', + format: { + displayForDummyItem: 'block', + }, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: {}, + }, + }, + ], + }); + + const result2 = deleteSelection(model, [deleteList]); + normalizeContentModel(model); + expect(result2.deleteResult).toEqual('range'); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + isImplicit: false, + }, + ], + }); + + const result3 = deleteSelection(model, [deleteList]); + normalizeContentModel(model); + expect(result3.deleteResult).toEqual('notDeleted'); + expect(model).toEqual({ blockGroupType: 'Document', blocks: [ @@ -401,6 +476,89 @@ describe('deleteList', () => { }; const result = deleteSelection(model, [deleteList]); normalizeContentModel(model); + + expect(result.deleteResult).toBe('range'); + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Table', + rows: [ + { + height: 22, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + format: {}, + isImplicit: false, + }, + ], + levels: [ + { + listType: 'UL', + format: { + marginTop: '0px', + marginBottom: '0px', + listStyleType: 'disc', + displayForDummyItem: 'block', + }, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: {}, + }, + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: { + useBorderBox: true, + borderCollapse: true, + }, + widths: [120], + dataset: { + editingInfo: + '{"topBorderColor":"#ABABAB","bottomBorderColor":"#ABABAB","verticalBorderColor":"#ABABAB","hasHeaderRow":false,"hasFirstColumn":false,"hasBandedRows":false,"hasBandedColumns":false,"bgColorEven":null,"bgColorOdd":"#ABABAB20","headerRowColor":"#ABABAB","tableBorderFormat":0,"verticalAlign":"top"}', + }, + }, + ], + format: {}, + }); + + const result2 = deleteSelection(model, [deleteList]); + normalizeContentModel(model); + + expect(result2.deleteResult).toBe('range'); expect(model).toEqual({ blockGroupType: 'Document', blocks: [ @@ -453,7 +611,6 @@ describe('deleteList', () => { ], format: {}, }); - expect(result.deleteResult).toEqual('range'); }); it('delete list if the cursor is before text', () => { @@ -507,6 +664,60 @@ describe('deleteList', () => { const result = deleteSelection(model, [deleteList]); normalizeContentModel(model); expect(result.deleteResult).toEqual('range'); + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + format: { + listStyleType: '"1. "', + }, + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: 'text', + format: {}, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + isImplicit: true, + }, + ], + levels: [ + { + listType: 'OL', + format: { + displayForDummyItem: 'block', + }, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: {}, + }, + }, + ], + }); + + const result2 = deleteSelection(model, [deleteList]); + normalizeContentModel(model); + expect(result2.deleteResult).toEqual('range'); + expect(model).toEqual({ blockGroupType: 'Document', blocks: [ diff --git a/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts index e8b88ac05e7..3edb4b8fff5 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts @@ -301,6 +301,7 @@ describe('handleEnterOnList', () => { marginBottom: '0px', listStyleType: 'decimal', startNumberOverride: undefined, + displayForDummyItem: undefined, }, dataset: { editingInfo: '{"orderedStyleType":1}', @@ -436,6 +437,7 @@ describe('handleEnterOnList', () => { marginBottom: '0px', listStyleType: 'decimal', startNumberOverride: undefined, + displayForDummyItem: undefined, }, dataset: { editingInfo: '{"orderedStyleType":1}', @@ -817,6 +819,7 @@ describe('handleEnterOnList', () => { marginTop: '0px', marginBottom: '0px', startNumberOverride: undefined, + displayForDummyItem: undefined, }, dataset: { editingInfo: '{"orderedStyleType":10}', @@ -1898,6 +1901,7 @@ describe(' handleEnterOnList - keyboardInput', () => { marginBottom: '0px', listStyleType: 'decimal', startNumberOverride: undefined, + displayForDummyItem: undefined, }, dataset: { editingInfo: '{"orderedStyleType":1}', diff --git a/packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts b/packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts index 73aa831ef2c..9678665979c 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts @@ -90,7 +90,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], + [null!, null!, null!, forwardDeleteCollapsedSelection, null!], 'notDeleted', true, 0 @@ -108,7 +108,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], + [null!, null!, deleteList, backwardDeleteCollapsedSelection, deleteEmptyQuote], 'notDeleted', true, 0 @@ -128,7 +128,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, forwardDeleteWordSelection, forwardDeleteCollapsedSelection, deleteList, null!], + [null!, forwardDeleteWordSelection, null!, forwardDeleteCollapsedSelection, null!], 'notDeleted', true, 0 @@ -151,8 +151,8 @@ describe('keyboardDelete', () => { [ null!, backwardDeleteWordSelection, - backwardDeleteCollapsedSelection, deleteList, + backwardDeleteCollapsedSelection, deleteEmptyQuote, ], 'notDeleted', @@ -174,7 +174,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], + [null!, null!, null!, forwardDeleteCollapsedSelection, null!], 'notDeleted', true, 0 @@ -197,8 +197,8 @@ describe('keyboardDelete', () => { [ deleteAllSegmentBefore, null!, - backwardDeleteCollapsedSelection, deleteList, + backwardDeleteCollapsedSelection, deleteEmptyQuote, ], 'notDeleted', @@ -242,7 +242,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], + [null!, null!, null!, forwardDeleteCollapsedSelection, null!], 'notDeleted', true, 0 @@ -284,7 +284,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], + [null!, null!, deleteList, backwardDeleteCollapsedSelection, deleteEmptyQuote], 'notDeleted', true, 0 @@ -336,7 +336,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], + [null!, null!, null!, forwardDeleteCollapsedSelection, null!], 'singleChar', false, 1 @@ -388,7 +388,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], + [null!, null!, deleteList, backwardDeleteCollapsedSelection, deleteEmptyQuote], 'singleChar', false, 1 @@ -482,7 +482,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], + [null!, null!, deleteList, backwardDeleteCollapsedSelection, deleteEmptyQuote], 'singleChar', false, 1