UNPKG

@progress/kendo-angular-treelist

Version:

Kendo UI TreeList for Angular - Display hierarchical data in an Angular tree grid view that supports sorting, filtering, paging, and much more.

263 lines (262 loc) 10.3 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 } from '@angular/core'; import { Subject, Subscription, merge } from "rxjs"; import { ViewCollection } from '../data/data.collection'; import { SelectionChangeEvent } from './selection-change-event'; import { hasClasses, isPresent, Keys } from '@progress/kendo-angular-common'; import * as i0 from "@angular/core"; /** * @hidden */ export const defaultSelected = (_item) => false; const noop = () => false; /** * @hidden */ export class SelectionService { changes = new Subject(); selectAllCheckedStateChange = new Subject(); state = []; set settings(value) { if (typeof value === 'object') { this._settings = value; } else { this._settings = { enabled: value }; } this.enabled = this._settings.enabled !== false && !this._settings.readonly; if (this._settings.enabled !== false) { if (this._settings.mode === 'cell') { this.isCellSelected = this.cellSelected; this.isRowSelected = noop; } else { this.isCellSelected = noop; this.isRowSelected = this.rowSelected; } } else { this.isCellSelected = noop; this.isRowSelected = noop; } } get settings() { return this._settings; } get enableMarquee() { const checkboxOnly = this.settings?.checkboxOnly; if (!this.settings || checkboxOnly) { return false; } const dragAndMultiple = typeof (this.settings) === 'object' && isPresent(this.settings) && this.settings.multiple && this.settings.enabled !== false && !this.settings.checkboxOnly && this.settings.drag !== false; return this.enabled && dragAndMultiple; } get enableMultiple() { return this.enabled && this.settings.multiple; } get rowSelection() { return this.settings.mode !== 'cell'; } isSelected = defaultSelected; isRowSelected = noop; isCellSelected = noop; enabled = false; dragging = false; selectAllCheckedState = false; view; columnsContainer; _settings = {}; selectionOrigin; tables = []; subscriptions = new Subscription(); init(treelist) { this.view = treelist.view; this.columnsContainer = treelist.columnsContainer; this.subscriptions.add(merge(treelist.pageChange, treelist.dataStateChange).subscribe(() => { this.selectionOrigin = null; })); this.subscriptions.add(treelist.domEvents.cellMousedown.subscribe((args) => { if (this.enabled && this._settings.multiple && args.originalEvent.shiftKey) { args.originalEvent.preventDefault(); } })); } ngOnDestroy() { this.subscriptions.unsubscribe(); } registerTable(table) { this.tables.push(table); } unregisterTable(table) { this.tables = this.tables.filter(t => t !== table); } click(args, toggle) { if (this.dragging) { this.dragging = false; return; } const isCheckbox = args.originalEvent.target ? hasClasses(args.originalEvent.target, 'k-checkbox') : true; if (this.rowSelection && this.settings.checkboxOnly && !isCheckbox) { return; } const { dataItem, column, columnIndex, originalEvent } = args; const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey; if (((originalEvent.code === Keys.Enter || originalEvent.code === Keys.NumpadEnter) && !ctrlKey) || (originalEvent.button && originalEvent.button !== 0)) { return; } const selected = this.isSelected(dataItem, column, columnIndex); const toggleSelected = ctrlKey || toggle; if (this._settings.multiple) { if (originalEvent.shiftKey) { const origin = this.selectionOrigin || { columnIndex: 0, column: this.leafColumns[0], item: this.view.firstItem.data }; this.selectRange({ item: dataItem, column, columnIndex }, origin); } else { this.selectionOrigin = { item: dataItem, column, columnIndex }; const action = toggleSelected ? (selected ? 'remove' : 'add') : 'select'; this.changes.next(new SelectionChangeEvent(action, [{ dataItem, column, columnIndex }])); } } else if (!selected || toggleSelected) { const action = selected && toggleSelected ? 'remove' : 'select'; this.changes.next(new SelectionChangeEvent(action, [{ dataItem, column, columnIndex }])); } if (ctrlKey) { originalEvent.preventDefault(); } } checkboxClick(args) { if (this.dragging) { this.dragging = false; return; } if (args.column.checkChildren && args.viewItem.hasChildren && !args.originalEvent.shiftKey && !args.originalEvent.ctrlKey) { const data = [args.dataItem]; const selected = Boolean(args.viewItem.selected); ViewCollection.loadView({ fields: Object.assign({}, this.view.fieldAccessor(), { data: data, hasFooter: false, pageable: false, isVirtual: false }), loaded: this.view.loaded, selectionState: this.view.selectionState }).subscribe(view => { if (!view) { return; } const selectedItems = view.data.filter(item => Boolean(item.selected) === selected).map(item => ({ dataItem: item.data })); this.changes.next(new SelectionChangeEvent(selected ? 'remove' : 'add', selectedItems)); }); } else { this.click(args, true); } } toggleAll(select) { ViewCollection.loadView({ fields: Object.assign({}, this.view.fieldAccessor(), { hasFooter: false, pageable: false, isVirtual: false }), loaded: this.view.loaded, selectionState: this.view.selectionState }).subscribe(view => { if (!view) { return; } const selectedItems = view.data.filter(item => Boolean(item.selected) !== select).map(item => ({ dataItem: item.data })); this.changes.next(new SelectionChangeEvent(select ? 'add' : 'remove', selectedItems)); }); } selectRange(firstPoint, secondPoint) { const rangeItems = this.rangeItems(firstPoint, secondPoint); this.changes.next(new SelectionChangeEvent('select', rangeItems)); } rangeItems(firstPoint, secondPoint) { const firstIndex = this.view.findIndex(item => item.data === firstPoint.item || item === firstPoint.item); const secondIndex = this.view.findIndex(item => item.data === secondPoint.item || item === secondPoint.item); const startIndex = Math.min(firstIndex, secondIndex); const endIndex = Math.max(firstIndex, secondIndex); const rangeItems = this.view.data.slice(startIndex, endIndex + 1).filter(item => item.type === 'data'); if (this._settings.mode === 'cell') { const leafColumns = this.leafColumns; const startColumnIndex = Math.min(firstPoint.columnIndex, secondPoint.columnIndex); const endColumnIndex = Math.max(firstPoint.columnIndex, secondPoint.columnIndex); const selectedItems = []; for (let idx = 0; idx < rangeItems.length; idx++) { for (let columnIdx = startColumnIndex; columnIdx <= endColumnIndex; columnIdx++) { selectedItems.push({ dataItem: rangeItems[idx].data, column: leafColumns[columnIdx], columnIndex: columnIdx }); } } return selectedItems; } return rangeItems.map(item => ({ dataItem: item.data })); } updateSelectedState() { if (this.rowSelection) { this.view.updateSelectedState(); } } targetArgs(target, skipFocusable) { let result; this.tables.some(t => { result = t.targetArgs(target, skipFocusable); return result; }); return result; } rowSelected(dataItem) { return this.isSelected(dataItem); } cellSelected(dataItem, column, columnIndex) { return this.isSelected(dataItem, column, columnIndex); } // expose in the treelist? get leafColumns() { return this.columnsContainer.lockedLeafColumns.toArray().concat(this.columnsContainer.nonLockedLeafColumns.toArray()); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, decorators: [{ type: Injectable }] });