From c89baecab296c380a4f890602bae674d1499b9d3 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 30 Dec 2024 17:35:30 -0500 Subject: [PATCH] fix: add missing key shortcuts for grid navigation & cell selections --- ...-frozen-columns-and-rows-spreadsheet.cy.ts | 86 ++++++++ .../e2e/example-spreadsheet-dataview.cy.ts | 198 ++++++++++++++++-- cypress/e2e/example-spreadsheet.cy.ts | 108 ++++++++-- ...e-frozen-columns-and-rows-spreadsheet.html | 22 +- src/plugins/slick.cellselectionmodel.ts | 23 +- src/slick.grid.ts | 30 ++- 6 files changed, 416 insertions(+), 51 deletions(-) create mode 100644 cypress/e2e/example-frozen-columns-and-rows-spreadsheet.cy.ts diff --git a/cypress/e2e/example-frozen-columns-and-rows-spreadsheet.cy.ts b/cypress/e2e/example-frozen-columns-and-rows-spreadsheet.cy.ts new file mode 100644 index 00000000..5c73946e --- /dev/null +++ b/cypress/e2e/example-frozen-columns-and-rows-spreadsheet.cy.ts @@ -0,0 +1,86 @@ +describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { + const GRID_ROW_HEIGHT = 25; + const titles = [ + '', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK' + ]; + + it('should load Example', () => { + cy.visit(`${Cypress.config('baseUrl')}/examples/example-frozen-columns-and-rows-spreadsheet.html`); + }); + + it('should have exact column titles on grid', () => { + cy.get('#myGrid') + .find('.slick-header-columns') + .children() + .each(($child, index) => { + if (index < titles.length) { + expect($child.text()).to.eq(titles[index]); + } + }); + }); + + it('should click on cell B5 (top left canvas) and ArrowUp 1 times and ArrowDown 3 time and expect cell selection B5-B8 (from top left canvas to bottom left canvas', () => { + cy.get(`.grid-canvas-top.grid-canvas-left .slick-row[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell.l2.r2`) + .as('cell_B5') + .click(); + + cy.get('@cell_B5') + .type('{shift}{uparrow}{downarrow}{downarrow}{downarrow}{downarrow}', { release: false }); + + cy.get('.slick-cell.l2.r2.selected') + .should('have.length', 4); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":5,"fromCell":2,"toCell":2,"toRow":8}'); + }); + + it('should click on cell E5 (top right canvas) then PageDown 2 times w/selection E5-F41 (bottom right canvas', () => { + cy.get(`.grid-canvas-top.grid-canvas-right .slick-row[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell.l5.r5`) + .as('cell_E5') + .click(); + + cy.get('@cell_E5') + .type('{shift}{rightarrow}{pagedown}{pagedown}', { release: false }); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":5,"fromCell":5,"toCell":6,"toRow":41}'); + }); + + it('should click on cell F40 then Shift+Ctrl+Home and expect selection A0-F40', () => { + cy.get(`.grid-canvas-bottom.grid-canvas-right .slick-row[style="top: ${GRID_ROW_HEIGHT * 33}px;"] > .slick-cell.l6.r6`) + .as('cell_F40') + .click(); + + cy.get('@cell_F40') + .type('{shift}{ctrl}{home}', { release: false }); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":0,"fromCell":0,"toCell":6,"toRow":40}'); + }); + + it('should click on cell F40 then Shift+Ctrl+End and expect selection of F40-CV98', () => { + cy.get(`.grid-canvas-bottom.grid-canvas-right .slick-row[style="top: ${GRID_ROW_HEIGHT * 33}px;"] > .slick-cell.l5.r5`) + .as('cell_F40') + .click(); + + cy.get('@cell_F40') + .type('{shift}{ctrl}{end}', { release: false }); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":40,"fromCell":5,"toCell":100,"toRow":99}'); + }); + + it('should click on cell CS95 then Ctrl+A and expect selection of A0-CV98', () => { + cy.get(`.grid-canvas-bottom.grid-canvas-right .slick-row[style="top: ${GRID_ROW_HEIGHT * 89}px;"] > .slick-cell.l95.r95`) + .as('cell_CS95') + .click(); + + cy.get('@cell_CS95') + .type('{ctrl}{A}', { release: false }); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":0,"fromCell":0,"toCell":100,"toRow":99}'); + }); +}); diff --git a/cypress/e2e/example-spreadsheet-dataview.cy.ts b/cypress/e2e/example-spreadsheet-dataview.cy.ts index 1e5b7ce9..b0f3ff3d 100644 --- a/cypress/e2e/example-spreadsheet-dataview.cy.ts +++ b/cypress/e2e/example-spreadsheet-dataview.cy.ts @@ -1,5 +1,5 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 }, () => { - const cellHeight = 25; + const GRID_ROW_HEIGHT = 25; const titles = [ '', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', @@ -21,14 +21,94 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 }); }); + describe('no pagination - basic key navigations', () => { + it('should start at D10, then type "Arrow Up" key and expect active cell to become D9', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{uparrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 9}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":9,"fromCell":4,"toCell":4,"toRow":9}'); + }); + + it('should start at D10, then type "Arrow Down" key and expect active cell to become D11', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{downarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 11}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":11,"fromCell":4,"toCell":4,"toRow":11}'); + }); + + it('should start at D10, then type "Arrow Left" key and expect active cell to become C10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{leftarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l3.r3.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":3,"toCell":3,"toRow":10}'); + }); + + it('should start at D10, then type "Arrow Right" key and expect active cell to become E10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{rightarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l5.r5.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":5,"toCell":5,"toRow":10}'); + }); + + it('should start at D10, then type "Home" key and expect active cell to become CV10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{end}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l100.r100.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":100,"toCell":100,"toRow":10}'); + }); + + it('should start at D10, then type "Home" key and expect active cell to become {A-1}10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{home}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l0.r0.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":0,"toCell":0,"toRow":10}'); + }); + }); + describe('no Pagination - showing all', () => { it('should click on cell B10 and ArrowUp 3 times and ArrowDown 1 time and expect cell selection B8-B10', () => { - cy.getCell(10, 2, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 2, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_B10') .click(); cy.get('@cell_B10') - .type('{shift}{uparrow}{uparrow}{uparrow}{downarrow}'); + .type('{shift}{uparrow}{uparrow}{uparrow}{downarrow}', { release: false }); cy.get('.slick-cell.l2.r2.selected') .should('have.length', 3); @@ -38,76 +118,148 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 }); it('should click on cell D10 then PageDown 2 times w/selection D10-D46 ', () => { - cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') .click(); cy.get('@cell_D10') - .type('{shift}{pagedown}{pagedown}'); + .type('{shift}{pagedown}{pagedown}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}'); }); it('should click on cell D10 then PageDown 3 times then PageUp 1 time w/selection D10-D46', () => { - cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') .click(); cy.get('@cell_D10') - .type('{shift}{pagedown}{pagedown}{pagedown}{pageup}'); + .type('{shift}{pagedown}{pagedown}{pagedown}{pageup}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}'); }); 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 }) + cy.getCell(46, 5, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_E46') .click(); cy.get('@cell_E46') - .type('{shift}{end}'); + .type('{shift}{end}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":46,"fromCell":5,"toCell":100,"toRow":46}'); }); it('should click on cell CP54 then Ctrl+Shift+End keys with selection E46-CV99', () => { - cy.getCell(54, 94, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(54, 94, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CP54') .click(); cy.get('@cell_CP54') - .type('{ctrl}{shift}{end}'); + .type('{ctrl}{shift}{end}', { release: false }); 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 }) + cy.getCell(95, 98, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CP95') .click(); cy.get('@cell_CP95') - .type('{ctrl}{shift}{home}'); + .type('{ctrl}{shift}{home}', { release: false }); 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 }) + cy.getCell(5, 95, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CR95') .click(); cy.get('@cell_CR95') - .type('{ctrl}{home}'); + .type('{ctrl}{home}', { release: false }); cy.get('#selectionRange') .should('have.text', ''); }); + + it('should click on cell E10 then Shift+Home with selection A10-E10', () => { + cy.getCell(10, 5, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_E10').click(); + + cy.get('@cell_E10').type('{shift}{home}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":0,"toCell":5,"toRow":10}'); + }); + + it('should click on cell E10 then Shift+End with selection E10-CV10', () => { + cy.getCell(10, 5, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_E10').click(); + + cy.get('@cell_E10').type('{shift}{end}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":5,"toCell":100,"toRow":10}'); + }); + + it('should click on cell CN10 then Shift+Ctrl+ArrowLeft with selection A10-CN10', () => { + cy.getCell(10, 92, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CN10').click(); + + cy.get('@cell_CN10').type('{shift}{ctrl}{leftarrow}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":0,"toCell":92,"toRow":10}'); + }); + + it('should click on cell CN10 then Shift+Ctrl+ArrowRight key with full row horizontal selection CN10-CV10', () => { + cy.getCell(10, 92, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CN10').click(); + + cy.get('@cell_CN10').type('{shift}{ctrl}{rightarrow}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":92,"toCell":100,"toRow":10}'); + }); + + it('should click on cell CN10 then Shift+Ctrl+ArrowUp key with full column vertical top selection E0-CN10', () => { + cy.getCell(10, 92, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CN10').click(); + + cy.get('@cell_CN10').type('{shift}{ctrl}{uparrow}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":0,"fromCell":92,"toCell":92,"toRow":10}'); + }); + + it('should click on cell CN10 then Shift+Ctrl+ArrowDown key with full column vertical bottom selection CN10-E99', () => { + cy.getCell(10, 92, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CN10').click(); + + cy.get('@cell_CN10').type('{shift}{ctrl}{downarrow}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":92,"toCell":92,"toRow":99}'); + }); + + it('should click on cell CL91 then Ctrl+Shift+End keys with selection CL91-CV99', () => { + cy.getCell(91, 90, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CL91').click(); + + cy.get('@cell_CL91').type('{ctrl}{shift}{end}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":91,"fromCell":90,"toCell":100,"toRow":99}'); + }); + + it('should click on cell CP91 again then Ctrl+A keys and expect to scroll select everything in the grid', () => { + cy.getCell(91, 94, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_CP91').click(); + + cy.get('@cell_CP91').type('{ctrl}{a}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":0,"fromCell":0,"toCell":100,"toRow":99}'); + }); + + it('should click on cell F92 then Ctrl+Home keys to navigate to 0,0 coordinates', () => { + cy.getCell(92, 6, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }).as('cell_F92').click(); + + cy.get('@cell_F92').type('{ctrl}{home}', { release: false }); + + cy.get('#selectionRange').should('have.text', '{"fromRow":0,"fromCell":0,"toCell":0,"toRow":0}'); + }); }); describe('with Pagination', () => { @@ -117,36 +269,36 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 }); it('should click on cell B14 then Shift+End with selection B14-24', () => { - cy.getCell(14, 2, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(14, 2, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_B14') .click(); cy.get('@cell_B14') - .type('{shift}{end}'); + .type('{shift}{end}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":14,"fromCell":2,"toCell":100,"toRow":14}'); }); it('should click on cell CS14 then Shift+Home with selection A14-CS14', () => { - cy.getCell(14, 97, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(14, 97, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CS14') .click(); cy.get('@cell_CS14') - .type('{shift}{home}'); + .type('{shift}{home}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":14,"fromCell":0,"toCell":97,"toRow":14}'); }); 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 }) + cy.getCell(3, 95, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CN3') .click(); cy.get('@cell_CN3') - .type('{shift}{pagedown}{pagedown}{pagedown}'); + .type('{shift}{pagedown}{pagedown}{pagedown}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":3,"fromCell":95,"toCell":95,"toRow":24}'); @@ -155,12 +307,12 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 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, 92, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(15, 92, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CN41') .click(); cy.get('@cell_CN41') - .type('{shift}{pageup}{pageup}{pageup}'); + .type('{shift}{pageup}{pageup}{pageup}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":0,"fromCell":92,"toCell":92,"toRow":15}'); diff --git a/cypress/e2e/example-spreadsheet.cy.ts b/cypress/e2e/example-spreadsheet.cy.ts index 2a9c6691..e95d321e 100644 --- a/cypress/e2e/example-spreadsheet.cy.ts +++ b/cypress/e2e/example-spreadsheet.cy.ts @@ -1,5 +1,5 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { - const cellHeight = 25; + const GRID_ROW_HEIGHT = 25; const titles = [ '', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', @@ -22,12 +22,12 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { }); it('should click on cell B10 and ArrowUp 3 times and ArrowDown 1 time and expect cell selection B8-B10', () => { - cy.getCell(10, 2, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 2, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_B10') .click(); cy.get('@cell_B10') - .type('{shift}{uparrow}{uparrow}{uparrow}{downarrow}'); + .type('{shift}{uparrow}{uparrow}{uparrow}{downarrow}', { release: false }); cy.get('.slick-cell.l2.r2.selected') .should('have.length', 3); @@ -37,67 +37,67 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { }); it('should click on cell D10 then PageDown 2 times w/selection D10-D46 ', () => { - cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') .click(); cy.get('@cell_D10') - .type('{shift}{pagedown}{pagedown}'); + .type('{shift}{pagedown}{pagedown}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}'); }); it('should click on cell D10 then PageDown 3 times then PageUp 1 time w/selection D10-D46', () => { - cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') .click(); cy.get('@cell_D10') - .type('{shift}{pagedown}{pagedown}{pagedown}{pageup}'); + .type('{shift}{pagedown}{pagedown}{pagedown}{pageup}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":46}'); }); 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 }) + cy.getCell(46, 5, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_E46') .click(); cy.get('@cell_E46') - .type('{shift}{end}'); + .type('{shift}{end}', { release: false }); cy.get('#selectionRange') .should('have.text', '{"fromRow":46,"fromCell":5,"toCell":100,"toRow":46}'); }); it('should click on cell CP54 then Ctrl+Shift+End keys with selection E46-CV99', () => { - cy.getCell(54, 94, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) + cy.getCell(54, 94, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CP54') .click(); cy.get('@cell_CP54') - .type('{ctrl}{shift}{end}'); + .type('{ctrl}{shift}{end}', { release: false }); 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 }) + cy.getCell(95, 98, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CP95') .click(); cy.get('@cell_CP95') - .type('{ctrl}{shift}{home}'); + .type('{ctrl}{shift}{home}', { release: false }); 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 }) + cy.getCell(5, 95, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CR95') .click(); @@ -107,4 +107,84 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { cy.get('#selectionRange') .should('have.text', ''); }); + + describe('basic key navigations', () => { + it('should start at D10, then type "Arrow Up" key and expect active cell to become D9', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{uparrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 9}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":9,"fromCell":4,"toCell":4,"toRow":9}'); + }); + + it('should start at D10, then type "Arrow Down" key and expect active cell to become D11', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{downarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 11}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":11,"fromCell":4,"toCell":4,"toRow":11}'); + }); + + it('should start at D10, then type "Arrow Left" key and expect active cell to become C10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{leftarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l3.r3.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":3,"toCell":3,"toRow":10}'); + }); + + it('should start at D10, then type "Arrow Right" key and expect active cell to become E10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{rightarrow}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l5.r5.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":5,"toCell":5,"toRow":10}'); + }); + + it('should start at D10, then type "Home" key and expect active cell to become CV10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{end}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l100.r100.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":100,"toCell":100,"toRow":10}'); + }); + + it('should start at D10, then type "Home" key and expect active cell to become {A-1}10', () => { + cy.getCell(10, 4, '', { parentSelector: '#myGrid', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_D10') + .click(); + + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + + cy.get('@cell_D10').type('{home}'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l0.r0.selected`).should('have.length', 1); + cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":0,"toCell":0,"toRow":10}'); + }); + }); }); diff --git a/examples/example-frozen-columns-and-rows-spreadsheet.html b/examples/example-frozen-columns-and-rows-spreadsheet.html index df114553..a9080eb3 100644 --- a/examples/example-frozen-columns-and-rows-spreadsheet.html +++ b/examples/example-frozen-columns-and-rows-spreadsheet.html @@ -48,6 +48,9 @@

  • End
  • + +

    Range Selection

    + @@ -66,6 +69,7 @@

    diff --git a/src/plugins/slick.cellselectionmodel.ts b/src/plugins/slick.cellselectionmodel.ts index e22f1bd5..2a99a5fe 100644 --- a/src/plugins/slick.cellselectionmodel.ts +++ b/src/plugins/slick.cellselectionmodel.ts @@ -152,7 +152,7 @@ export class SlickCellSelectionModel { } protected isKeyAllowed(key: string) { - return ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'PageDown', 'PageUp', 'Home', 'End'].some(k => k === key); + return ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'PageDown', 'PageUp', 'Home', 'End', 'a', 'A'].some(k => k === key); } protected handleKeyDown(e: SlickEventData_) { @@ -181,13 +181,22 @@ export class SlickCellSelectionModel { let dRow = last.toRow - last.fromRow; let dCell = last.toCell - last.fromCell; + let toCell: undefined | number; + let toRow = 0; + + // when using Ctrl+{a, A} we will change our position to cell 0,0 and select all grid cells + if (e.ctrlKey && e.key?.toLowerCase() === 'a') { + this._grid.setActiveCell(0, 0, false, false, true); + active.row = 0; + active.cell = 0; + toCell = colLn - 1; + toRow = dataLn - 1; + } // walking direction 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 && !e.ctrlKey) { // single cell move: (Arrow{Up/ArrowDown/ArrowLeft/ArrowRight}) @@ -210,12 +219,16 @@ export class SlickCellSelectionModel { this._prevSelectedRow = active.row; } - if (e.shiftKey && !e.ctrlKey && e.key === 'Home') { + if ((!e.ctrlKey && e.shiftKey && e.key === 'Home') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowLeft')) { toCell = 0; toRow = active.row; - } else if (e.shiftKey && !e.ctrlKey && e.key === 'End') { + } else if ((!e.ctrlKey && e.shiftKey && e.key === 'End') || (e.ctrlKey && e.shiftKey && e.key === 'ArrowRight')) { toCell = colLn - 1; toRow = active.row; + } else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowUp') { + toRow = 0; + } else if (e.ctrlKey && e.shiftKey && e.key === 'ArrowDown') { + toRow = dataLn - 1; } else if (e.ctrlKey && e.shiftKey && e.key === 'Home') { toCell = 0; toRow = 0; diff --git a/src/slick.grid.ts b/src/slick.grid.ts index f82d5b8b..cd28b6a2 100644 --- a/src/slick.grid.ts +++ b/src/slick.grid.ts @@ -5462,10 +5462,18 @@ export class SlickGrid = Column, O e return; } } - if (e.which === keyCode.HOME) { - handled = (e.ctrlKey) ? this.navigateTop() : this.navigateRowStart(); - } else if (e.which === keyCode.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(); } } } @@ -6350,7 +6358,7 @@ export class SlickGrid = Column, O e this.navigateToRow(this.getDataLength() - 1); } - protected navigateToRow(row: number) { + navigateToRow(row: number) { const num_rows = this.getDataLength(); if (!num_rows) { return true; } @@ -6652,6 +6660,18 @@ export class SlickGrid = Column, 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.