diff --git a/changes/add_GLT-4047 b/changes/add_GLT-4047 new file mode 100644 index 00000000..866d5059 --- /dev/null +++ b/changes/add_GLT-4047 @@ -0,0 +1 @@ +Range selection by allowing users to click a row, hold the Shift key, and click another row, thereby toggling the selection state of all rows in between \ No newline at end of file diff --git a/ts/component/table-builder.ts b/ts/component/table-builder.ts index 8e9bd2c1..bf40f4a3 100644 --- a/ts/component/table-builder.ts +++ b/ts/component/table-builder.ts @@ -134,6 +134,8 @@ export class TableBuilder { selectedItems: Set = new Set(); selectAllCheckbox?: HTMLInputElement; onFilterChange?: (key: string, value: string, add: boolean) => void; + lastClickedRowIndex: number | null = null; + private isShiftKeyPressed: boolean = false; constructor( definition: TableDefinition, @@ -164,6 +166,17 @@ export class TableBuilder { this.onFilterChange = onFilterChange; this.container = container; this.columns = definition.generateColumns(); + document.addEventListener("keydown", (event: KeyboardEvent) => { + if (event.key === "Shift") { + this.isShiftKeyPressed = true; + } + }); + + document.addEventListener("keyup", (event: KeyboardEvent) => { + if (event.key === "Shift") { + this.isShiftKeyPressed = false; + } + }); } public applyFilter(key: string, value: string, add: boolean) { @@ -662,7 +675,7 @@ export class TableBuilder { private addTableBody(table: HTMLTableElement, data?: ParentType[]) { if (data && data.length) { let previousSubheading: string | null = null; - data.forEach((parent) => { + data.forEach((parent, index) => { if (this.definition.getSubheading) { const currentSubheading = this.definition.getSubheading(parent); if (currentSubheading !== previousSubheading) { @@ -670,7 +683,7 @@ export class TableBuilder { previousSubheading = currentSubheading; } } - this.addDataRow(table, parent); + this.addDataRow(table, parent, index); }); } else { this.addNoDataRow(table); @@ -774,7 +787,11 @@ export class TableBuilder { th.appendChild(document.createTextNode(text || "Other")); } - private addDataRow(table: HTMLTableElement, parent: ParentType) { + private addDataRow( + table: HTMLTableElement, + parent: ParentType, + rowIndex: number + ) { let children: ChildType[] = []; if (this.definition.getChildren) { children = this.definition.getChildren(parent); @@ -784,7 +801,7 @@ export class TableBuilder { tbody.classList.add("nobreak"); const tr = this.addBodyRow(tbody, parent); if (this.definition.bulkActions) { - this.addRowSelectCell(tr, parent); + this.addRowSelectCell(tr, parent, rowIndex); } this.columns.forEach((column, i) => { if (column.child) { @@ -839,24 +856,66 @@ export class TableBuilder { cell.appendChild(document.createTextNode("NO DATA")); } - private addRowSelectCell(tr: HTMLTableRowElement, item: ParentType) { + private addRowSelectCell( + tr: HTMLTableRowElement, + item: ParentType, + rowIndex: number + ) { const td = makeCell(tr, true); const checkbox = document.createElement("input"); checkbox.className = "row-select"; checkbox.type = "checkbox"; + checkbox.onchange = (event) => { - if (checkbox.checked) { - this.selectedItems.add(item); + const currentRowIndex = rowIndex; + + if (this.isShiftKeyPressed && this.lastClickedRowIndex !== null) { + this.toggleRange( + this.lastClickedRowIndex, + currentRowIndex, + checkbox.checked + ); } else { - if (this.selectAllCheckbox) { - this.selectAllCheckbox.checked = false; - this.selectedItems.delete(item); + if (checkbox.checked) { + this.selectedItems.add(item); + } else { + if (this.selectAllCheckbox) { + this.selectAllCheckbox.checked = false; + this.selectedItems.delete(item); + } } } + this.lastClickedRowIndex = currentRowIndex; }; + td.appendChild(checkbox); } + private toggleRange( + startIndex: number, + endIndex: number, + isSelected: boolean + ) { + const [minIndex, maxIndex] = [ + Math.min(startIndex, endIndex), + Math.max(startIndex, endIndex), + ]; + + const rowSelects = this.container.getElementsByClassName("row-select"); + for (let i = minIndex; i <= maxIndex; i++) { + const rowSelect = rowSelects[i] as HTMLInputElement; + if (rowSelect) { + rowSelect.checked = isSelected; + const item = this.allItems[i]; + if (isSelected) { + this.selectedItems.add(item); + } else { + this.selectedItems.delete(item); + } + } + } + } + private addParentCell( tr: HTMLTableRowElement, column: ColumnDefinition,