UNPKG

@eclipse-scout/core

Version:
205 lines (179 loc) 7.04 kB
/* * Copyright (c) 2010, 2023 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {arrays, graphics, KeyStroke, Rectangle, ScoutKeyboardEvent, Table, TableRow} from '../../index'; export abstract class AbstractTableNavigationKeyStroke extends KeyStroke { declare field: Table; constructor(table: Table) { super(); this.repeatable = true; this.field = table; this.shift = !table.multiSelect ? false : undefined; this.stopPropagation = true; this.keyStrokeMode = KeyStroke.Mode.DOWN; this.inheritAccessibility = false; } protected override _accept(event: ScoutKeyboardEvent): boolean { let accepted = super._accept(event); if (!accepted) { return false; } if (!this.field.visibleRows.length) { // Key stroke is accepted but currently not executable because there are no rows. // Apply propagation flags to prevent bubbling, a focused table should always swallow the navigation keys even if there are no rows. this._applyPropagationFlags(event); return false; } if (event.shiftKey && this.field.footer?.$controlContainer?.has(event.target).length) { // Allow text selection inside control container (labels, iframes etc.) return false; } let $activeElement = this.field.$container.activeElement(); let elementType = $activeElement[0].tagName.toLowerCase(); let $filterInput = this.field.$container.data('filter-field'); if ((!$filterInput || $activeElement[0] !== $filterInput[0]) && (elementType === 'textarea' || elementType === 'input') && (!event.originalEvent || (event.originalEvent && !event.originalEvent.smartFieldEvent))) { return false; } this.field.$container.addClass('keyboard-navigation'); return true; } /** * Returns viewport sensitive information containing the first and last visible row in the viewport. */ protected _viewportInfo(): ViewPortInfo { let table = this.field, viewport: ViewPortInfo = {}, rows = table.visibleRows; if (rows.length === 0) { return viewport; } let viewportBounds = graphics.offsetBounds(table.$data); let dataInsets = graphics.insets(table.$data); let dataMarginTop = table.$data.cssMarginTop(); viewportBounds = viewportBounds.subtract(dataInsets); // if data has a negative margin, adjust viewport otherwise a selected first row will never be in the viewport if (dataMarginTop < 0) { viewportBounds.y -= Math.abs(dataMarginTop); viewportBounds.height += Math.abs(dataMarginTop); } let firstRow = this._findFirstRowInViewport(table, viewportBounds); let lastRow = this._findLastRowInViewport(table, rows.indexOf(firstRow), viewportBounds); viewport.firstRow = firstRow; viewport.lastRow = lastRow; return viewport; } firstRowAfterSelection(): TableRow { let $selectedRows = this.field.$selectedRows(); if (!$selectedRows.length) { return; } let rows = this.field.visibleRows, row = $selectedRows.last().data('row') as TableRow, rowIndex = this.field.filteredRows().indexOf(row); return rows[rowIndex + 1]; } firstRowBeforeSelection(): TableRow { let $selectedRows = this.field.$selectedRows(); if (!$selectedRows.length) { return; } let rows = this.field.visibleRows, row = $selectedRows.first().data('row') as TableRow, rowIndex = this.field.visibleRows.indexOf(row); return rows[rowIndex - 1]; } /** * Searches for the last selected row in the current selection block, starting from rowIndex. Expects row at rowIndex to be selected. */ protected _findLastSelectedRowBefore(table: Table, rowIndex: number): TableRow { let rows = table.visibleRows; if (rowIndex === 0) { return rows[rowIndex]; } let row = arrays.findFromReverse(rows, rowIndex, (row, i) => { let previousRow = rows[i - 1]; if (!previousRow) { return false; } return !table.isRowSelected(previousRow); }); // when no row has been found, use first row in table if (!row) { row = rows[0]; } return row; } /** * Searches for the last selected row in the current selection block, starting from rowIndex. Expects row at rowIndex to be selected. */ protected _findLastSelectedRowAfter(table: Table, rowIndex: number): TableRow { let rows = table.visibleRows; if (rowIndex === rows.length - 1) { return rows[rowIndex]; } let row = arrays.findFrom(rows, rowIndex, (row, i) => { let nextRow = rows[i + 1]; if (!nextRow) { return false; } return !table.isRowSelected(nextRow); }); // when no row has been found, use last row in table if (!row) { row = rows[rows.length - 1]; } return row; } protected _findFirstRowInViewport(table: Table, viewportBounds: Rectangle): TableRow { let rows = table.visibleRows; return arrays.find(rows, (row, i) => { let $row = row.$row; if (!$row) { // If row is not rendered, it cannot be part of the view port -> check next row return false; } let rowOffset = $row.offset(); let rowMarginTop = row.$row.cssMarginTop(); // Selected row has a negative row margin // -> add this margin to the offset to make sure this function does always return the same row independent of selection state if (rowMarginTop < 0) { rowOffset.top += Math.abs(rowMarginTop); } // If the row is fully visible in the viewport -> break and return the row return viewportBounds.contains(rowOffset.left, rowOffset.top); }); } protected _findLastRowInViewport(table: Table, startRowIndex: number, viewportBounds: Rectangle): TableRow { let rows = table.visibleRows; if (startRowIndex === rows.length - 1) { return rows[startRowIndex]; } return arrays.findFromForward(rows, startRowIndex, (row, i) => { let nextRow = rows[i + 1]; if (!nextRow) { // If next row is not available (row is the last row) -> break and return current row return true; } let $nextRow = nextRow.$row; if (!$nextRow) { // If next row is not rendered anymore, current row has to be the last in the viewport return true; } let nextRowOffsetBounds = graphics.offsetBounds($nextRow); // If the next row is not fully visible in the viewport -> break and return current row return !viewportBounds.contains(nextRowOffsetBounds.x, nextRowOffsetBounds.y + nextRowOffsetBounds.height - 1); }); } protected override _isEnabled(): boolean { return !this.field.tileMode && super._isEnabled(); } } export type ViewPortInfo = { firstRow?: TableRow; lastRow?: TableRow };