diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts index 1e9085c302b..b6dd6a3b73b 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -47,6 +47,7 @@ const initialState: OptionState = { autoOrdinals: true, autoMailto: true, autoTel: true, + removeListMargins: false, }, markdownOptions: { bold: true, diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx index d1ba83fee58..c9e83bc06b0 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx @@ -112,6 +112,7 @@ export class Plugins extends PluginsBase { private autoOrdinals = React.createRef(); private autoTel = React.createRef(); private autoMailto = React.createRef(); + private removeListMargins = React.createRef(); private markdownBold = React.createRef(); private markdownItalic = React.createRef(); private markdownStrikethrough = React.createRef(); @@ -180,6 +181,13 @@ export class Plugins extends PluginsBase { this.props.state.autoFormatOptions.autoMailto, (state, value) => (state.autoFormatOptions.autoMailto = value) )} + {this.renderCheckBox( + 'Remove List Margins', + this.removeListMargins, + this.props.state.autoFormatOptions.removeListMargins, + (state, value) => + (state.autoFormatOptions.removeListMargins = value) + )} )} {this.renderPluginItem( diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index 13c35dedff2..b47bc496fdc 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -28,6 +28,7 @@ const DefaultOptions: Partial = { autoHyphen: false, autoFraction: false, autoOrdinals: false, + removeListMargins: false, }; /** @@ -40,6 +41,7 @@ export class AutoFormatPlugin implements EditorPlugin { * @param options An optional parameter that takes in an object of type AutoFormatOptions, which includes the following properties: * - autoBullet: A boolean that enables or disables automatic bullet list formatting. Defaults to false. * - autoNumbering: A boolean that enables or disables automatic numbering formatting. Defaults to false. + * - removeListMargins: A boolean to remove list margins when it is automatically triggered. Defaults to false. * - autoHyphen: A boolean that enables or disables automatic hyphen transformation. Defaults to false. * - autoFraction: A boolean that enables or disables automatic fraction transformation. Defaults to false. * - autoOrdinals: A boolean that enables or disables automatic ordinal number transformation. Defaults to false. @@ -125,6 +127,7 @@ export class AutoFormatPlugin implements EditorPlugin { autoOrdinals, autoMailto, autoTel, + removeListMargins, } = this.options; let shouldHyphen = false; let shouldLink = false; @@ -138,7 +141,8 @@ export class AutoFormatPlugin implements EditorPlugin { paragraph, context, autoBullet, - autoNumbering + autoNumbering, + removeListMargins ); } @@ -211,7 +215,11 @@ export class AutoFormatPlugin implements EditorPlugin { formatTextSegmentBeforeSelectionMarker( editor, (model, _previousSegment, paragraph, _markerFormat, context) => { - const { autoBullet, autoNumbering } = this.options; + const { + autoBullet, + autoNumbering, + removeListMargins, + } = this.options; let shouldList = false; if (autoBullet || autoNumbering) { shouldList = keyboardListTrigger( @@ -219,7 +227,8 @@ export class AutoFormatPlugin implements EditorPlugin { paragraph, context, autoBullet, - autoNumbering + autoNumbering, + removeListMargins ); context.canUndoByBackspace = shouldList; } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/interface/AutoFormatOptions.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/interface/AutoFormatOptions.ts index 49b941092c2..ad814cede15 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/interface/AutoFormatOptions.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/interface/AutoFormatOptions.ts @@ -28,4 +28,9 @@ export interface AutoFormatOptions extends AutoLinkOptions { * Transform ordinal numbers into superscript */ autoOrdinals?: boolean; + + /** + * Remove the margins of auto triggered list + */ + removeListMargins?: boolean; } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts index 08686fbe122..15c9755e698 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts @@ -21,13 +21,14 @@ export function keyboardListTrigger( paragraph: ShallowMutableContentModelParagraph, context: FormatContentModelContext, shouldSearchForBullet: boolean = true, - shouldSearchForNumbering: boolean = true + shouldSearchForNumbering: boolean = true, + removeListMargins?: boolean ) { const listStyleType = getListTypeStyle(model, shouldSearchForBullet, shouldSearchForNumbering); if (listStyleType) { paragraph.segments.splice(0, 1); const { listType, styleType, index } = listStyleType; - triggerList(model, listType, styleType, index); + triggerList(model, listType, styleType, index, removeListMargins); context.canUndoByBackspace = true; setAnnounceData(model, context); @@ -40,9 +41,10 @@ const triggerList = ( model: ReadonlyContentModelDocument, listType: 'OL' | 'UL', styleType: number, - index?: number + index?: number, + removeListMargins?: boolean ) => { - setListType(model, listType); + setListType(model, listType, removeListMargins); const isOrderedList = listType == 'OL'; if (index && index > 0 && isOrderedList) { setModelListStartNumber(model, index); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index 05aa2ed03a9..2c08e079405 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -378,6 +378,73 @@ describe('Content Model Auto Format Plugin Test', () => { false ); }); + + it('should trigger keyboardListTrigger with no margins', () => { + const event: EditorInputEvent = { + eventType: 'input', + rawEvent: { data: ' ', defaultPrevented: false, inputType: 'insertText' } as any, + }; + runTest( + event, + true, + { + blockGroupType: 'Document', + format: {}, + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + levels: [ + { + listType: 'UL', + format: { + startNumberOverride: 1, + direction: undefined, + textAlign: undefined, + marginBottom: '0px', + marginTop: '0px', + }, + dataset: { + editingInfo: + '{"applyListStyleFromLevel":false,"unorderedStyleType":1}', + }, + }, + ], + + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: { + fontFamily: undefined, + fontSize: undefined, + textColor: undefined, + }, + }, + format: {}, + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + marker, + { + segmentType: 'Br', + format: {}, + }, + ], + }, + ], + }, + ], + }, + { + autoBullet: true, + autoNumbering: true, + removeListMargins: true, + }, + true + ); + }); }); describe('onPluginEvent - [TAB] - keyboardListTrigger', () => { diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts index efcc84ef7e9..8e97d289388 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts @@ -13,14 +13,16 @@ describe('keyboardListTrigger', () => { expectedResult: boolean, shouldSearchForBullet: boolean = true, shouldSearchForNumbering: boolean = true, - expectedContext?: any + expectedContext?: any, + removeListMargins?: boolean ) { const result = keyboardListTrigger( model, paragraph, context, shouldSearchForBullet, - shouldSearchForNumbering + shouldSearchForNumbering, + removeListMargins ); expect(result).toBe(expectedResult); if (expectedContext) { @@ -542,4 +544,120 @@ describe('keyboardListTrigger', () => { } ); }); + + it('trigger a new numbering list after a numbering list no margins', () => { + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'A)', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }; + runTest( + { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: { + editingInfo: '{"orderedStyleType":3}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: {}, + }, + format: { + listStyleType: '"1) "', + }, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: { + editingInfo: '{"orderedStyleType":3}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: {}, + }, + format: { + listStyleType: '"2) "', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Br', + format: {}, + }, + ], + format: {}, + }, + paragraph, + ], + format: {}, + }, + paragraph, + { canUndoByBackspace: true } as any, + true, + undefined /* shouldSearchForBullet */, + undefined /* shouldSearchForNumbering */, + { + canUndoByBackspace: true, + announceData: { defaultStrings: 'announceListItemNumbering', formatStrings: ['A'] }, + }, + true /* removeListMargins */ + ); + }); });