UNPKG

@progress/kendo-angular-grid

Version:

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

219 lines (218 loc) 8.18 kB
/**----------------------------------------------------------------------------------------- * 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'; import { isPresent } from '../utils'; /** * @hidden */ export class NavigationCursor { model; changes = new Subject(); set metadata(value) { this._metadata = value; if (isPresent(value)) { const newActiveCol = value.hasDetailTemplate ? 1 : 0; const shouldChange = this.activeRow < value.headerRows && this.activeCol === 0; if (shouldChange && newActiveCol !== this.activeCol) { this.activeCol = newActiveCol; this.reset(); } } } get metadata() { return this._metadata; } activeRow = 0; activeCol = 0; virtualCol = 0; virtualRow = 0; _metadata; 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(row) { return this.metadata.columns.leafColumnsToRender.length - 1 + (this.metadata.hasDetailTemplate && (!row || !row.groupItem) ? 1 : 0); } offsetCol(offset) { const prevRow = this.model.findRow(this.virtualRow); const lastIndex = this.lastCellIndex(prevRow); 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 && this.metadata.hasDetailTemplate) { this.virtualCol += 1; return false; } 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 (!nextCell) { this.virtualCol = nextColIndex; return this.activate(cell.rowIndex, nextColIndex); } if (cell !== nextCell) { cell = nextCell; this.virtualCol = cell.colIndex; } else { this.virtualCol = virtualCol; } return this.activate(cell.rowIndex, this.virtualCol); } this.virtualCol = cell.colIndex; 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)); if (this.metadata.hasDetailTemplate && !this.model.findRow(nextIndex)) { nextIndex = offset > 0 ? nextIndex + 1 : nextIndex - 1; nextIndex = Math.max(0, Math.min(nextIndex, maxIndex)); } if (this.metadata.hasDetailTemplate && nextIndex === maxIndex) { if (this.model.lastRow.index !== maxIndex) { // Don't attempt to navigate past the last collapsed row. nextIndex--; } } const nextRow = this.model.findRow(nextIndex); if (nextRow) { // remove duplication let cell = this.model.findCell(this.virtualCol, nextRow); if (!cell) { return; } 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)); if (!cell) { return; } } 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 || this.metadata.hasDetailTemplate)) { return this.activate(this.virtualRow + offset, this.virtualCol); } this.virtualRow = cell.rowIndex; return this.activate(this.virtualRow, cell.colIndex); } }