UNPKG

@progress/kendo-angular-grid

Version:

Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.

339 lines (338 loc) 14.7 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Injectable, EventEmitter } from '@angular/core'; import { iterator } from '../utils'; import { isPresent } from '../utils'; import { DomEventsService } from '../common/dom-events.service'; import { LocalDataChangesService } from '../editing/local-data-changes.service'; import { NavigationService } from '../navigation/navigation.service'; import { CellSelectionAggregateService } from '../aggregates/selection-aggregate.service'; import * as i0 from "@angular/core"; import * as i1 from "../common/dom-events.service"; import * as i2 from "../aggregates/selection-aggregate.service"; import * as i3 from "../editing/local-data-changes.service"; import * as i4 from "../navigation/navigation.service"; /** * @hidden */ export class CellSelectionService { domEvents; aggregateService; localDataChangesService; navigationService; changes = new EventEmitter(); mouseUpEvent = new EventEmitter(); currentSelection = []; settings; active = false; aggregates; nonSelectableRows = new Map(); get enableMarquee() { const checkboxOnly = this.settings && typeof this.settings === 'object' && this.settings.checkboxOnly; if (!this.settings || checkboxOnly) { return false; } const selectableSettings = this.settings.selectable; const dragAndMultiple = typeof (selectableSettings) === 'object' && isPresent(selectableSettings) && selectableSettings.mode === 'multiple' && selectableSettings.cell && selectableSettings.enabled !== false && selectableSettings.drag; return this.active && dragAndMultiple; } get hasNonSelectable() { return this.nonSelectableRows.size > 0; } mouseDownEventArgs; mouseUpEventArgs; dragging = false; dragSelectDeselect = false; lastSelectionItem = { itemKey: 0, columnKey: 0 }; lastSelectionItemRowIndex = 0; lastSelectionItemColIndex = 0; cellClickSubscription; dataChangedSubscription; mousedownSubscription; constructor(domEvents, aggregateService, localDataChangesService, navigationService) { this.domEvents = domEvents; this.aggregateService = aggregateService; this.localDataChangesService = localDataChangesService; this.navigationService = navigationService; this.addSubscriptions(); } init(settings) { this.settings = settings; this.currentSelection = []; this.nonSelectableRows = new Map(); if (settings.selectable && settings.selectable.enabled !== false) { const iterator = this.getIterator(); let item = iterator.next(); while (!item.done) { if (item.value && item.value.type === "data") { const rowArgs = { dataItem: item.value.data, index: item.value.index }; settings.columns.forEach(col => { const selectedCellArgs = settings.cellSelected(rowArgs, col, col.leafIndex); if (selectedCellArgs.selected) { this.currentSelection.push(selectedCellArgs.item); } if (!settings.isRowSelectable(rowArgs)) { this.nonSelectableRows.set(rowArgs.index, rowArgs.dataItem); } }); } item = iterator.next(); } } } isCellSelected(item, col) { if (this.settings && this.active) { const selectedCellArgs = this.settings.cellSelected({ dataItem: item.data, index: item.index }, col, col.leafIndex); return this.options.enabled && selectedCellArgs.selected && !this.nonSelectableRows.has(item.index); } return false; } handleClick(item, event) { if (this.dragging) { this.dragging = false; return; } let ev; const ctrlKey = event.ctrlKey || event.metaKey; if (this.options.mode === "single" && ctrlKey && this.isCellSelected(item, item.column)) { ev = this.toggle(item); } else if (this.options.mode === "multiple") { if ((ctrlKey || !this.options.metaKeyMultiSelect) && !event.shiftKey) { ev = this.toggle(item); } else if (event.shiftKey) { const startRowIndex = Math.min(this.lastSelectionItemRowIndex, item.index); const startColIndex = Math.min(this.lastSelectionItemColIndex, item.column.leafIndex); const endRowIndex = Math.max(this.lastSelectionItemRowIndex, item.index); const endColIndex = Math.max(this.lastSelectionItemColIndex, item.column.leafIndex); ev = this.selectRange(startRowIndex, startColIndex, endRowIndex, endColIndex); } } if (!isPresent(ev)) { ev = this.select(item); this.currentSelection = [this.lastSelectionItem]; } if (!ev.selectedCells.length && !ev.deselectedCells.length) { return; } ev.ctrlKey = ctrlKey; ev.shiftKey = event.shiftKey; if (this.options.cellAggregates && !event.shiftKey) { ev.cellAggregates = this.aggregateService.onSelectionChange(ev); } if (ev.shiftKey) { ev.rangeStartCell = this.lastSelectionItem; ev.rangeEndCell = { ...this.settings.cellSelected({ dataItem: item.data, index: item.index }, item.column, item.column.leafIndex).item }; } this.changes.emit(ev); } toggle(item) { const selectedCells = []; const deselectedCells = []; this.lastSelectionItem = this.settings.cellSelected({ dataItem: item.data, index: item.index }, item.column, item.column.leafIndex).item; this.lastSelectionItemRowIndex = item.index; this.lastSelectionItemColIndex = item.column.leafIndex; if (this.isCellSelected(item, item.column)) { deselectedCells.push(this.lastSelectionItem); } else if (!this.nonSelectableRows.has(item.index)) { selectedCells.push(this.lastSelectionItem); } return { deselectedCells, selectedCells }; } select(item) { const selectedCells = []; const deselectedCells = []; this.lastSelectionItem = this.settings.cellSelected({ dataItem: item.data, index: item.index }, item.column, item.column.leafIndex).item; this.lastSelectionItemRowIndex = item.index; this.lastSelectionItemColIndex = item.column.leafIndex; if (!this.isCellSelected(item, item.column) && !this.nonSelectableRows.has(item.index)) { selectedCells.push(this.lastSelectionItem); } this.currentSelection.forEach((selectedItem) => { if (selectedItem.itemKey !== this.lastSelectionItem.itemKey || selectedItem.columnKey !== this.lastSelectionItem.columnKey) { deselectedCells.push(selectedItem); } }); return { deselectedCells, selectedCells }; } //Used to manually deselect removed items deselect(removedItem) { const iterator = this.getIterator(); let item = iterator.next(); let rowArgs; while (!item.done) { if (item.value && item.value.type === "data" && item.value.data === removedItem) { rowArgs = { dataItem: item.value.data, index: item.value.index }; break; } item = iterator.next(); } if (rowArgs) { const cellsToRemove = this.currentSelection.filter(selectedItem => { const contender = this.settings.cellSelected(rowArgs, null, null).item; return selectedItem.itemKey === contender.itemKey || this.nonSelectableRows.has(rowArgs.index); }); if (cellsToRemove.length) { const ev = { ctrlKey: false, deselectedCells: cellsToRemove, selectedCells: [] }; this.changes.emit(ev); } } } selectRange(startRowIndex, startColIndex, endRowIndex, endColIndex) { const selectedCells = []; const deselectedCells = []; const selectionStartRow = Math.min(startRowIndex, endRowIndex); const selectionStartCol = Math.min(startColIndex, endColIndex); const selectionEndRow = Math.max(startRowIndex, endRowIndex); const selectionEndCol = Math.max(startColIndex, endColIndex); const iterator = this.getIterator(); let next = iterator.next(); while (!next.done) { if (next.value && next.value.type === "data") { const idx = next.value.index; const data = next.value.data; const rowArgs = { dataItem: data, index: idx }; this.settings.columns.forEach(col => { const { item } = this.settings.cellSelected(rowArgs, col, col.leafIndex); const selected = this.isCellSelected(next.value, col); const isInRowRange = selectionStartRow <= idx && idx <= selectionEndRow; const isInColRange = selectionStartCol <= col.leafIndex && col.leafIndex <= selectionEndCol; const isInSelectionRect = isInRowRange && isInColRange; if (!isInSelectionRect && selected) { deselectedCells.push(item); } if (isInSelectionRect && !selected && !this.nonSelectableRows.has(idx)) { selectedCells.push(item); } }); } next = iterator.next(); } let cellAggregates; if (this.options.cellAggregates) { cellAggregates = this.aggregateService.onSelectionChange({ selectedCells, deselectedCells }); } return { deselectedCells, selectedCells, cellAggregates }; } get options() { const defaultOptions = { cellAggregates: false, checkboxOnly: false, enabled: true, mode: "multiple", metaKeyMultiSelect: true }; if (!isPresent(this.settings)) { return defaultOptions; } if (typeof this.settings.selectable === 'boolean') { return { cellAggregates: false, checkboxOnly: false, enabled: this.settings.selectable, mode: "multiple", metaKeyMultiSelect: true }; } else { return Object.assign(defaultOptions, this.settings.selectable); } } ngOnDestroy() { this.removeSubscriptions(); } addSubscriptions() { if (!this.cellClickSubscription) { this.cellClickSubscription = this.domEvents.cellClick.subscribe((args) => { if (this.options.enabled && !this.options.checkboxOnly && args.type !== 'contextmenu') { if (this.active) { this.handleClick({ index: args.rowIndex, data: args.dataItem, column: args.column }, args.originalEvent); } } }); } if (!this.mousedownSubscription) { this.mousedownSubscription = this.domEvents.cellMousedown.subscribe((args) => { this.mouseDownEventArgs = args; if (this.options.enabled && (!this.options.mode || this.options.mode === "multiple") && !this.options.checkboxOnly && args.originalEvent.shiftKey) { if (this.active) { args.originalEvent.preventDefault(); this.navigationService.focusCellByElement(args.originalEvent.target); } } }); } if (this.localDataChangesService && !this.dataChangedSubscription) { this.dataChangedSubscription = this.localDataChangesService.changes.subscribe((args) => { if (this.active) { if (isPresent(args.action) && args.action === 'remove') { this.deselect(args.item); } } }); } } getIterator() { const accessor = this.settings.view.accessor(); if (!accessor) { return; } return accessor[iterator](); } removeSubscriptions() { if (this.cellClickSubscription) { this.cellClickSubscription.unsubscribe(); this.cellClickSubscription = null; } if (this.mousedownSubscription) { this.mousedownSubscription.unsubscribe(); this.mousedownSubscription = null; } if (this.dataChangedSubscription) { this.dataChangedSubscription.unsubscribe(); this.dataChangedSubscription = null; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionService, deps: [{ token: i1.DomEventsService }, { token: i2.CellSelectionAggregateService }, { token: i3.LocalDataChangesService }, { token: i4.NavigationService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i1.DomEventsService }, { type: i2.CellSelectionAggregateService }, { type: i3.LocalDataChangesService }, { type: i4.NavigationService }]; } });