diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts index b9245e0c0f9..d2aaf74b2be 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts @@ -22,9 +22,11 @@ import type { TableSelectionInfo, TableCellCoordinate, RangeSelection, + MouseUpEvent, } from 'roosterjs-content-model-types'; const MouseLeftButton = 0; +const MouseMiddleButton = 1; const MouseRightButton = 2; const Up = 'ArrowUp'; const Down = 'ArrowDown'; @@ -140,7 +142,7 @@ class SelectionPlugin implements PluginWithState { break; case 'mouseUp': - this.onMouseUp(); + this.onMouseUp(this.editor, event); break; case 'keyDown': @@ -164,30 +166,46 @@ class SelectionPlugin implements PluginWithState { let image: HTMLImageElement | null; // Image selection - if ( - selection?.type == 'image' && - (rawEvent.button == MouseLeftButton || - (rawEvent.button == MouseRightButton && - !this.getClickingImage(rawEvent) && - !this.getContainedTargetImage(rawEvent, selection))) - ) { - this.setDOMSelection(null /*domSelection*/, null /*tableSelection*/); - } + if (editor.isExperimentalFeatureEnabled('LegacyImageSelection')) { + if ( + rawEvent.button === MouseRightButton && + (image = + this.getClickingImage(rawEvent) ?? + this.getContainedTargetImage(rawEvent, selection)) && + image.isContentEditable + ) { + this.selectImageWithRange(image, rawEvent); + return; + } else if (selection?.type == 'image' && selection.image !== rawEvent.target) { + this.selectBeforeOrAfterElement(editor, selection.image); + return; + } + } else { + if ( + selection?.type == 'image' && + (rawEvent.button == MouseLeftButton || + (rawEvent.button == MouseRightButton && + !this.getClickingImage(rawEvent) && + !this.getContainedTargetImage(rawEvent, selection))) + ) { + this.setDOMSelection(null /*domSelection*/, null /*tableSelection*/); + } - if ( - (image = - this.getClickingImage(rawEvent) ?? - this.getContainedTargetImage(rawEvent, selection)) && - image.isContentEditable - ) { - this.setDOMSelection( - { - type: 'image', - image: image, - }, - null - ); - return; + if ( + (image = + this.getClickingImage(rawEvent) ?? + this.getContainedTargetImage(rawEvent, selection)) && + image.isContentEditable + ) { + this.setDOMSelection( + { + type: 'image', + image: image, + }, + null + ); + return; + } } // Table selection @@ -228,6 +246,25 @@ class SelectionPlugin implements PluginWithState { } } + private selectImageWithRange(image: HTMLImageElement, event: Event) { + const range = image.ownerDocument.createRange(); + range.selectNode(image); + + const domSelection = this.editor?.getDOMSelection(); + if (domSelection?.type == 'image' && image == domSelection.image) { + event.preventDefault(); + } else { + this.setDOMSelection( + { + type: 'range', + isReverted: false, + range, + }, + null + ); + } + } + private onMouseMove = (event: Event) => { if (this.editor && this.state.tableSelection) { const hasTableSelection = !!this.state.tableSelection.lastCo; @@ -288,7 +325,21 @@ class SelectionPlugin implements PluginWithState { } }; - private onMouseUp() { + private onMouseUp(editor: IEditor, event: MouseUpEvent) { + let image: HTMLImageElement | null; + + if ( + editor.isExperimentalFeatureEnabled('LegacyImageSelection') && + (image = this.getClickingImage(event.rawEvent)) && + image.isContentEditable && + event.rawEvent.button != MouseMiddleButton && + (event.rawEvent.button == + MouseRightButton /* it's not possible to drag using right click */ || + event.isClicking) + ) { + this.selectImageWithRange(image, event.rawEvent); + } + this.detachMouseEvent(); } diff --git a/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts index e2243a23ec1..e2584338b5d 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts @@ -370,6 +370,9 @@ describe('SelectionPlugin handle image selection', () => { getColorManager: () => ({ getDarkColor: (color: string) => `${DEFAULT_DARK_COLOR_SUFFIX_COLOR}${color}`, }), + isExperimentalFeatureEnabled: () => { + return false; + }, } as any; plugin = createSelectionPlugin({}); plugin.initialize(editor); @@ -765,6 +768,9 @@ describe('SelectionPlugin handle table selection', () => { } }, announce: announceSpy, + isExperimentalFeatureEnabled: () => { + return false; + }, } as any; plugin = createSelectionPlugin({}); plugin.initialize(editor); @@ -2246,6 +2252,9 @@ describe('SelectionPlugin on Safari', () => { getColorManager: () => ({ getDarkColor: (color: string) => `${DEFAULT_DARK_COLOR_SUFFIX_COLOR}${color}`, }), + isExperimentalFeatureEnabled: () => { + return false; + }, } as any) as IEditor; }); diff --git a/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts b/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts index bdd12a56d22..15159013fd9 100644 --- a/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts +++ b/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts @@ -8,4 +8,8 @@ export type ExperimentalFeature = * When this feature is enabled, we will persist a content model in memory as long as we can, * and use cached element when write back if it is not changed. */ - 'PersistCache'; + | 'PersistCache' + /** + * Workaround for the Legacy Image Edit + */ + | 'LegacyImageSelection';