Better cell selection (#6914)

* Better cell selection

* Explicit return type and undefined assignment

* More complex copy/paste

* Get TS to be less mad at me

* Remove EoL

* Fail safe if statement

* strict null check
This commit is contained in:
Amir Omidi
2019-09-11 18:23:49 -07:00
committed by GitHub
parent 0793e11b04
commit abe917f3c1
3 changed files with 176 additions and 25 deletions

View File

@@ -28,6 +28,7 @@ export interface ICellRangeSelectorOptions {
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
onCellRangeSelected: Slick.Event<Slick.Range>;
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
onAppendCellRangeSelected: Slick.Event<Slick.Range>;
}
export interface ICellRangeDecorator {
@@ -45,6 +46,7 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
public onBeforeCellRangeSelected = new Slick.Event<Slick.Cell>();
public onCellRangeSelected = new Slick.Event<Slick.Range>();
public onAppendCellRangeSelected = new Slick.Event<Slick.Range>();
constructor(private options: ICellRangeSelectorOptions) {
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
@@ -138,11 +140,18 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
if (!dd || !dd.range || !dd.range.start || !dd.range.end) {
return;
}
this.onCellRangeSelected.notify(new Slick.Range(
let newRange = new Slick.Range(
dd.range.start.row,
dd.range.start.cell,
dd.range.end.row,
dd.range.end.cell
));
);
if (e.ctrlKey) {
this.onAppendCellRangeSelected.notify(newRange);
} else {
this.onCellRangeSelected.notify(newRange);
}
}
}

View File

@@ -36,11 +36,14 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this._handler.subscribe(this.grid.onActiveCellChanged, (e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
this._handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e, args));
this._handler.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
this.grid.registerPlugin(this.selector);
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range));
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, false));
this._handler.subscribe(this.selector.onAppendCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, true));
this._handler.subscribe(this.selector.onBeforeCellRangeSelected, (e: Event, cell: Slick.Cell) => this.handleBeforeCellRangeSelected(e, cell));
}
@@ -87,13 +90,18 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
return true;
}
private handleCellRangeSelected(e: Event, range: Slick.Range) {
private handleCellRangeSelected(e: Event, range: Slick.Range, append: boolean) {
this.grid.setActiveCell(range.fromRow, range.fromCell, false, false, true);
this.setSelectedRanges([range]);
if (append) {
this.setSelectedRanges(this.insertIntoSelections(this.getSelectedRanges(), range));
} else {
this.setSelectedRanges([range]);
}
}
private handleActiveCellChange(e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
private handleActiveCellChange(e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell) && !e.ctrlKey) {
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
} else if (!this.options.selectActiveCell) {
// clear the previous selection once the cell changes
@@ -118,6 +126,119 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
}
}
/**
* DO NOT CALL THIS DIRECTLY - GO THROUGH INSERT INTO SELECTIONS
*
*/
private mergeSelections(ranges: Array<Slick.Range>, range: Slick.Range) {
// New ranges selection
let newRanges: Array<Slick.Range> = [];
// Have we handled this value
let handled = false;
for (let current of ranges) {
// We've already processed everything. Add everything left back to the list.
if (handled) {
newRanges.push(current);
continue;
}
let newRange: Slick.Range | undefined = undefined;
// if the ranges are the same.
if (current.fromRow === range.fromRow &&
current.fromCell === range.fromCell &&
current.toRow === range.toRow &&
current.toCell === range.toCell) {
// If we're actually not going to handle it during this loop
// this region will be added with the handled boolean check
continue;
}
// Rows are the same - horizontal merging of the selection area
if (current.fromRow === range.fromRow && current.toRow === range.toRow) {
// Check if the new region is adjacent to the old selection group
if (range.toCell + 1 === current.fromCell || range.fromCell - 1 === current.toCell) {
handled = true;
let fromCell = Math.min(range.fromCell, current.fromCell, range.toCell, current.toCell);
let toCell = Math.max(range.fromCell, current.fromCell, range.toCell, current.toCell);
newRange = new Slick.Range(range.fromRow, fromCell, range.toRow, toCell);
}
// Cells are the same - vertical merging of the selection area
} else if (current.fromCell === range.fromCell && current.toCell === range.toCell) {
// Check if the new region is adjacent to the old selection group
if (range.toRow + 1 === current.fromRow || range.fromRow - 1 === current.toRow) {
handled = true;
let fromRow = Math.min(range.fromRow, current.fromRow, range.fromRow, current.fromRow);
let toRow = Math.max(range.toRow, current.toRow, range.toRow, current.toRow);
newRange = new Slick.Range(fromRow, range.fromCell, toRow, range.toCell);
}
}
if (newRange) {
newRanges.push(newRange);
} else {
newRanges.push(current);
}
}
if (!handled) {
newRanges.push(range);
}
return {
newRanges,
handled
};
}
private insertIntoSelections(ranges: Array<Slick.Range>, range: Slick.Range): Array<Slick.Range> {
let result = this.mergeSelections(ranges, range);
let newRanges = result.newRanges;
// Keep merging the rows until we stop having changes
let i = 0;
while (true) {
if (i++ > 10000) {
console.error('InsertIntoSelection infinite loop: Report this error on github');
break;
}
let shouldContinue = false;
for (let current of newRanges) {
result = this.mergeSelections(newRanges, current);
if (result.handled) {
shouldContinue = true;
newRanges = result.newRanges;
break;
}
}
if (shouldContinue) {
continue;
}
break;
}
return newRanges;
}
private handleIndividualCellSelection(e: MouseEvent, args: Slick.OnClickEventArgs<T>) {
if (!e.ctrlKey) {
return;
}
let ranges: Array<Slick.Range>;
ranges = this.getSelectedRanges();
ranges = this.insertIntoSelections(ranges, new Slick.Range(args.row, args.cell));
this.grid.setActiveCell(args.row, args.cell);
this.setSelectedRanges(ranges);
e.preventDefault();
e.stopImmediatePropagation();
}
private handleKeyDown(e: KeyboardEvent) {
/***
* Кey codes