Skip to content

Commit

Permalink
Add new beforeBeginEditing hook (#10699)
Browse files Browse the repository at this point in the history
  • Loading branch information
budnix committed Jan 11, 2024
1 parent 59a506f commit 2c60f30
Show file tree
Hide file tree
Showing 12 changed files with 460 additions and 5 deletions.
8 changes: 8 additions & 0 deletions .changelogs/10699.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Added new `beforeBeginEditing` hook",
"type": "added",
"issueOrPR": 10699,
"breaking": false,
"framework": "none"
}
5 changes: 5 additions & 0 deletions handsontable/src/__tests__/core/settings.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,11 @@ const allSettings: Required<Handsontable.GridSettings> = {
afterViewRender: (isForced) => {},
beforeAddChild: (parent, element, index) => {},
beforeAutofill: (start, end, data) => {},
beforeBeginEditing: (row: number, column: number, initialValue, event, fullEditMode: boolean) => {
event.preventDefault();

return true;
},
beforeCellAlignment: (stateBefore, range, type, alignmentClass) => {},
beforeChange: (changes, source) => { if (changes?.[0] !== null) { changes[0][3] = 10; } return false; },
beforeChangeRender: (changes, source) => {},
Expand Down
96 changes: 96 additions & 0 deletions handsontable/src/__tests__/editorManager/openEditor.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
describe('EditorManager open editor', () => {
const id = 'testContainer';

beforeEach(function() {
this.$container = $(`<div id="${id}" style="width: 300px; height: 200px; overflow: auto"></div>`).appendTo('body');
});

afterEach(function() {
if (this.$container) {
destroy();
this.$container.remove();
}
});

it('should be possible to open an editor using a mouse', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 2);
mouseDoubleClick(getCell(2, 2));

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should not be possible to open an editor using a mouse when more than 2 cells are selected (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 2, 2, 3);
keyDown('shift');
mouseDoubleClick(getCell(2, 3));

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeUndefined();
});

it('should be possible to open an editor using a mouse when 1 cell is selected (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 2);
keyDown('shift');
mouseDoubleClick(getCell(2, 2));

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should not be possible to open an editor using a mouse when Ctrl/Cmd key is pressed', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 3);
keyDown('control/meta');
mouseDoubleClick(getCell(2, 3));

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeUndefined();
});

it('should be possible to open an editor using Enter key', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 2);
keyDown('enter');

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should be possible to open an editor using Enter key when more than 2 cells are selected (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCell(2, 2, 2, 3);
keyDownUp(['shift', 'enter']);

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
expect(getActiveEditor().row).toBe(2);
expect(getActiveEditor().col).toBe(2);
});

it('should not be possible to open an editor using Enter key when Ctrl/Cmd key is pressed', () => {
handsontable({
data: createSpreadsheetData(5, 5),
});
selectCells([[1, 1], [2, 3]]);
keyDownUp(['control/meta', 'enter']);

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeDefined();
});
});
29 changes: 25 additions & 4 deletions handsontable/src/editorManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,29 @@ class EditorManager {
return;
}

const selection = this.hot.getSelectedRangeLast();
let allowOpening = this.hot.runHooks(
'beforeBeginEditing',
selection.highlight.row,
selection.highlight.col,
newInitialValue,
event,
enableFullEditMode,
);

// If the above hook does not return boolean apply default behavior which disallows opening
// an editor after double mouse click for non-contiguous selection (while pressing Ctrl/Cmd) and
// for multiple selected cells (while pressing SHIFT).
if (event instanceof MouseEvent && typeof allowOpening !== 'boolean') {
allowOpening = this.hot.selection.getLayerLevel() === 0 && selection.isSingle();
}

if (allowOpening === false) {
this.clearActiveEditor();

return;
}

if (!this.activeEditor) {
this.hot.scrollToFocusedCell();
this.prepareEditor();
Expand Down Expand Up @@ -374,11 +397,9 @@ class EditorManager {
*
* @param {MouseEvent} event The mouse event object.
* @param {object} coords The cell coordinates.
* @param {HTMLTableCellElement|HTMLTableHeaderCellElement} elem The element which triggers the action.
*/
#onCellDblClick(event, coords, elem) {
// may be TD or TH
if (elem.nodeName === 'TD') {
#onCellDblClick(event, coords) {
if (coords.isCell()) {
this.openEditor(null, event, true);
}
}
Expand Down
19 changes: 19 additions & 0 deletions handsontable/src/pluginHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,25 @@ const REGISTERED_HOOKS = [
*/
'afterDetachChild',

/**
* Fired before the editor is opened and rendered.
*
* @since 14.1.0
* @event Hooks#beforeBeginEditing
* @param {number} row Visual row index of the edited cell.
* @param {number} column Visual column index of the edited cell.
* @param {*} initialValue The initial editor value.
* @param {MouseEvent | KeyboardEvent} event The event which was responsible for opening the editor.
* @param {boolean} fullEditMode `true` if the editor is opened in full edit mode, `false` otherwise.
* Editor opened in full edit mode does not close after pressing Arrow keys.
* @returns {boolean | undefined} If the callback returns `false,` the editor won't be opened after
* the mouse double click or after pressing the Enter key. Returning `undefined` (or other value
* than boolean) will result in default behavior, which disallows opening an editor for non-contiguous
* selection (while pressing Ctrl/Cmd) and for multiple selected cells (while pressing SHIFT).
* Returning `true` removes those restrictions.
*/
'beforeBeginEditing',

/**
* Fired after the editor is opened and rendered.
*
Expand Down
117 changes: 117 additions & 0 deletions handsontable/src/plugins/mergeCells/__tests__/openEditor.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
describe('MergeCells open editor', () => {
const id = 'testContainer';

beforeEach(function() {
this.$container = $(`<div id="${id}" style="width: 300px; height: 200px; overflow: auto"></div>`).appendTo('body');
});

afterEach(function() {
if (this.$container) {
destroy();
this.$container.remove();
}
});

it('should be possible to open an editor using a mouse when merge cell is selected', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1);
mouseDoubleClick(getCell(1, 1));

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should be possible to open an editor using a mouse when merge cell is selected (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1);
keyDown('shift');
mouseDoubleClick(getCell(1, 1));

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should not be possible to open an editor using a mouse when more than 2 cells are selected (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1, 2, 3);
keyDown('shift');
mouseDoubleClick(getCell(2, 3));

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeUndefined();
});

it('should not be possible to open an editor using a mouse when Ctrl/Cmd key is pressed', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1);
keyDown('control/meta');
mouseDoubleClick(getCell(1, 1));

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeUndefined();
});

it('should be possible to open an editor for merged cell using Enter key', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1);
keyDown('enter');

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
});

it('should be possible to open an editor for merged cell using Enter (while holding Shift)', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCell(1, 1);
keyDownUp(['shift', 'enter']);

expect(isEditorVisible()).toBe(true);
expect(getActiveEditor()).toBeDefined();
expect(getActiveEditor().row).toBe(1);
expect(getActiveEditor().col).toBe(1);
});

it('should not be possible to open an editor using Enter key when Ctrl/Cmd key is pressed', () => {
handsontable({
data: createSpreadsheetData(5, 5),
mergeCells: [
{ row: 1, col: 1, rowspan: 2, colspan: 2 }
]
});
selectCells([[1, 1], [3, 4]]);
keyDownUp(['control/meta', 'enter']);

expect(isEditorVisible()).toBe(false);
expect(getActiveEditor()).toBeDefined();
});
});
2 changes: 1 addition & 1 deletion handsontable/src/plugins/mergeCells/cellsCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class MergedCellsCollection {
}

/**
* Get a merged cell containing the provided range.
* Get the first-found merged cell containing the provided range.
*
* @param {CellRange|object} range The range to search merged cells for.
* @returns {MergedCellCoords|boolean}
Expand Down
36 changes: 36 additions & 0 deletions handsontable/src/plugins/mergeCells/mergeCells.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class MergeCells extends BasePlugin {
this.addHook('beforeDrawBorders', (...args) => this.#onBeforeDrawAreaBorders(...args));
this.addHook('afterDrawSelection', (...args) => this.#onAfterDrawSelection(...args));
this.addHook('beforeRemoveCellClassNames', (...args) => this.#onBeforeRemoveCellClassNames(...args));
this.addHook('beforeBeginEditing', (...args) => this.#onBeforeBeginEditing(...args));
this.addHook('beforeUndoStackChange', (action, source) => {
if (source === 'MergeCells') {
return false;
Expand Down Expand Up @@ -1258,4 +1259,39 @@ export class MergeCells extends BasePlugin {
#onBeforeRemoveCellClassNames() {
return this.selectionCalculations.getSelectedMergedCellClassNameToRemove();
}

/**
* Allows to prevent opening the editor while more than one merged cell is selected.
*
* @param {number} row Visual row index of the edited cell.
* @param {number} column Visual column index of the edited cell.
* @param {string | null} initialValue The initial editor value.
* @param {MouseEvent | KeyboardEvent} event The event which was responsible for opening the editor.
* @returns {boolean | undefined}
*/
#onBeforeBeginEditing(row, column, initialValue, event) {
if (!(event instanceof MouseEvent)) {
return;
}

const selection = this.hot.getSelectedRangeLast();
const mergeCell = this.mergedCellsCollection.getByRange(selection);

if (!mergeCell) {
return;
}

const from = this.hot._createCellCoords(
mergeCell.row,
mergeCell.col
);
const to = this.hot._createCellCoords(
mergeCell.row + mergeCell.rowspan - 1,
mergeCell.col + mergeCell.colspan - 1
);

return this.hot.selection.getLayerLevel() === 0 && selection.isEqual(
this.hot._createCellRange(from, from, to)
);
}
}

0 comments on commit 2c60f30

Please sign in to comment.