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: revamp all cell selection range with key combos, fixes #935 #940

Merged
merged 2 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions cypress/e2e/example-auto-scroll-when-dragging.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => {
});

it('should select border shown in cell selection model, and hidden in row selection model when dragging', { scrollBehavior: false }, function () {
cy.getCell(0, 1, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
cy.getNthCell(0, 1, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell1')
.dragStart();

Expand All @@ -39,7 +39,7 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => {
cy.get('#myGrid .slick-range-decorator').should('not.be.exist');
cy.get('#myGrid .slick-cell.selected').should('have.length', 6);

cy.getCell(0, 1, '', { parentSelector: "#myGrid2", rowHeight: cellHeight })
cy.getNthCell(0, 1, '', { parentSelector: "#myGrid2", rowHeight: cellHeight })
.as('cell2')
.dragStart();
cy.get('#myGrid2 .slick-range-decorator').should('be.exist').and('have.css', 'border-style').and('equal', 'none');
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => {

function getIntervalUntilRow16Displayed(selector, px) {
const viewportSelector = (selector + ' .slick-viewport:first');
cy.getCell(0, 1, '', { parentSelector: selector, rowHeight: cellHeight })
cy.getNthCell(0, 1, '', { parentSelector: selector, rowHeight: cellHeight })
.dragStart();
return cy.get(viewportSelector).invoke('scrollTop').then(scrollBefore => {
cy.dragOutside('bottom', 0, px, { parentSelector: selector, rowHeight: cellHeight });
Expand Down Expand Up @@ -275,7 +275,7 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => {
});

function testDragInGrouping(selector) {
cy.getCell(7, 0, 'bottomRight', { parentSelector: selector, rowHeight: cellHeight })
cy.getNthCell(7, 0, 'bottomRight', { parentSelector: selector, rowHeight: cellHeight })
.dragStart();
cy.get(selector + ' .slick-viewport:last').as('viewport').invoke('scrollTop').then(scrollBefore => {
cy.dragOutside('bottom', 400, 300, { parentSelector: selector, rowHeight: cellHeight });
Expand Down
74 changes: 49 additions & 25 deletions cypress/e2e/example-spreadsheet-dataview.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0
.should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}');
});

it('should click on cell E12 then End key w/selection E46-E99', () => {
it('should click on cell E46 then Shift+End key with full row horizontal selection E46-CV46', () => {
cy.getCell(46, 5, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_E46')
.click();
Expand All @@ -70,19 +70,43 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0
.type('{shift}{end}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":46,"fromCell":5,"toCell":5,"toRow":99}');
.should('have.text', '{"fromRow":46,"fromCell":5,"toCell":100,"toRow":46}');
});

it('should click on cell C85 then End key w/selection C0-C85', () => {
cy.getCell(85, 3, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_C85')
it('should click on cell CP54 then Ctrl+Shift+End keys with selection E46-CV99', () => {
cy.getCell(54, 94, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CP54')
.click();

cy.get('@cell_C85')
.type('{shift}{home}');
cy.get('@cell_CP54')
.type('{ctrl}{shift}{end}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":54,"fromCell":94,"toCell":100,"toRow":99}');
});

it('should click on cell CP95 then Ctrl+Shift+Home keys with selection C0-CP95', () => {
cy.getCell(95, 98, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CP95')
.click();

cy.get('@cell_CP95')
.type('{ctrl}{shift}{home}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":0,"toCell":98,"toRow":95}');
});

it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => {
cy.getCell(5, 95, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CR95')
.click();

cy.get('@cell_CR95')
.type('{ctrl}{home}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":3,"toCell":3,"toRow":85}');
.should('have.text', '');
});
});

Expand All @@ -92,7 +116,7 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0
cy.get('[data-val="25"]').click();
});

it('should click on cell B14 then Shift+End w/selection B14-24', () => {
it('should click on cell B14 then Shift+End with selection B14-24', () => {
cy.getCell(14, 2, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_B14')
.click();
Expand All @@ -101,45 +125,45 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0
.type('{shift}{end}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":14,"fromCell":2,"toCell":2,"toRow":24}');
.should('have.text', '{"fromRow":14,"fromCell":2,"toCell":100,"toRow":14}');
});

it('should click on cell C19 then Shift+End w/selection C0-19', () => {
cy.getCell(19, 2, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_C19')
it('should click on cell CS14 then Shift+Home with selection A14-CS14', () => {
cy.getCell(14, 97, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CS14')
.click();

cy.get('@cell_C19')
cy.get('@cell_CS14')
.type('{shift}{home}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":2,"toCell":2,"toRow":19}');
.should('have.text', '{"fromRow":14,"fromCell":0,"toCell":97,"toRow":14}');
});

it('should click on cell E3 then Shift+PageDown multiple times with current page selection starting at E3 w/selection E3-24', () => {
cy.getCell(3, 5, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_E3')
it('should click on cell CN3 then Shift+PageDown multiple times with current page selection starting at E3 w/selection E3-24', () => {
cy.getCell(3, 95, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CN3')
.click();

cy.get('@cell_E3')
cy.get('@cell_CN3')
.type('{shift}{pagedown}{pagedown}{pagedown}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":3,"fromCell":5,"toCell":5,"toRow":24}');
.should('have.text', '{"fromRow":3,"fromCell":95,"toCell":95,"toRow":24}');
});

it('should change to 2nd page then click on cell D41 then Shift+PageUp multiple times with current page selection w/selection D25-41', () => {
it('should change to 2nd page then click on cell CN41 then Shift+PageUp multiple times with current page selection w/selection D25-41', () => {
cy.get('.slick-pager .sgi-chevron-right').click();

cy.getCell(15, 4, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_D41')
cy.getCell(15, 92, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CN41')
.click();

cy.get('@cell_D41')
cy.get('@cell_CN41')
.type('{shift}{pageup}{pageup}{pageup}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":4,"toCell":4,"toRow":15}');
.should('have.text', '{"fromRow":0,"fromCell":92,"toCell":92,"toRow":15}');
});
});
});
40 changes: 32 additions & 8 deletions cypress/e2e/example-spreadsheet.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => {
.should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}');
});

it('should click on cell E12 then End key w/selection E46-E99', () => {
it('should click on cell E46 then Shift+End key with full row horizontal selection E46-CV46', () => {
cy.getCell(46, 5, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_E46')
.click();
Expand All @@ -69,18 +69,42 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => {
.type('{shift}{end}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":46,"fromCell":5,"toCell":5,"toRow":99}');
.should('have.text', '{"fromRow":46,"fromCell":5,"toCell":100,"toRow":46}');
});

it('should click on cell C85 then End key w/selection C0-C85', () => {
cy.getCell(85, 3, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_C85')
it('should click on cell CP54 then Ctrl+Shift+End keys with selection E46-CV99', () => {
cy.getCell(54, 94, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CP54')
.click();

cy.get('@cell_C85')
.type('{shift}{home}');
cy.get('@cell_CP54')
.type('{ctrl}{shift}{end}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":3,"toCell":3,"toRow":85}');
.should('have.text', '{"fromRow":54,"fromCell":94,"toCell":100,"toRow":99}');
});

it('should click on cell CP95 then Ctrl+Shift+Home keys with selection C0-CP95', () => {
cy.getCell(95, 98, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CP95')
.click();

cy.get('@cell_CP95')
.type('{ctrl}{shift}{home}');

cy.get('#selectionRange')
.should('have.text', '{"fromRow":0,"fromCell":0,"toCell":98,"toRow":95}');
});

it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => {
cy.getCell(5, 95, '', { parentSelector: "#myGrid", rowHeight: cellHeight })
.as('cell_CR95')
.click();

cy.get('@cell_CR95')
.type('{ctrl}{home}');

cy.get('#selectionRange')
.should('have.text', '');
});
});
13 changes: 11 additions & 2 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,30 @@ declare global {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
convertPosition(viewport: string): Chainable<HTMLElement | JQuery<HTMLElement> | { x: string; y: string; }>;
getCell(row: number, col: number, viewport?: string, options?: { parentSelector?: string, rowHeight?: number; }): Chainable<HTMLElement | JQuery<HTMLElement>>;
getNthCell(row: number, nthCol: number, viewport?: string, options?: { parentSelector?: string, rowHeight?: number; }): Chainable<HTMLElement | JQuery<HTMLElement>>;
restoreLocalStorage(): Chainable<HTMLElement | JQuery<HTMLElement>>;
saveLocalStorage(): Chainable<HTMLElement | JQuery<HTMLElement>>;
}
}
}

// convert position like 'topLeft' to the object { x: 'left|right', y: 'top|bottom' }
Cypress.Commands.add('convertPosition', (viewport = 'topLeft') => cy.wrap(convertPosition(viewport)))
Cypress.Commands.add('convertPosition', (viewport = 'topLeft') => cy.wrap(convertPosition(viewport)));

Cypress.Commands.add('getCell', (row, col, viewport = 'topLeft', { parentSelector = '', rowHeight = 25 } = {}) => {
const position = convertPosition(viewport);
const canvasSelectorX = position.x ? `.grid-canvas-${position.x}` : '';
const canvasSelectorY = position.y ? `.grid-canvas-${position.y}` : '';

return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top: ${row * rowHeight}px;"] > .slick-cell:nth(${col})`);
return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top: ${row * rowHeight}px;"] > .slick-cell.l${col}.r${col}`);
});

Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentSelector = '', rowHeight = 25 } = {}) => {
const position = convertPosition(viewport);
const canvasSelectorX = position.x ? `.grid-canvas-${position.x}` : '';
const canvasSelectorY = position.y ? `.grid-canvas-${position.y}` : '';

return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top: ${row * rowHeight}px;"] > .slick-cell:nth(${nthCol})`);
});

const LOCAL_STORAGE_MEMORY = {};
Expand Down
10 changes: 5 additions & 5 deletions cypress/support/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ Cypress.Commands.add('dragStart', { prevSubject: true }, (subject, { cellWidth =
return cy.wrap(subject).click({ force: true })
.trigger('mousedown', { which: 1 })
.trigger('mousemove', cellWidth / 3, cellHeight / 3);
})
});

// use a different command name than "drag" so that it doesn't conflict with the "@4tw/cypress-drag-drop" lib
// @ts-ignore
Cypress.Commands.add('dragCell', { prevSubject: true }, (subject, addRow, addCell, { cellWidth = 80, cellHeight = 25 } = {}) => {
return cy.wrap(subject).trigger('mousemove', cellWidth * (addCell + 0.5), cellHeight * (addRow + 0.5), { force: true });
})
});

Cypress.Commands.add('dragOutside', (viewport = 'topLeft', ms = 0, px = 0, { parentSelector = 'div[class^="slickgrid_"]', scrollbarDimension = 17 } = {}) => {
const $parent = cy.$$(parentSelector);
Expand All @@ -49,17 +49,17 @@ Cypress.Commands.add('dragOutside', (viewport = 'topLeft', ms = 0, px = 0, { par
cy.wait(ms);
}
return;
})
});

Cypress.Commands.add('dragEnd', { prevSubject: 'optional' }, (subject, gridSelector = 'div[class^="slickgrid_"]') => {
cy.get(gridSelector).trigger('mouseup', { force: true });
return;
})
});

export function getScrollDistanceWhenDragOutsideGrid(selector, viewport, dragDirection, fromRow, fromCol, px = 100) {
return (cy as any).convertPosition(viewport).then((_viewportPosition: { x: number; y: number; }) => {
const viewportSelector = `${selector} .slick-viewport-${_viewportPosition.x}.slick-viewport-${_viewportPosition.y}`;
(cy as any).getCell(fromRow, fromCol, viewport, { parentSelector: selector })
(cy as any).getNthCell(fromRow, fromCol, viewport, { parentSelector: selector })
.dragStart();
return cy.get(viewportSelector).then($viewport => {
const scrollTopBefore = $viewport.scrollTop();
Expand Down
2 changes: 1 addition & 1 deletion examples/example-frozen-columns-and-rows-spreadsheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h2>
<li>Use Ctrl-C and Ctrl-V keyboard shortcuts to cut and paste cells</li>
<li>Use Esc to cancel a copy and paste operation</li>
<li>Edit the cell and select a cell range to paste the range</li>
<li>Cell Selection using "Shift+{key}" where "key" can be any of:</li>
<li>Cell Selection using "Shift+{key}" or "Ctrl+Shift+{key}" where "key" can be any of:</li>
<ul>
<li>Arrow Up/Down/Left/Right</li>
<li>Page Up/Down</li>
Expand Down
2 changes: 1 addition & 1 deletion examples/example-spreadsheet-dataview.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ <h2>
<li>Use Ctrl-C and Ctrl-V keyboard shortcuts to cut and paste cells</li>
<li>Use Esc to cancel a copy and paste operation</li>
<li>Edit the cell and select a cell range to paste the range</li>
<li>Cell Selection using "Shift+{key}" where "key" can be any of:</li>
<li>Cell Selection using "Shift+{key}" or "Ctrl+Shift+{key}" where "key" can be any of:</li>
<ul>
<li>Arrow Up/Down/Left/Right</li>
<li>Page Up/Down</li>
Expand Down
2 changes: 1 addition & 1 deletion examples/example-spreadsheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ <h2>
<li>Use Ctrl-C and Ctrl-V keyboard shortcuts to cut and paste cells</li>
<li>Use Esc to cancel a copy and paste operation</li>
<li>Edit the cell and select a cell range to paste the range</li>
<li>Cell Selection using "Shift+{key}" where "key" can be any of:</li>
<li>Cell Selection using "Shift+{key}" or "Ctrl+Shift+{key}" where "key" can be any of:</li>
<ul>
<li>Arrow Up/Down/Left/Right</li>
<li>Page Up/Down</li>
Expand Down
30 changes: 21 additions & 9 deletions src/plugins/slick.cellselectionmodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,12 @@ export class SlickCellSelectionModel {

protected handleActiveCellChange(_e: Event, args: OnActiveCellChangedEventArgs) {
this._prevSelectedRow = undefined;
if (this._options?.selectActiveCell && Utils.isDefined(args.row) && Utils.isDefined(args.cell)) {
const isCellDefined = Utils.isDefined(args.cell);
const isRowDefined = Utils.isDefined(args.row);

if (this._options?.selectActiveCell && isRowDefined && isCellDefined) {
this.setSelectedRanges([new SlickRange(args.row, args.cell)]);
} else if (!this._options?.selectActiveCell) {
} else if (!this._options?.selectActiveCell || (!isRowDefined && !isCellDefined)) {
// clear the previous selection once the cell changes
this.setSelectedRanges([]);
}
Expand All @@ -154,17 +157,16 @@ export class SlickCellSelectionModel {

protected handleKeyDown(e: KeyboardEvent) {
let ranges: SlickRange_[], last: SlickRange_;
const colLn = this._grid.getColumns().length;
const active = this._grid.getActiveCell();
const metaKey = e.ctrlKey || e.metaKey;
let dataLn = 0;
if (this._dataView) {
dataLn = this._dataView?.getPagingInfo().pageSize || this._dataView.getLength();
} else {
dataLn = this._grid.getDataLength();
}

if (active && e.shiftKey && !metaKey && !e.altKey && this.isKeyAllowed(e.key)) {

if (active && (e.shiftKey || e.ctrlKey) && !e.altKey && this.isKeyAllowed(e.key)) {
ranges = this.getSelectedRanges().slice();
if (!ranges.length) {
ranges.push(new SlickRange(active.row, active.cell));
Expand All @@ -184,9 +186,10 @@ export class SlickCellSelectionModel {
const dirRow = active.row === last.fromRow ? 1 : -1;
const dirCell = active.cell === last.fromCell ? 1 : -1;
const isSingleKeyMove = e.key.startsWith('Arrow');
let toCell: undefined | number = undefined;
let toRow = 0;

if (isSingleKeyMove) {
if (isSingleKeyMove && !e.ctrlKey) {
// single cell move: (Arrow{Up/ArrowDown/ArrowLeft/ArrowRight})
if (e.key === 'ArrowLeft') {
dCell -= dirCell;
Expand All @@ -207,9 +210,17 @@ export class SlickCellSelectionModel {
this._prevSelectedRow = active.row;
}

if (e.key === 'Home') {
if (e.shiftKey && !e.ctrlKey && e.key === 'Home') {
toCell = 0;
toRow = active.row;
} else if (e.shiftKey && !e.ctrlKey && e.key === 'End') {
toCell = colLn - 1;
toRow = active.row;
} else if (e.ctrlKey && e.shiftKey && e.key === 'Home') {
toCell = 0;
toRow = 0;
} else if (e.key === 'End') {
} else if (e.ctrlKey && e.shiftKey && e.key === 'End') {
toCell = colLn - 1;
toRow = dataLn - 1;
} else if (e.key === 'PageUp') {
if (this._prevSelectedRow >= 0) {
Expand All @@ -230,7 +241,8 @@ export class SlickCellSelectionModel {
}

// define new selection range
const new_last = new SlickRange(active.row, active.cell, toRow, active.cell + dirCell * dCell);
toCell ??= active.cell + dirCell * dCell;
const new_last = new SlickRange(active.row, active.cell, toRow, toCell);
if (this.removeInvalidRanges([new_last]).length) {
ranges.push(new_last);
const viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
Expand Down
Loading