Skip to content

Commit

Permalink
fix(core): add missing key shortcuts for navigation & cell selections (
Browse files Browse the repository at this point in the history
…#1788)

* fix(core): add multiple key shortcuts w/wo cell selection
  • Loading branch information
ghiscoding authored Dec 30, 2024
1 parent 38cc4b1 commit 972783e
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 28 deletions.
94 changes: 90 additions & 4 deletions packages/common/src/core/__tests__/slickGrid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6504,29 +6504,81 @@ describe('SlickGrid core file', () => {
const navigateRowStartSpy = vi.spyOn(grid, 'navigateRowStart');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'Home' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: false });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateRowStartSpy).toHaveBeenCalled();
});

it('should call navigateTop() when triggering Ctrl+Home key', () => {
it('should call navigateRowStart() when triggering Ctrl+ArrowLeft key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateTopSpy = vi.spyOn(grid, 'navigateTop');
const navigateRowStartSpy = vi.spyOn(grid, 'navigateRowStart');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'ArrowLeft' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateRowStartSpy).toHaveBeenCalled();
});

it('should call navigateTopStart() when triggering Ctrl+Home key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateTopStartSpy = vi.spyOn(grid, 'navigateTopStart');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'Home' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateTopStartSpy).toHaveBeenCalled();
});

it('should call navigateTop() when triggering Ctrl+ArrowUp key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateTopSpy = vi.spyOn(grid, 'navigateTop');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'ArrowUp' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateTopSpy).toHaveBeenCalled();
});

it('should call navigateBottomEnd() when triggering Ctrl+End key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateBottomEndSpy = vi.spyOn(grid, 'navigateBottomEnd');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'End' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateBottomEndSpy).toHaveBeenCalled();
});

it('should call navigateRowEnd() when triggering End key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
Expand All @@ -6543,7 +6595,24 @@ describe('SlickGrid core file', () => {
expect(navigateRowEndSpy).toHaveBeenCalled();
});

it('should call navigateBottom() when triggering Ctrl+End key', () => {
it('should call navigateRowEnd() when triggering Ctrl+ArrowRight key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateRowEndSpy = vi.spyOn(grid, 'navigateRowEnd');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'ArrowRight' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateRowEndSpy).toHaveBeenCalled();
});

it('should call navigateBottom() when triggering Ctrl+ArrowDown key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
Expand All @@ -6552,14 +6621,31 @@ describe('SlickGrid core file', () => {
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateBottomSpy = vi.spyOn(grid, 'navigateBottom');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'End' });
Object.defineProperty(event, 'key', { writable: true, value: 'ArrowDown' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateBottomSpy).toHaveBeenCalled();
});

it('should call navigateTop() when triggering Ctrl+ArrowUp key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
{ id: 'age', field: 'age', name: 'Age', editorClass: InputEditor },
] as Column[];
grid = new SlickGrid<any, Column>(container, items, columns, { ...defaultOptions, enableCellNavigation: true, editable: true });
const onKeyDownSpy = vi.spyOn(grid.onKeyDown, 'notify');
const navigateTopSpy = vi.spyOn(grid, 'navigateTop');
const event = new CustomEvent('keydown');
Object.defineProperty(event, 'key', { writable: true, value: 'ArrowUp' });
Object.defineProperty(event, 'ctrlKey', { writable: true, value: true });
container.querySelector('.grid-canvas-left')!.dispatchEvent(event);

expect(onKeyDownSpy).toHaveBeenCalled();
expect(navigateTopSpy).toHaveBeenCalled();
});

it('should call navigatePageDown() when triggering PageDown key', () => {
const columns = [
{ id: 'name', field: 'name', name: 'Name' },
Expand Down
30 changes: 25 additions & 5 deletions packages/common/src/core/slickGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5191,10 +5191,18 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
return;
}
}
if (e.key === 'Home') {
handled = e.ctrlKey ? this.navigateTop() : this.navigateRowStart();
} else if (e.key === 'End') {
handled = e.ctrlKey ? this.navigateBottom() : this.navigateRowEnd();
if (e.ctrlKey && e.key === 'Home') {
this.navigateTopStart();
} else if (e.ctrlKey && e.key === 'End') {
this.navigateBottomEnd();
} else if (e.ctrlKey && e.key === 'ArrowUp') {
this.navigateTop();
} else if (e.ctrlKey && e.key === 'ArrowDown') {
this.navigateBottom();
} else if ((e.ctrlKey && e.key === 'ArrowLeft') || (!e.ctrlKey && e.key === 'Home')) {
this.navigateRowStart();
} else if ((e.ctrlKey && e.key === 'ArrowRight') || (!e.ctrlKey && e.key === 'End')) {
this.navigateRowEnd();
}
}
}
Expand Down Expand Up @@ -6106,7 +6114,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
return this.navigateToRow(this.getDataLength() - 1);
}

protected navigateToRow(row: number): boolean {
navigateToRow(row: number): boolean {
const num_rows = this.getDataLength();
if (!num_rows) {
return true;
Expand Down Expand Up @@ -6426,6 +6434,18 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
return this.navigate('end');
}

/** Navigate to coordinate 0,0 (top left home) */
navigateTopStart(): boolean | undefined {
this.navigateToRow(0);
return this.navigate('home');
}

/** Navigate to bottom row end (bottom right end) */
navigateBottomEnd(): boolean | undefined {
this.navigateBottom();
return this.navigate('end');
}

/**
* @param {string} dir Navigation direction.
* @return {boolean} Whether navigation resulted in a change of active cell.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,30 @@ describe('CellSelectionModel Plugin', () => {
expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to row index 0 horizontally when using Shift+Ctrl+ArrowLeft key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
const expectedRowZeroIdx = 0;
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber });
vi.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true);
const scrollCellSpy = vi.spyOn(gridStub, 'scrollCellIntoView');

plugin.init(gridStub);
plugin.setSelectedRanges([
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false },
{ fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false },
] as unknown as SlickRange[]);
const setSelectRangeSpy = vi.spyOn(plugin, 'setSelectedRanges');
const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'ArrowLeft');
gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub);

const expectedRangeCalled = [
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.any(Function) } as unknown as SlickRange,
{ fromCell: expectedRowZeroIdx, fromRow: notifyingRowNumber, toCell: 2, toRow: 100 },
];
expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled);
expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to same row last cell index horizontally when using Shift+End key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
const columnsLn = mockColumns.length;
Expand All @@ -513,6 +537,76 @@ describe('CellSelectionModel Plugin', () => {
expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to same row last cell index horizontally when using Shift+Ctrl+ArrowRight key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
const columnsLn = mockColumns.length;
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber });
vi.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true);
const scrollCellSpy = vi.spyOn(gridStub, 'scrollCellIntoView');

plugin.init(gridStub);
plugin.setSelectedRanges([
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false },
{ fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false },
] as unknown as SlickRange[]);
const setSelectRangeSpy = vi.spyOn(plugin, 'setSelectedRanges');
const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'ArrowRight');
gridStub.onKeyDown.notify({ cell: 1, row: 101, grid: gridStub }, keyDownEvent, gridStub);

const expectedRangeCalled = [
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.any(Function) } as unknown as SlickRange,
{ fromCell: columnsLn - 1, fromRow: notifyingRowNumber, toCell: 2, toRow: 100 },
];
expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled);
expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to grid top on same column when using Ctrl+Shift+ArrowUp key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber });
vi.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true);
const scrollCellSpy = vi.spyOn(gridStub, 'scrollCellIntoView');

plugin.init(gridStub);
plugin.setSelectedRanges([
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false },
{ fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false },
] as unknown as SlickRange[]);
const setSelectRangeSpy = vi.spyOn(plugin, 'setSelectedRanges');
const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'ArrowUp');
gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub);

const expectedRangeCalled = [
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.any(Function) } as unknown as SlickRange,
{ fromCell: 2, fromRow: 0, toCell: 2, toRow: 100 },
];
expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled);
expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to grid bottom on same column when using Ctrl+Shift+ArrowDown key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber });
vi.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true);
const scrollCellSpy = vi.spyOn(gridStub, 'scrollCellIntoView');

plugin.init(gridStub);
plugin.setSelectedRanges([
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false },
{ fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false },
] as unknown as SlickRange[]);
const setSelectRangeSpy = vi.spyOn(plugin, 'setSelectedRanges');
const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'ArrowDown');
gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub);

const expectedRangeCalled = [
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.any(Function) } as unknown as SlickRange,
{ fromCell: 2, fromRow: 100, toCell: 2, toRow: NB_ITEMS - 1 },
];
expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled);
expect(scrollCellSpy).toHaveBeenCalledWith(NB_ITEMS - 1, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to cell,row index 0 when using Ctrl+Shift+Home key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
const expectedRowZeroIdx = 0;
Expand Down Expand Up @@ -561,6 +655,30 @@ describe('CellSelectionModel Plugin', () => {
expect(scrollCellSpy).toHaveBeenCalledWith(expectedLastRowIdx, 2, false);
});

it('should call "setSelectedRanges" with Slick Range from current position to row index 0 horizontally when using Ctrl+A key combo when triggered by "onKeyDown"', () => {
const notifyingRowNumber = 100;
const expectedRowZeroIdx = 0;
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber });
vi.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true);
const scrollCellSpy = vi.spyOn(gridStub, 'scrollCellIntoView');

plugin.init(gridStub);
plugin.setSelectedRanges([
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false },
{ fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false },
] as unknown as SlickRange[]);
const setSelectRangeSpy = vi.spyOn(plugin, 'setSelectedRanges');
const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey'], 'a');
gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub);

const expectedRangeCalled = [
{ fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.any(Function) } as unknown as SlickRange,
{ fromCell: expectedRowZeroIdx, fromRow: expectedRowZeroIdx, toCell: 2, toRow: NB_ITEMS - 1 },
];
expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled);
expect(scrollCellSpy).toHaveBeenCalledWith(NB_ITEMS - 1, expectedRowZeroIdx, false);
});

it('should call "rangesAreEqual" and expect True when both ranges are equal', () => {
vi.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: 3 });

Expand Down
Loading

0 comments on commit 972783e

Please sign in to comment.