-
Notifications
You must be signed in to change notification settings - Fork 178
fix: ensure cell selections are always rectangular #330
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
aca75c2
873ac67
88415fb
f5b03ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -177,12 +177,48 @@ export class TableMap { | |
| top: topB, | ||
| bottom: bottomB, | ||
| } = this.findCell(b); | ||
| return { | ||
| let rect = { | ||
| left: Math.min(leftA, leftB), | ||
| top: Math.min(topA, topB), | ||
| right: Math.max(rightA, rightB), | ||
| bottom: Math.max(bottomA, bottomB), | ||
| }; | ||
|
|
||
| // Expand the rectangle to ensure it's truly rectangular and includes | ||
| // all cells that span across its boundaries | ||
| let expanded = true; | ||
| while (expanded) { | ||
| expanded = false; | ||
|
|
||
| // Check all cells in the current rectangle | ||
| for (let row = rect.top; row < rect.bottom; row++) { | ||
| for (let col = rect.left; col < rect.right; col++) { | ||
| const index = row * this.width + col; | ||
| const cellPos = this.map[index]; | ||
| const cellRect = this.findCell(cellPos); | ||
|
|
||
| // If this cell extends beyond the current rectangle, expand it | ||
| if (cellRect.left < rect.left) { | ||
| rect.left = cellRect.left; | ||
| expanded = true; | ||
| } | ||
| if (cellRect.right > rect.right) { | ||
| rect.right = cellRect.right; | ||
| expanded = true; | ||
| } | ||
| if (cellRect.top < rect.top) { | ||
| rect.top = cellRect.top; | ||
| expanded = true; | ||
| } | ||
| if (cellRect.bottom > rect.bottom) { | ||
| rect.bottom = cellRect.bottom; | ||
| expanded = true; | ||
| } | ||
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| return rect; | ||
| } | ||
|
|
||
| // Return the position of all cells that have the top left corner in | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import ist from 'ist'; | ||
| import { describe, it } from 'vitest'; | ||
|
|
||
| import { TableMap } from '../src'; | ||
|
|
||
| import { table, tr, td, p } from './build'; | ||
|
|
||
| describe('CellSelection rectangular constraint', () => { | ||
| it('should expand selection to include full rowspan cells', () => { | ||
| // Table structure: | ||
| // | A | B (rowspan=2) | C | | ||
| // | D | B | E | | ||
| const tableNode = table( | ||
| tr( | ||
| td(p('A')), // pos 1 | ||
| td({ rowspan: 2 }, p('B')), // pos 6 | ||
| td(p('C')), // pos 11 | ||
| ), | ||
|
Comment on lines
+13
to
+17
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unclear what |
||
| tr( | ||
| td(p('D')), // pos 18 | ||
| // B continues here | ||
| td(p('E')), // pos 23 | ||
| ), | ||
| ); | ||
|
|
||
| const map = TableMap.get(tableNode); | ||
|
|
||
| // Select from A (pos=1) to C (pos=11) | ||
| // Because B has rowspan=2, the selection should expand to a rectangle | ||
| // that includes D and E | ||
| const rect = map.rectBetween(1, 11); | ||
|
|
||
| console.log('Map:', map.map); | ||
| console.log('Rect:', rect); | ||
| console.log('Cells in rect:', map.cellsInRect(rect)); | ||
|
||
|
|
||
| // Expected: selection should include all cells A, B, C, D, E | ||
| const cells = map.cellsInRect(rect); | ||
|
|
||
| // Verify the selection is rectangular (should include the second row) | ||
| ist(rect.top, 0); | ||
| ist(rect.bottom, 2); // Should be 2, not 1 | ||
| ist(rect.left, 0); | ||
| ist(rect.right, 3); | ||
|
|
||
| // Should include all 5 cells | ||
| ist(cells.length, 5); | ||
| }); | ||
|
|
||
| it('should expand selection to include full colspan cells', () => { | ||
| // Table structure: | ||
| // | A | B | C | | ||
| // | D (colspan=2) | E | | ||
| // When selecting from A to E, crossing D (colspan=2), | ||
| // the selection should be a complete rectangle | ||
| const tableNode = table( | ||
| tr( | ||
| td(p('A')), // pos 1 | ||
| td(p('B')), // pos 6 | ||
| td(p('C')), // pos 11 | ||
| ), | ||
| tr( | ||
| td({ colspan: 2 }, p('D')), // pos 18 | ||
| td(p('E')), // pos 23 | ||
| ), | ||
| ); | ||
|
|
||
| const map = TableMap.get(tableNode); | ||
|
|
||
| // Select from A (pos=1) to E (pos=23) | ||
| // Should form a complete rectangle | ||
| const rect = map.rectBetween(1, 23); | ||
|
|
||
| console.log('Map:', map.map); | ||
| console.log('Rect:', rect); | ||
| console.log('Cells in rect:', map.cellsInRect(rect)); | ||
|
||
|
|
||
| const cells = map.cellsInRect(rect); | ||
|
|
||
| // Verify the selection is rectangular | ||
| ist(rect.top, 0); | ||
| ist(rect.bottom, 2); | ||
| ist(rect.left, 0); | ||
| ist(rect.right, 3); | ||
|
|
||
| // Should include all cells | ||
| ist(cells.length, 5); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe there is room for performance improvement:
this.findCell()call is slow because it's anthis.findCell()calls. One method is to use aseenobject to cache all checked points like this example.