Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Mouseout behavior to hide table editors #2181

Merged
merged 5 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import TableEditor from './editors/TableEditor';
import { normalizeRect, safeInstanceOf } from 'roosterjs-editor-dom';
import { contains, normalizeRect, safeInstanceOf } from 'roosterjs-editor-dom';
import { PluginEventType } from 'roosterjs-editor-types';
import type {
CreateElementData,
Expand Down Expand Up @@ -52,17 +52,18 @@ export default class TableResize implements EditorPlugin {
this.editor = editor;
this.onMouseMoveDisposer = this.editor.addDomEventHandler({
mousemove: this.onMouseMove,
mouseout: e => this.onMouseOut(e),
});
const scrollContainer = this.editor.getScrollContainer();
scrollContainer.addEventListener('mouseout', this.onMouseOut);
}

private onMouseOut = (ev: Event) => {
private onMouseOut = ({ relatedTarget, currentTarget }: MouseEvent) => {
if (
isMouseEvent(ev) &&
safeInstanceOf(ev.relatedTarget, 'HTMLElement') &&
safeInstanceOf(relatedTarget, 'HTMLElement') &&
safeInstanceOf(currentTarget, 'HTMLElement') &&
this.tableEditor &&
!this.tableEditor.isOwnedElement(ev.relatedTarget) &&
!this.editor?.contains(ev.relatedTarget)
!this.tableEditor.isOwnedElement(relatedTarget) &&
!contains(currentTarget, relatedTarget)
) {
this.setTableEditor(null);
}
Expand All @@ -72,6 +73,8 @@ export default class TableResize implements EditorPlugin {
* Dispose this plugin
*/
dispose() {
const scrollContainer = this.editor?.getScrollContainer();
scrollContainer?.removeEventListener('mouseout', this.onMouseOut);
this.onMouseMoveDisposer?.();
this.invalidateTableRects();
this.disposeTableEditor();
Expand Down Expand Up @@ -129,7 +132,7 @@ export default class TableResize implements EditorPlugin {
this.tableEditor?.onMouseMove(x, y);
};

private setTableEditor(table: HTMLTableElement | null, e?: MouseEvent) {
public setTableEditor(table: HTMLTableElement | null, e?: MouseEvent) {
BryanValverdeU marked this conversation as resolved.
Show resolved Hide resolved
if (this.tableEditor && !this.tableEditor.isEditing() && table != this.tableEditor.table) {
this.disposeTableEditor();
}
Expand Down Expand Up @@ -176,7 +179,3 @@ export default class TableResize implements EditorPlugin {
}
}
}

function isMouseEvent(e: Event): e is MouseEvent {
return !!(e as MouseEvent).pageX;
}
264 changes: 264 additions & 0 deletions packages/roosterjs-editor-plugins/test/TableResize/tableResizeTest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as Contains from 'roosterjs-editor-dom/lib/utils/contains';
import * as TestHelper from 'roosterjs-editor-api/test/TestHelper';
import { createElement } from 'roosterjs-editor-dom';
import { DEFAULT_TABLE, DEFAULT_TABLE_MERGED, EXCEL_TABLE, WORD_TABLE } from './tableData';
import { TableResize } from '../../lib/TableResize';
import {
Expand Down Expand Up @@ -715,3 +717,265 @@ xdescribe('Table Resizer/Inserter tests', () => {
expect(pluginName).toBe(expectedName);
});
});

describe('TableResize', () => {
let editor: IEditor;
let plugin: TableResize;
const TEST_ID = 'inserterTest';

let mouseOutListener: undefined | ((this: HTMLElement, ev: MouseEvent) => any);

beforeEach(() => {
editor = TestHelper.initEditor(TEST_ID);
plugin = new TableResize();

spyOn(editor, 'getScrollContainer').and.returnValue(<HTMLElement>(<any>{
addEventListener: <K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
) => {
if (type == 'mouseout') {
mouseOutListener = listener as (this: HTMLElement, ev: MouseEvent) => any;
}
},
removeEventListener: <K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | EventListenerOptions
) => {
if (type == 'mouseout') {
mouseOutListener = undefined;
}
},
}));
plugin.initialize(editor);
});

afterEach(() => {
plugin.dispose();
editor.dispose();
TestHelper.removeElement(TEST_ID);
document.body = document.createElement('body');
});

it('Dismiss table editor on mouse out', () => {
const ele = createElement(
{
tag: 'div',
children: [
{
tag: 'div',
children: ['asd'],
},
],
},
editor.getDocument()
);
const table = createElement(
{
tag: 'table',
children: [
{
tag: 'tr',
children: [
{
tag: 'td',
children: ['Test'],
},
],
},
],
},
editor.getDocument()
) as HTMLTableElement;
editor.insertNode(table);

spyOn(plugin, 'setTableEditor').and.callThrough();

plugin.setTableEditor(table);

if (mouseOutListener) {
const boundedListener = mouseOutListener.bind(ele);
spyOn(Contains, 'default').and.returnValue(false);
boundedListener(<MouseEvent>(<any>{
currentTarget: ele,
relatedTarget: ele,
}));

expect(plugin.setTableEditor).toHaveBeenCalledWith(null);
}
});

it('Do not dismiss table editor on mouse out, related target is contained in scroll container', () => {
const ele = createElement(
{
tag: 'div',
children: [
{
tag: 'div',
children: ['asd'],
},
],
},
editor.getDocument()
);
const table = createElement(
{
tag: 'table',
children: [
{
tag: 'tr',
children: [
{
tag: 'td',
children: ['Test'],
},
],
},
],
},
editor.getDocument()
) as HTMLTableElement;
editor.insertNode(table);

spyOn(plugin, 'setTableEditor').and.callThrough();

plugin.setTableEditor(table);

if (mouseOutListener) {
const boundedListener = mouseOutListener.bind(ele);
spyOn(Contains, 'default').and.returnValue(true);
boundedListener(<MouseEvent>(<any>{
currentTarget: ele,
relatedTarget: ele,
}));

expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null);
}
});

it('Do not dismiss table editor on mouse out, table editor not', () => {
const ele = createElement(
{
tag: 'div',
children: [
{
tag: 'div',
children: ['asd'],
},
],
},
editor.getDocument()
);

spyOn(plugin, 'setTableEditor').and.callThrough();

if (mouseOutListener) {
const boundedListener = mouseOutListener.bind(ele);
spyOn(Contains, 'default').and.returnValue(false);
boundedListener(<MouseEvent>(<any>{
currentTarget: ele,
relatedTarget: ele,
}));

expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null);
}
});

it('Do not dismiss table editor on mouse out, related target null', () => {
const ele = createElement(
{
tag: 'div',
children: [
{
tag: 'div',
children: ['asd'],
},
],
},
editor.getDocument()
);
const table = createElement(
{
tag: 'table',
children: [
{
tag: 'tr',
children: [
{
tag: 'td',
children: ['Test'],
},
],
},
],
},
editor.getDocument()
) as HTMLTableElement;
editor.insertNode(table);

spyOn(plugin, 'setTableEditor').and.callThrough();

plugin.setTableEditor(table);

if (mouseOutListener) {
const boundedListener = mouseOutListener.bind(ele);
spyOn(Contains, 'default').and.returnValue(false);
boundedListener(<MouseEvent>(<any>{
currentTarget: ele,
relatedTarget: null,
}));

expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null);
}
});

it('Do not dismiss table editor on mouse out, currentTarget null', () => {
const ele = createElement(
{
tag: 'div',
children: [
{
tag: 'div',
children: ['asd'],
},
],
},
editor.getDocument()
);
const table = createElement(
{
tag: 'table',
children: [
{
tag: 'tr',
children: [
{
tag: 'td',
children: ['Test'],
},
],
},
],
},
editor.getDocument()
) as HTMLTableElement;
editor.insertNode(table);

spyOn(plugin, 'setTableEditor').and.callThrough();

plugin.setTableEditor(table);

if (mouseOutListener) {
const boundedListener = mouseOutListener.bind(ele);
spyOn(Contains, 'default').and.returnValue(false);
boundedListener(<MouseEvent>(<any>{
currentTarget: null,
relatedTarget: ele,
}));

expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null);
}
});
});
Loading