Skip to content

Commit

Permalink
Merge pull request #2556 from microsoft/u/juliaroldi/adjust-image-sel…
Browse files Browse the repository at this point in the history
…ection

Fix selection with ctrl+a
  • Loading branch information
juliaroldi authored Apr 4, 2024
2 parents 154d72d + 1377277 commit 12dc692
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

this.isSafari = !!env.isSafari;
this.isMac = !!env.isMac;

document.addEventListener('selectionchange', this.onSelectionChange);
if (this.isSafari) {
document.addEventListener('selectionchange', this.onSelectionChangeSafari);
this.disposer = this.editor.attachDomEvent({
focus: { beforeDispatch: this.onFocus },
drop: { beforeDispatch: this.onDrop },
Expand All @@ -76,9 +75,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}

dispose() {
this.editor
?.getDocument()
.removeEventListener('selectionchange', this.onSelectionChangeSafari);
this.editor?.getDocument().removeEventListener('selectionchange', this.onSelectionChange);

if (this.disposer) {
this.disposer();
Expand Down Expand Up @@ -522,13 +519,27 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
};

private onSelectionChangeSafari = () => {
private onSelectionChange = () => {
if (this.editor?.hasFocus() && !this.editor.isInShadowEdit()) {
// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
// So we always save a selection whenever editor has focus. Then after blur, we can still use this cached selection.
const newSelection = this.editor.getDOMSelection();

if (newSelection?.type == 'range') {
//If am image selection changed to a wider range due a keyboard event, we should update the selection
const selection = this.editor.getDocument().getSelection();
if (
newSelection?.type == 'image' &&
selection &&
selection.containsNode(newSelection.image, false /*partiallyContained*/)
) {
this.editor.setDOMSelection({
type: 'range',
range: selection.getRangeAt(0),
isReverted: false,
});
}

// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
// So we always save a selection whenever editor has focus. Then after blur, we can still use this cached selection.
if (newSelection?.type == 'range' && this.isSafari) {
this.state.selection = newSelection;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createDOMHelper } from '../../../lib/editor/core/DOMHelperImpl';
import { createSelectionPlugin } from '../../../lib/corePlugin/selection/SelectionPlugin';
import {
DOMEventRecord,
DOMSelection,
EditorPlugin,
IEditor,
PluginWithState,
Expand All @@ -14,8 +15,10 @@ describe('SelectionPlugin', () => {
const disposer = jasmine.createSpy('disposer');
const attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
const removeEventListenerSpy = jasmine.createSpy('removeEventListener');
const addEventListenerSpy = jasmine.createSpy('addEventListener');
const getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});
const state = plugin.getState();
const editor = ({
Expand Down Expand Up @@ -46,13 +49,14 @@ describe('SelectionPlugin', () => {
imageSelectionBorderColor: 'red',
});
const state = plugin.getState();

const addEventListenerSpy = jasmine.createSpy('addEventListener');
const attachDomEvent = jasmine
.createSpy('attachDomEvent')
.and.returnValue(jasmine.createSpy('disposer'));
const removeEventListenerSpy = jasmine.createSpy('removeEventListener');
const getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});

plugin.initialize(<IEditor>(<any>{
Expand Down Expand Up @@ -81,15 +85,18 @@ describe('SelectionPlugin handle onFocus and onBlur event', () => {
let getDocumentSpy: jasmine.Spy;
let setDOMSelectionSpy: jasmine.Spy;
let removeEventListenerSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

let editor: IEditor;

beforeEach(() => {
triggerEvent = jasmine.createSpy('triggerEvent');
getElementAtCursorSpy = jasmine.createSpy('getElementAtCursor');
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');

Expand Down Expand Up @@ -153,13 +160,16 @@ describe('SelectionPlugin handle image selection', () => {
let setDOMSelectionSpy: jasmine.Spy;
let getDocumentSpy: jasmine.Spy;
let createRangeSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

beforeEach(() => {
getDOMSelectionSpy = jasmine.createSpy('getDOMSelection');
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');
createRangeSpy = jasmine.createSpy('createRange');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
createRange: createRangeSpy,
addEventListener: addEventListenerSpy,
});

editor = {
Expand Down Expand Up @@ -557,6 +567,7 @@ describe('SelectionPlugin handle table selection', () => {
let mouseMoveDisposer: jasmine.Spy;
let requestAnimationFrameSpy: jasmine.Spy;
let getComputedStyleSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

beforeEach(() => {
contentDiv = document.createElement('div');
Expand All @@ -565,12 +576,14 @@ describe('SelectionPlugin handle table selection', () => {
createRangeSpy = jasmine.createSpy('createRange');
requestAnimationFrameSpy = jasmine.createSpy('requestAnimationFrame');
getComputedStyleSpy = jasmine.createSpy('getComputedStyle');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
createRange: createRangeSpy,
defaultView: {
requestAnimationFrame: requestAnimationFrameSpy,
getComputedStyle: getComputedStyleSpy,
},
addEventListener: addEventListenerSpy,
});
focusDisposer = jasmine.createSpy('focus');
mouseMoveDisposer = jasmine.createSpy('mouseMove');
Expand Down Expand Up @@ -1875,19 +1888,22 @@ describe('SelectionPlugin on Safari', () => {
let isInShadowEditSpy: jasmine.Spy;
let getDOMSelectionSpy: jasmine.Spy;
let editor: IEditor;
let getSelectionSpy: jasmine.Spy;

beforeEach(() => {
disposer = jasmine.createSpy('disposer');
appendChildSpy = jasmine.createSpy('appendChild');
attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getSelectionSpy = jasmine.createSpy('getSelection');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
head: {
appendChild: appendChildSpy,
},
addEventListener: addEventListenerSpy,
removeEventListener: removeEventListenerSpy,
getSelection: getSelectionSpy,
});
hasFocusSpy = jasmine.createSpy('hasFocus');
isInShadowEditSpy = jasmine.createSpy('isInShadowEdit');
Expand Down Expand Up @@ -2093,4 +2109,155 @@ describe('SelectionPlugin on Safari', () => {
});
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(0);
});

it('', () => {});
});

describe('SelectionPlugin selectionChange on image selected', () => {
let disposer: jasmine.Spy;
let appendChildSpy: jasmine.Spy;
let attachDomEvent: jasmine.Spy;
let removeEventListenerSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;
let getDocumentSpy: jasmine.Spy;
let hasFocusSpy: jasmine.Spy;
let isInShadowEditSpy: jasmine.Spy;
let getDOMSelectionSpy: jasmine.Spy;
let editor: IEditor;
let setDOMSelectionSpy: jasmine.Spy;
let containsNodeSpy: jasmine.Spy;
let getRangeAtSpy: jasmine.Spy;
let getSelectionSpy: jasmine.Spy;

beforeEach(() => {
disposer = jasmine.createSpy('disposer');
appendChildSpy = jasmine.createSpy('appendChild');
attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
containsNodeSpy = jasmine.createSpy('containsNode');
getRangeAtSpy = jasmine.createSpy('getRangeAt');
getSelectionSpy = jasmine.createSpy('getSelection').and.returnValue({
containsNode: containsNodeSpy,
getRangeAt: getRangeAtSpy,
});
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
head: {
appendChild: appendChildSpy,
},
addEventListener: addEventListenerSpy,
removeEventListener: removeEventListenerSpy,
getSelection: getSelectionSpy,
});
hasFocusSpy = jasmine.createSpy('hasFocus');
isInShadowEditSpy = jasmine.createSpy('isInShadowEdit');
getDOMSelectionSpy = jasmine.createSpy('getDOMSelection');
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');

editor = ({
getDocument: getDocumentSpy,
attachDomEvent,
getEnvironment: () => ({
isSafari: true,
}),
hasFocus: hasFocusSpy,
isInShadowEdit: isInShadowEditSpy,
getDOMSelection: getDOMSelectionSpy,
setDOMSelection: setDOMSelectionSpy,
} as any) as IEditor;
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'image',
image: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(true);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).toHaveBeenCalledWith({
type: 'range',
range: { startContainer: {} } as Range,
isReverted: false,
});
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'image',
image: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(false);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'range',
range: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(true);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});
});

0 comments on commit 12dc692

Please sign in to comment.