@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.
177 lines (176 loc) • 6.46 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Subject } from 'rxjs';
/**
* @hidden
*/
export class NavigationCursor {
model;
changes = new Subject();
metadata;
activeRow = 0;
activeCol = 0;
virtualCol = 0;
virtualRow = 0;
get row() {
return this.model.findRow(this.activeRow);
}
get cell() {
const row = this.row;
if (row) {
return this.model.findCell(this.activeCol, row);
}
}
get dataRowIndex() {
const row = this.row;
if (row) {
return row.dataRowIndex;
}
return -1;
}
constructor(model) {
this.model = model;
}
/**
* Assumes and announces a new cursor position.
*/
reset(rowIndex = this.activeRow, colIndex = this.activeCol, force = true) {
if (this.activate(rowIndex, colIndex, force)) {
this.virtualRow = rowIndex;
this.virtualCol = colIndex;
}
}
activate(rowIndex, colIndex, force) {
if (!force && this.isActiveRange(rowIndex, colIndex)) {
return false;
}
const prevColIndex = this.activeCol;
const prevRowIndex = this.activeRow;
this.activeCol = colIndex;
this.activeRow = rowIndex;
this.changes.next({
colIndex,
prevColIndex,
prevRowIndex,
rowIndex
});
return true;
}
isActiveRange(rowIndex, colIndex) {
if (this.activeRow !== rowIndex) {
return false;
}
const cell = this.cell;
const { start, end } = this.model.cellRange(cell);
return !cell || (start <= colIndex && colIndex <= end);
}
/**
* Assumes a new cursor position without announcing it.
*/
assume(rowIndex = this.activeRow, colIndex = this.activeCol) {
this.virtualRow = rowIndex;
this.virtualCol = colIndex;
this.activeCol = colIndex;
this.activeRow = rowIndex;
}
/**
* Announces a current cursor position to subscribers.
*/
announce() {
this.changes.next({
colIndex: this.activeCol,
prevColIndex: this.activeCol,
prevRowIndex: this.activeRow,
rowIndex: this.activeRow
});
}
activateVirtualCell(cell) {
const rowRange = this.model.rowRange(cell);
const cellRange = this.model.cellRange(cell);
const activeCol = this.activeCol;
const activeRow = this.activeRow;
if (rowRange.start <= activeRow && activeRow <= rowRange.end &&
cellRange.start <= activeCol && activeCol <= cellRange.end) {
this.activeRow = cell.rowIndex;
this.activeCol = cell.colIndex;
return true;
}
}
isActive(rowIndex, colIndex) {
return this.activeCol === colIndex && this.activeRow === rowIndex;
}
moveUp(offset = 1) {
return this.offsetRow(-offset);
}
moveDown(offset = 1) {
return this.offsetRow(offset);
}
moveLeft(offset = 1) {
return this.offsetCol(-offset);
}
moveRight(offset = 1) {
return this.offsetCol(offset);
}
lastCellIndex() {
return this.metadata.columns.leafColumnsToRender.length - 1;
}
offsetCol(offset) {
const prevRow = this.model.findRow(this.virtualRow);
const lastIndex = this.lastCellIndex();
const virtualCol = this.virtualCol;
this.virtualCol = Math.max(0, Math.min(virtualCol + offset, lastIndex));
let nextColIndex = this.virtualCol;
const nextRowIndex = this.virtualRow;
let cell = this.model.findCell(this.virtualCol, prevRow);
if (!cell && this.metadata.virtualColumns) {
return this.activate(nextRowIndex, nextColIndex);
}
if (cell.colSpan > 1 && cell.colIndex <= virtualCol && virtualCol < cell.colIndex + cell.colSpan) {
nextColIndex = offset > 0 ? Math.min(cell.colIndex + cell.colSpan, lastIndex) : Math.max(0, cell.colIndex + offset);
const nextCell = this.model.findCell(nextColIndex, prevRow);
if (cell !== nextCell) {
cell = nextCell;
this.virtualCol = cell.colIndex;
}
else {
this.virtualCol = virtualCol;
}
}
return this.activate(cell.rowIndex, cell.colIndex);
}
offsetRow(offset) {
let nextColIndex = this.virtualCol;
if (this.metadata && this.metadata.isVirtual) {
const maxIndex = this.metadata.maxLogicalRowIndex;
let nextIndex = Math.max(0, Math.min(this.activeRow + offset, maxIndex));
const nextRow = this.model.findRow(nextIndex);
if (nextRow) {
// remove duplication
let cell = this.model.findCell(this.virtualCol, nextRow);
if (cell.rowIndex <= this.virtualRow && offset > 0 && cell.rowSpan > 1) {
cell = this.model.findCell(this.virtualCol, this.model.findRow(cell.rowIndex + cell.rowSpan - 1 + offset));
}
nextIndex = cell.rowIndex;
nextColIndex = cell.colIndex;
}
this.virtualRow = nextIndex;
return this.activate(nextIndex, nextColIndex);
}
const nextRow = this.model.findRow(this.virtualRow + offset) || this.model.nextRow(this.virtualRow, offset);
if (!nextRow) {
return false;
}
let cell = this.model.findCell(this.virtualCol, nextRow);
if (cell && cell.rowIndex <= this.virtualRow && offset > 0 && cell.rowSpan > 1) { // spanned cell go to next
const nextPos = cell.rowIndex + cell.rowSpan - 1 + offset;
cell = this.model.findCell(this.virtualCol, this.model.findRow(nextPos));
}
if (!cell && this.metadata.virtualColumns) {
return this.activate(this.virtualRow + offset, this.virtualCol);
}
this.virtualRow = cell.rowIndex;
return this.activate(this.virtualRow, cell.colIndex);
}
}