devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
1,100 lines (1,090 loc) • 94.1 kB
JavaScript
/**
* DevExtreme (esm/__internal/grids/grid_core/keyboard_navigation/module.js)
* Version: 22.1.9
* Build date: Tue Apr 18 2023
*
* Copyright (c) 2012 - 2023 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
getOuterHeight,
getHeight,
getWidth,
getOuterWidth
} from "../../../../core/utils/size";
import $ from "../../../../core/renderer";
import domAdapter from "../../../../core/dom_adapter";
import eventsEngine from "../../../../events/core/events_engine";
import {
isDefined,
isEmptyObject
} from "../../../../core/utils/type";
import {
addNamespace,
createEvent,
isCommandKeyPressed
} from "../../../../events/utils/index";
import pointerEvents from "../../../../events/pointer";
import {
name as clickEventName
} from "../../../../events/click";
import {
noop
} from "../../../../core/utils/common";
import browser from "../../../../core/utils/browser";
import {
keyboard
} from "../../../../events/short";
import devices from "../../../../core/devices";
import * as accessibility from "../../../../ui/shared/accessibility";
import {
focused
} from "../../../../ui/widget/selectors";
import gridCoreUtils from "../module_utils";
import core from "../modules";
import {
GridCoreKeyboardNavigationDom
} from "./dom";
const ROWS_VIEW_CLASS = "rowsview";
const EDIT_FORM_CLASS = "edit-form";
const GROUP_FOOTER_CLASS = "group-footer";
const ROW_CLASS = "dx-row";
const DATA_ROW_CLASS = "dx-data-row";
const GROUP_ROW_CLASS = "dx-group-row";
const HEADER_ROW_CLASS = "dx-header-row";
const EDIT_FORM_ITEM_CLASS = "edit-form-item";
const MASTER_DETAIL_ROW_CLASS = "dx-master-detail-row";
const FREESPACE_ROW_CLASS = "dx-freespace-row";
const VIRTUAL_ROW_CLASS = "dx-virtual-row";
const MASTER_DETAIL_CELL_CLASS = "dx-master-detail-cell";
const EDITOR_CELL_CLASS = "dx-editor-cell";
const DROPDOWN_EDITOR_OVERLAY_CLASS = "dx-dropdowneditor-overlay";
const COMMAND_EXPAND_CLASS = "dx-command-expand";
const COMMAND_SELECT_CLASS = "dx-command-select";
const COMMAND_EDIT_CLASS = "dx-command-edit";
const COMMAND_CELL_SELECTOR = "[class^=dx-command]";
const CELL_FOCUS_DISABLED_CLASS = "dx-cell-focus-disabled";
const DATEBOX_WIDGET_NAME = "dxDateBox";
const FOCUS_STATE_CLASS = "dx-state-focused";
const WIDGET_CLASS = "dx-widget";
const REVERT_BUTTON_CLASS = "dx-revert-button";
const FAST_EDITING_DELETE_KEY = "delete";
const INTERACTIVE_ELEMENTS_SELECTOR = "input:not([type='hidden']), textarea, a, select, button, [tabindex], .dx-checkbox";
const NON_FOCUSABLE_ELEMENTS_SELECTOR = INTERACTIVE_ELEMENTS_SELECTOR + ", .dx-dropdowneditor-icon";
const EDIT_MODE_ROW = "row";
const EDIT_MODE_FORM = "form";
const EDIT_MODE_BATCH = "batch";
const EDIT_MODE_CELL = "cell";
const FOCUS_TYPE_ROW = "row";
const FOCUS_TYPE_CELL = "cell";
const COLUMN_HEADERS_VIEW = "columnHeadersView";
const FUNCTIONAL_KEYS = ["shift", "control", "alt"];
function isGroupRow($row) {
return $row && $row.hasClass("dx-group-row")
}
function isDetailRow($row) {
return $row && $row.hasClass("dx-master-detail-row")
}
function isDataRow($row) {
return $row && !isGroupRow($row) && !isDetailRow($row)
}
function isNotFocusedRow($row) {
return !$row || $row.hasClass("dx-freespace-row") || $row.hasClass("dx-virtual-row")
}
function isEditorCell(that, $cell) {
return !that._isRowEditMode() && $cell && !$cell.hasClass("dx-command-select") && $cell.hasClass("dx-editor-cell")
}
function isElementDefined($element) {
return isDefined($element) && $element.length > 0
}
function isMobile() {
return "desktop" !== devices.current().deviceType
}
function isCellInHeaderRow($cell) {
return !!$cell.parent(".dx-header-row").length
}
function isFixedColumnIndexOffsetRequired(that, column) {
const rtlEnabled = that.option("rtlEnabled");
let result = false;
if (rtlEnabled) {
result = !("right" === column.fixedPosition || isDefined(column.command) && !isDefined(column.fixedPosition))
} else {
result = !(!isDefined(column.fixedPosition) || "left" === column.fixedPosition)
}
return result
}
function shouldPreventScroll(that) {
const keyboardController = that.getController("keyboardNavigation");
return keyboardController._isVirtualScrolling() ? that.option("focusedRowIndex") === keyboardController.getRowIndex() : false
}
const KeyboardNavigationController = core.ViewController.inherit({
init() {
this._dataController = this.getController("data");
this._selectionController = this.getController("selection");
this._editingController = this.getController("editing");
this._headerPanel = this.getView("headerPanel");
this._columnsController = this.getController("columns");
this._editorFactory = this.getController("editorFactory");
if (this.isKeyboardEnabled()) {
accessibility.subscribeVisibilityChange();
this._updateFocusTimeout = null;
this._fastEditingStarted = false;
this._focusedCellPosition = {};
this._canceledCellPosition = null;
const elementFocused = $element => {
this.setupFocusedView();
if (this._isNeedScroll) {
if ($element.is(":visible") && this._focusedView && this._focusedView.getScrollable) {
this._focusedView._scrollToElement($element);
this._isNeedScroll = false
}
}
};
this._editorFactory.focused.add(elementFocused);
this._initViewHandlers();
this._initDocumentHandlers();
this.createAction("onKeyDown")
}
},
_initViewHandlers() {
const rowsView = this.getView("rowsView");
const rowsViewFocusHandler = event => {
const $element = $(event.target);
const isRelatedTargetInRowsView = $(event.relatedTarget).closest(rowsView.element()).length;
const isLink = $element.is("a");
if (event.relatedTarget && isLink && !isRelatedTargetInRowsView && this._isEventInCurrentGrid(event)) {
let $focusedCell = this._getFocusedCell();
$focusedCell = !isElementDefined($focusedCell) ? rowsView.getCellElements(0).filter("[tabindex]").eq(0) : $focusedCell;
if (!$element.closest($focusedCell).length) {
event.preventDefault();
eventsEngine.trigger($focusedCell, "focus")
}
}
};
rowsView.renderCompleted.add(e => {
const $rowsView = rowsView.element();
const isFullUpdate = !e || "refresh" === e.changeType;
const isFocusedViewCorrect = this._focusedView && this._focusedView.name === rowsView.name;
let needUpdateFocus = false;
const isAppend = e && ("append" === e.changeType || "prepend" === e.changeType);
const $focusedElement = $(":focus");
const isFocusedElementCorrect = !$focusedElement.length || $focusedElement.closest($rowsView).length;
eventsEngine.off($rowsView, "focusin", rowsViewFocusHandler);
eventsEngine.on($rowsView, "focusin", rowsViewFocusHandler);
this._initPointerEventHandler();
this._initKeyDownHandler();
this._setRowsViewAttributes();
if (isFocusedViewCorrect && isFocusedElementCorrect) {
needUpdateFocus = this._isNeedFocus ? !isAppend : this._isHiddenFocus && isFullUpdate && !(null === e || void 0 === e ? void 0 : e.virtualColumnsScrolling);
needUpdateFocus && this._updateFocus(true)
}
})
},
_initDocumentHandlers() {
const document = domAdapter.getDocument();
this._documentClickHandler = this.createAction(e => {
const $target = $(e.event.target);
const isCurrentRowsViewClick = this._isEventInCurrentGrid(e.event) && $target.closest("." + this.addWidgetPrefix("rowsview")).length;
const isEditorOverlay = $target.closest(".dx-dropdowneditor-overlay").length;
const columnsResizerController = this.getController("columnsResizer");
const isColumnResizing = !!columnsResizerController && columnsResizerController.isResizing();
if (!isCurrentRowsViewClick && !isEditorOverlay && !isColumnResizing) {
const targetInsideFocusedView = this._focusedView ? $target.parents().filter(this._focusedView.element()).length > 0 : false;
!targetInsideFocusedView && this._resetFocusedCell(true);
this._resetFocusedView()
}
});
eventsEngine.on(document, addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), this._documentClickHandler)
},
_setRowsViewAttributes() {
const $rowsView = this._getRowsViewElement();
const isGridEmpty = !this._dataController.getVisibleRows().length;
if (isGridEmpty) {
this._applyTabIndexToElement($rowsView)
}
},
_initPointerEventHandler() {
const pointerEventName = !isMobile() ? pointerEvents.down : clickEventName;
const $rowsView = this._getRowsViewElement();
if (!isDefined(this._pointerEventAction)) {
this._pointerEventAction = this.createAction(this._pointerEventHandler)
}
eventsEngine.off($rowsView, addNamespace(pointerEventName, "dxDataGridKeyboardNavigation"), this._pointerEventAction);
eventsEngine.on($rowsView, addNamespace(pointerEventName, "dxDataGridKeyboardNavigation"), ".dx-row > td, .dx-row", this._pointerEventAction)
},
_initKeyDownHandler() {
const $rowsView = this._getRowsViewElement();
keyboard.off(this._keyDownListener);
this._keyDownListener = keyboard.on($rowsView, null, e => this._keyDownHandler(e))
},
dispose() {
this.callBase();
this._resetFocusedView();
keyboard.off(this._keyDownListener);
eventsEngine.off(domAdapter.getDocument(), addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), this._documentClickHandler);
clearTimeout(this._updateFocusTimeout);
accessibility.unsubscribeVisibilityChange()
},
optionChanged(args) {
const that = this;
switch (args.name) {
case "keyboardNavigation":
case "useLegacyKeyboardNavigation":
args.handled = true;
break;
default:
that.callBase(args)
}
},
isRowFocusType() {
return "row" === this.focusType
},
isCellFocusType() {
return "cell" === this.focusType
},
setRowFocusType() {
if (this.option("focusedRowEnabled")) {
this.focusType = "row"
}
},
setCellFocusType() {
this.focusType = "cell"
},
_keyDownHandler(e) {
var _a;
let needStopPropagation = true;
this._isNeedFocus = true;
this._isNeedScroll = true;
let isHandled = this._processOnKeyDown(e);
const isEditing = null === (_a = this._editingController) || void 0 === _a ? void 0 : _a.isEditing();
const {
originalEvent: originalEvent
} = e;
if (originalEvent.isDefaultPrevented()) {
this._isNeedFocus = false;
this._isNeedScroll = false;
return
}!FUNCTIONAL_KEYS.includes(e.keyName) && this._updateFocusedCellPositionByTarget(originalEvent.target);
if (!isHandled) {
switch (e.keyName) {
case "leftArrow":
case "rightArrow":
this._leftRightKeysHandler(e, isEditing);
isHandled = true;
break;
case "upArrow":
case "downArrow":
if (e.ctrl) {
accessibility.selectView("rowsView", this, originalEvent)
} else {
this._upDownKeysHandler(e, isEditing)
}
isHandled = true;
break;
case "pageUp":
case "pageDown":
this._pageUpDownKeyHandler(e);
isHandled = true;
break;
case "space":
isHandled = this._spaceKeyHandler(e, isEditing);
break;
case "A":
if (isCommandKeyPressed(e.originalEvent)) {
this._ctrlAKeyHandler(e, isEditing);
isHandled = true
} else {
isHandled = this._beginFastEditing(e.originalEvent)
}
break;
case "tab":
this._tabKeyHandler(e, isEditing);
isHandled = true;
break;
case "enter":
this._enterKeyHandler(e, isEditing);
isHandled = true;
break;
case "escape":
this._escapeKeyHandler(e, isEditing);
isHandled = true;
break;
case "F":
if (isCommandKeyPressed(e.originalEvent)) {
this._ctrlFKeyHandler(e);
isHandled = true
} else {
isHandled = this._beginFastEditing(e.originalEvent)
}
break;
case "F2":
this._f2KeyHandler();
isHandled = true;
break;
case "del":
case "backspace":
if (this._isFastEditingAllowed() && !this._isFastEditingStarted()) {
isHandled = this._beginFastEditing(originalEvent, true)
}
}
if (!isHandled && !this._beginFastEditing(originalEvent)) {
this._isNeedFocus = false;
this._isNeedScroll = false;
needStopPropagation = false
}
if (needStopPropagation) {
originalEvent.stopPropagation()
}
}
},
_processOnKeyDown(eventArgs) {
const {
originalEvent: originalEvent
} = eventArgs;
const args = {
handled: false,
event: originalEvent
};
this.executeAction("onKeyDown", args);
eventArgs.ctrl = originalEvent.ctrlKey;
eventArgs.alt = originalEvent.altKey;
eventArgs.shift = originalEvent.shiftKey;
return !!args.handled
},
_closeEditCell() {
setTimeout(() => {
this._editingController.closeEditCell()
})
},
_leftRightKeysHandler(eventArgs, isEditing) {
const rowIndex = this.getVisibleRowIndex();
const $event = eventArgs.originalEvent;
const $row = this._focusedView && this._focusedView.getRow(rowIndex);
const directionCode = this._getDirectionCodeByKey(eventArgs.keyName);
const isEditingNavigationMode = this._isFastEditingStarted();
const allowNavigate = (!isEditing || isEditingNavigationMode) && isDataRow($row);
if (allowNavigate) {
this.setCellFocusType();
isEditingNavigationMode && this._closeEditCell();
if (this._isVirtualColumnRender()) {
this._processVirtualHorizontalPosition(directionCode)
}
const $cell = this._getNextCell(directionCode);
if (isElementDefined($cell)) {
this._arrowKeysHandlerFocusCell($event, $cell, directionCode)
}
$event && $event.preventDefault()
}
},
_upDownKeysHandler(eventArgs, isEditing) {
var _a, _b;
const visibleRowIndex = this.getVisibleRowIndex();
const $row = this._focusedView && this._focusedView.getRow(visibleRowIndex);
const $event = eventArgs.originalEvent;
const isUpArrow = "upArrow" === eventArgs.keyName;
const dataSource = this._dataController.dataSource();
const isRowEditingInCurrentRow = null === (_b = null === (_a = this._editingController) || void 0 === _a ? void 0 : _a.isEditRowByIndex) || void 0 === _b ? void 0 : _b.call(_a, visibleRowIndex);
const isEditingNavigationMode = this._isFastEditingStarted();
const allowNavigate = (!isRowEditingInCurrentRow || !isEditing || isEditingNavigationMode) && $row && !isDetailRow($row);
if (allowNavigate) {
isEditingNavigationMode && this._closeEditCell();
if (!this._navigateNextCell($event, eventArgs.keyName)) {
if (this._isVirtualRowRender() && isUpArrow && dataSource && !dataSource.isLoading()) {
const rowHeight = getOuterHeight($row);
const rowIndex = this._focusedCellPosition.rowIndex - 1;
this._scrollBy(0, -rowHeight, rowIndex, $event)
}
}
$event && $event.preventDefault()
}
},
_pageUpDownKeyHandler(eventArgs) {
const pageIndex = this._dataController.pageIndex();
const pageCount = this._dataController.pageCount();
const pagingEnabled = this.option("paging.enabled");
const isPageUp = "pageUp" === eventArgs.keyName;
const pageStep = isPageUp ? -1 : 1;
const scrollable = this.getView("rowsView").getScrollable();
if (pagingEnabled && !this._isVirtualScrolling()) {
if ((isPageUp ? pageIndex > 0 : pageIndex < pageCount - 1) && !this._isVirtualScrolling()) {
this._dataController.pageIndex(pageIndex + pageStep);
eventArgs.originalEvent.preventDefault()
}
} else if (scrollable && getHeight(scrollable.container()) < getHeight(scrollable.$content())) {
this._scrollBy(0, getHeight(scrollable.container()) * pageStep);
eventArgs.originalEvent.preventDefault()
}
},
_spaceKeyHandler(eventArgs, isEditing) {
const rowIndex = this.getVisibleRowIndex();
const $target = $(eventArgs.originalEvent && eventArgs.originalEvent.target);
if (this.option("selection") && "none" !== this.option("selection").mode && !isEditing) {
const isFocusedRowElement = "row" === this._getElementType($target) && this.isRowFocusType() && isDataRow($target);
const isFocusedSelectionCell = $target.hasClass("dx-command-select");
if (isFocusedSelectionCell && "onClick" === this.option("selection.showCheckBoxesMode")) {
this._selectionController.startSelectionWithCheckboxes()
}
if (isFocusedRowElement || $target.parent().hasClass("dx-data-row") || $target.hasClass(this.addWidgetPrefix("rowsview"))) {
this._selectionController.changeItemSelection(rowIndex, {
shift: eventArgs.shift,
control: eventArgs.ctrl
});
eventArgs.originalEvent.preventDefault();
return true
}
return false
}
return this._beginFastEditing(eventArgs.originalEvent)
},
_ctrlAKeyHandler(eventArgs, isEditing) {
if (!isEditing && !eventArgs.alt && "multiple" === this.option("selection.mode") && this.option("selection.allowSelectAll")) {
this._selectionController.selectAll();
eventArgs.originalEvent.preventDefault()
}
},
_tabKeyHandler(eventArgs, isEditing) {
const editingOptions = this.option("editing");
const direction = eventArgs.shift ? "previous" : "next";
const isCellPositionDefined = isDefined(this._focusedCellPosition) && !isEmptyObject(this._focusedCellPosition);
let isOriginalHandlerRequired = !isCellPositionDefined || !eventArgs.shift && this._isLastValidCell(this._focusedCellPosition) || eventArgs.shift && this._isFirstValidCell(this._focusedCellPosition);
const eventTarget = eventArgs.originalEvent.target;
const focusedViewElement = this._focusedView && this._focusedView.element();
if (this._handleTabKeyOnMasterDetailCell(eventTarget, direction)) {
return
}
$(focusedViewElement).addClass(FOCUS_STATE_CLASS);
if (editingOptions && eventTarget && !isOriginalHandlerRequired) {
if ($(eventTarget).hasClass(this.addWidgetPrefix("rowsview"))) {
this._resetFocusedCell()
}
if (this._isVirtualColumnRender()) {
this._processVirtualHorizontalPosition(direction)
}
if (isEditing) {
if (!this._editingCellTabHandler(eventArgs, direction)) {
return
}
} else if (this._targetCellTabHandler(eventArgs, direction)) {
isOriginalHandlerRequired = true
}
}
if (isOriginalHandlerRequired) {
this._editorFactory.loseFocus();
if (this._editingController.isEditing() && !this._isRowEditMode()) {
this._resetFocusedCell(true);
this._resetFocusedView();
this._closeEditCell()
}
} else {
eventArgs.originalEvent.preventDefault()
}
},
_getMaxHorizontalOffset() {
const scrollable = this.component.getScrollable();
const rowsView = this.getView("rowsView");
const offset = scrollable ? scrollable.scrollWidth() - getWidth(rowsView.element()) : 0;
return offset
},
_isColumnRendered(columnIndex) {
const allVisibleColumns = this._columnsController.getVisibleColumns(null, true);
const renderedVisibleColumns = this._columnsController.getVisibleColumns();
const column = allVisibleColumns[columnIndex];
let result = false;
if (column) {
result = renderedVisibleColumns.indexOf(column) >= 0
}
return result
},
_isFixedColumn(columnIndex) {
const allVisibleColumns = this._columnsController.getVisibleColumns(null, true);
const column = allVisibleColumns[columnIndex];
return !!column && !!column.fixed
},
_isColumnVirtual(columnIndex) {
const localColumnIndex = columnIndex - this._columnsController.getColumnIndexOffset();
const visibleColumns = this._columnsController.getVisibleColumns();
const column = visibleColumns[localColumnIndex];
return !!column && "virtual" === column.command
},
_processVirtualHorizontalPosition(direction) {
const scrollable = this.component.getScrollable();
const columnIndex = this.getColumnIndex();
let nextColumnIndex;
let horizontalScrollPosition = 0;
let needToScroll = false;
switch (direction) {
case "next":
case "nextInRow": {
const columnsCount = this._getVisibleColumnCount();
nextColumnIndex = columnIndex + 1;
horizontalScrollPosition = this.option("rtlEnabled") ? this._getMaxHorizontalOffset() : 0;
if ("next" === direction) {
needToScroll = columnsCount === nextColumnIndex || this._isFixedColumn(columnIndex) && !this._isColumnRendered(nextColumnIndex)
} else {
needToScroll = columnsCount > nextColumnIndex && this._isFixedColumn(columnIndex) && !this._isColumnRendered(nextColumnIndex)
}
break
}
case "previous":
case "previousInRow":
nextColumnIndex = columnIndex - 1;
horizontalScrollPosition = this.option("rtlEnabled") ? 0 : this._getMaxHorizontalOffset();
if ("previous" === direction) {
const columnIndexOffset = this._columnsController.getColumnIndexOffset();
const leftEdgePosition = nextColumnIndex < 0 && 0 === columnIndexOffset;
needToScroll = leftEdgePosition || this._isFixedColumn(columnIndex) && !this._isColumnRendered(nextColumnIndex)
} else {
needToScroll = nextColumnIndex >= 0 && this._isFixedColumn(columnIndex) && !this._isColumnRendered(nextColumnIndex)
}
}
if (needToScroll) {
scrollable.scrollTo({
left: horizontalScrollPosition
})
} else if (isDefined(nextColumnIndex) && isDefined(direction) && this._isColumnVirtual(nextColumnIndex)) {
horizontalScrollPosition = this._getHorizontalScrollPositionOffset(direction);
0 !== horizontalScrollPosition && scrollable.scrollBy({
left: horizontalScrollPosition,
top: 0
})
}
},
_getHorizontalScrollPositionOffset(direction) {
let positionOffset = 0;
const $currentCell = this._getCell(this._focusedCellPosition);
const currentCellWidth = $currentCell && getOuterWidth($currentCell);
if (currentCellWidth > 0) {
const rtlMultiplier = this.option("rtlEnabled") ? -1 : 1;
positionOffset = "nextInRow" === direction || "next" === direction ? currentCellWidth * rtlMultiplier : currentCellWidth * rtlMultiplier * -1
}
return positionOffset
},
_editingCellTabHandler(eventArgs, direction) {
const eventTarget = eventArgs.originalEvent.target;
let $cell = this._getCellElementFromTarget(eventTarget);
let isEditingAllowed;
const $event = eventArgs.originalEvent;
const elementType = this._getElementType(eventTarget);
if ($cell.is("[class^=dx-command]")) {
return !this._targetCellTabHandler(eventArgs, direction)
}
this._updateFocusedCellPosition($cell);
const nextCellInfo = this._getNextCellByTabKey($event, direction, elementType);
$cell = nextCellInfo.$cell;
if (!$cell || this._handleTabKeyOnMasterDetailCell($cell, direction)) {
return false
}
const columnsController = this._columnsController;
const cellIndex = this.getView("rowsView").getCellIndex($cell);
const columnIndex = cellIndex + columnsController.getColumnIndexOffset();
const column = columnsController.getVisibleColumns(null, true)[columnIndex];
const $row = $cell.parent();
const rowIndex = this._getRowIndex($row);
const row = this._dataController.items()[rowIndex];
const editingController = this._editingController;
if (column && column.allowEditing) {
const isDataRow = !row || "data" === row.rowType;
isEditingAllowed = editingController.allowUpdating({
row: row
}) ? isDataRow : row && row.isNewRow
}
if (!isEditingAllowed) {
this._closeEditCell()
}
if (this._focusCell($cell, !nextCellInfo.isHighlighted)) {
if (!this._isRowEditMode() && isEditingAllowed) {
this._editFocusedCell()
} else {
this._focusInteractiveElement($cell, eventArgs.shift)
}
}
return true
},
_targetCellTabHandler(eventArgs, direction) {
const $event = eventArgs.originalEvent;
let eventTarget = $event.target;
let $cell = this._getCellElementFromTarget(eventTarget);
const $lastInteractiveElement = this._getInteractiveElement($cell, !eventArgs.shift);
let isOriginalHandlerRequired = false;
let elementType;
if (!isEditorCell(this, $cell) && $lastInteractiveElement.length && eventTarget !== $lastInteractiveElement.get(0)) {
isOriginalHandlerRequired = true
} else {
if (void 0 === this._focusedCellPosition.rowIndex && $(eventTarget).hasClass("dx-row")) {
this._updateFocusedCellPosition($cell)
}
elementType = this._getElementType(eventTarget);
if (this.isRowFocusType()) {
this.setCellFocusType();
if ("row" === elementType && isDataRow($(eventTarget))) {
eventTarget = this.getFirstValidCellInRow($(eventTarget));
elementType = this._getElementType(eventTarget)
}
}
const nextCellInfo = this._getNextCellByTabKey($event, direction, elementType);
$cell = nextCellInfo.$cell;
if (!$cell) {
return false
}
$cell = this._checkNewLineTransition($event, $cell);
if (!$cell) {
return false
}
this._focusCell($cell, !nextCellInfo.isHighlighted);
if (!isEditorCell(this, $cell)) {
this._focusInteractiveElement($cell, eventArgs.shift)
}
}
return isOriginalHandlerRequired
},
_getNextCellByTabKey($event, direction, elementType) {
let $cell = this._getNextCell(direction, elementType);
const args = $cell && this._fireFocusedCellChanging($event, $cell, true);
if (!args || args.cancel) {
return {}
}
if (args.$newCellElement) {
$cell = args.$newCellElement
}
return {
$cell: $cell,
isHighlighted: args.isHighlighted
}
},
_checkNewLineTransition($event, $cell) {
const rowIndex = this.getVisibleRowIndex();
const $row = $cell.parent();
if (rowIndex !== this._getRowIndex($row)) {
const cellPosition = this._getCellPosition($cell);
const args = this._fireFocusedRowChanging($event, $row);
if (args.cancel) {
return
}
if (args.rowIndexChanged) {
this.setFocusedColumnIndex(cellPosition.columnIndex);
$cell = this._getFocusedCell()
}
}
return $cell
},
_enterKeyHandler(eventArgs, isEditing) {
const $cell = this._getFocusedCell();
const rowIndex = this.getVisibleRowIndex();
const $row = this._focusedView && this._focusedView.getRow(rowIndex);
if (this.option("grouping.allowCollapsing") && isGroupRow($row) || this.option("masterDetail.enabled") && $cell && $cell.hasClass("dx-command-expand")) {
const key = this._dataController.getKeyByRowIndex(rowIndex);
const item = this._dataController.items()[rowIndex];
if (void 0 !== key && item && item.data && !item.data.isContinuation) {
this._dataController.changeRowExpand(key)
}
} else {
this._processEnterKeyForDataCell(eventArgs, isEditing)
}
},
_processEnterKeyForDataCell(eventArgs, isEditing) {
const direction = this._getEnterKeyDirection(eventArgs);
const allowEditingOnEnterKey = this._allowEditingOnEnterKey();
if (isEditing || !allowEditingOnEnterKey && direction) {
this._handleEnterKeyEditingCell(eventArgs.originalEvent);
if ("next" === direction || "previous" === direction) {
this._targetCellTabHandler(eventArgs, direction)
} else if ("upArrow" === direction || "downArrow" === direction) {
this._navigateNextCell(eventArgs.originalEvent, direction)
}
} else if (allowEditingOnEnterKey) {
this._startEditing(eventArgs)
}
},
_getEnterKeyDirection(eventArgs) {
const enterKeyDirection = this.option("keyboardNavigation.enterKeyDirection");
const isShift = eventArgs.shift;
if ("column" === enterKeyDirection) {
return isShift ? "upArrow" : "downArrow"
}
if ("row" === enterKeyDirection) {
return isShift ? "previous" : "next"
}
},
_handleEnterKeyEditingCell(event) {
const {
target: target
} = event;
const $cell = this._getCellElementFromTarget(target);
const isRowEditMode = this._isRowEditMode();
this._updateFocusedCellPosition($cell);
if (isRowEditMode) {
this._focusEditFormCell($cell);
setTimeout(this._editingController.saveEditData.bind(this._editingController))
} else {
eventsEngine.trigger($(target), "change");
this._closeEditCell();
event.preventDefault()
}
},
_escapeKeyHandler(eventArgs, isEditing) {
const $cell = this._getCellElementFromTarget(eventArgs.originalEvent.target);
if (isEditing) {
this._updateFocusedCellPosition($cell);
if (!this._isRowEditMode()) {
if ("cell" === this._editingController.getEditMode()) {
this._editingController.cancelEditData()
} else {
this._closeEditCell()
}
} else {
this._focusEditFormCell($cell);
this._editingController.cancelEditData();
if (0 === this._dataController.items().length) {
this._resetFocusedCell();
this._editorFactory.loseFocus()
}
}
eventArgs.originalEvent.preventDefault()
}
},
_ctrlFKeyHandler(eventArgs) {
if (this.option("searchPanel.visible")) {
const searchTextEditor = this._headerPanel.getSearchTextEditor();
if (searchTextEditor) {
searchTextEditor.focus();
eventArgs.originalEvent.preventDefault()
}
}
},
_f2KeyHandler() {
const isEditing = this._editingController.isEditing();
const rowIndex = this.getVisibleRowIndex();
const $row = this._focusedView && this._focusedView.getRow(rowIndex);
if (!isEditing && isDataRow($row)) {
this._startEditing()
}
},
_navigateNextCell($event, keyCode) {
const $cell = this._getNextCell(keyCode);
const directionCode = this._getDirectionCodeByKey(keyCode);
const isCellValid = $cell && this._isCellValid($cell);
const result = isCellValid ? this._arrowKeysHandlerFocusCell($event, $cell, directionCode) : false;
return result
},
_arrowKeysHandlerFocusCell($event, $nextCell, direction) {
const isVerticalDirection = "prevRow" === direction || "nextRow" === direction;
const args = this._fireFocusChangingEvents($event, $nextCell, isVerticalDirection, true);
$nextCell = args.$newCellElement;
if (!args.cancel && this._isCellValid($nextCell)) {
this._focus($nextCell, !args.isHighlighted);
return true
}
return false
},
_beginFastEditing(originalEvent, isDeleting) {
if (!this._isFastEditingAllowed() || originalEvent.altKey || originalEvent.ctrlKey || this._editingController.isEditing()) {
return false
}
if (isDeleting) {
this._startEditing(originalEvent, "delete")
} else {
const {
key: key
} = originalEvent;
const keyCode = originalEvent.keyCode || originalEvent.which;
const fastEditingKey = key || keyCode && String.fromCharCode(keyCode);
if (fastEditingKey && (1 === fastEditingKey.length || "delete" === fastEditingKey)) {
this._startEditing(originalEvent, fastEditingKey)
}
}
return true
},
_pointerEventHandler(e) {
const event = e.event || e;
let $target = $(event.currentTarget);
const rowsView = this.getView("rowsView");
const focusedViewElement = rowsView && rowsView.element();
const $parent = $target.parent();
const isInteractiveElement = $(event.target).is(INTERACTIVE_ELEMENTS_SELECTOR);
const isRevertButton = !!$(event.target).closest(".dx-revert-button").length;
const isExpandCommandCell = $target.hasClass("dx-command-expand");
if (!this._isEventInCurrentGrid(event)) {
return
}
if (!isRevertButton && (this._isCellValid($target, !isInteractiveElement) || isExpandCommandCell)) {
$target = this._isInsideEditForm($target) ? $(event.target) : $target;
this._focusView();
$(focusedViewElement).removeClass(FOCUS_STATE_CLASS);
if ($parent.hasClass("dx-freespace-row")) {
this._updateFocusedCellPosition($target);
this._applyTabIndexToElement(this._focusedView.element());
this._focusedView.focus(true)
} else if (!this._isMasterDetailCell($target)) {
this._clickTargetCellHandler(event, $target)
} else {
this._updateFocusedCellPosition($target)
}
} else if ($target.is("td")) {
this._resetFocusedCell()
}
},
_clickTargetCellHandler(event, $cell) {
const columnIndex = this.getView("rowsView").getCellIndex($cell);
const column = this._columnsController.getVisibleColumns()[columnIndex];
const isCellEditMode = this._isCellEditMode();
this.setCellFocusType();
const args = this._fireFocusChangingEvents(event, $cell, true);
$cell = args.$newCellElement;
if (!args.cancel) {
if (args.resetFocusedRow) {
this.getController("focus")._resetFocusedRow();
return
}
if (args.rowIndexChanged) {
$cell = this._getFocusedCell()
}
if (!args.isHighlighted && !isCellEditMode) {
this.setRowFocusType()
}
this._updateFocusedCellPosition($cell);
if (this._allowRowUpdating() && isCellEditMode && column && column.allowEditing) {
this._isNeedFocus = false;
this._isHiddenFocus = false
} else {
$cell = this._getFocusedCell();
const $target = event && $(event.target).closest(NON_FOCUSABLE_ELEMENTS_SELECTOR + ", td");
const skipFocusEvent = $target && $target.not($cell).is(NON_FOCUSABLE_ELEMENTS_SELECTOR);
const isEditor = !!column && !column.command && $cell.hasClass("dx-editor-cell");
const isDisabled = !isEditor && (!args.isHighlighted || skipFocusEvent);
this._focus($cell, isDisabled, skipFocusEvent)
}
} else {
this.setRowFocusType();
this.setFocusedRowIndex(args.prevRowIndex);
if (this._editingController.isEditing() && isCellEditMode) {
this._closeEditCell()
}
}
},
_allowRowUpdating() {
const rowIndex = this.getVisibleRowIndex();
const row = this._dataController.items()[rowIndex];
return this._editingController.allowUpdating({
row: row
}, "click")
},
focus(element) {
let activeElementSelector;
const focusedRowEnabled = this.option("focusedRowEnabled");
const isHighlighted = this._isCellElement($(element));
if (!element) {
activeElementSelector = ".dx-datagrid-rowsview .dx-row[tabindex]";
if (!focusedRowEnabled) {
activeElementSelector += ", .dx-datagrid-rowsview .dx-row > td[tabindex]"
}
element = this.component.$element().find(activeElementSelector).first()
}
element && this._focusElement($(element), isHighlighted)
},
getFocusedView() {
return this._focusedView
},
setupFocusedView() {
if (this.isKeyboardEnabled() && !isDefined(this._focusedView)) {
this._focusView()
}
},
_focusElement($element, isHighlighted) {
const rowsViewElement = $(this._getRowsViewElement());
const $focusedView = $element.closest(rowsViewElement);
const isRowFocusType = this.isRowFocusType();
let args = {};
if (!$focusedView.length || this._isCellElement($element) && !this._isCellValid($element)) {
return
}
this._focusView();
this._isNeedFocus = true;
this._isNeedScroll = true;
if (this._isCellElement($element) || isGroupRow($element)) {
this.setCellFocusType();
args = this._fireFocusChangingEvents(null, $element, false, isHighlighted);
$element = args.$newCellElement;
if (isRowFocusType && !args.isHighlighted) {
this.setRowFocusType()
}
}
if (!args.cancel) {
this._focus($element, !args.isHighlighted);
this._focusInteractiveElement($element)
}
},
_getFocusedViewByElement($element) {
const view = this.getFocusedView();
const $view = view && $(view.element());
return $element && 0 !== $element.closest($view).length
},
_focusView() {
this._focusedView = this.getView("rowsView")
},
_resetFocusedView() {
this.setRowFocusType();
this._focusedView = null
},
_focusInteractiveElement($cell, isLast) {
if (!$cell) {
return
}
const $focusedElement = this._getInteractiveElement($cell, isLast);
this._testInteractiveElement = $focusedElement;
gridCoreUtils.focusAndSelectElement(this, $focusedElement)
},
_focus($cell, disableFocus, skipFocusEvent) {
const $row = $cell && !$cell.hasClass("dx-row") ? $cell.closest(".dx-row") : $cell;
if ($row && isNotFocusedRow($row)) {
return
}
const focusedView = this._focusedView;
const $focusViewElement = focusedView && focusedView.element();
let $focusElement;
this._isHiddenFocus = disableFocus;
const isRowFocus = isGroupRow($row) || this.isRowFocusType();
if (isRowFocus) {
$focusElement = $row;
if (focusedView) {
this.setFocusedRowIndex(this._getRowIndex($row))
}
} else if (this._isCellElement($cell)) {
$focusElement = $cell;
this._updateFocusedCellPosition($cell)
}
if ($focusElement) {
if ($focusViewElement) {
$focusViewElement.find(".dx-row[tabindex], .dx-row > td[tabindex]").not($focusElement).removeClass("dx-cell-focus-disabled").removeAttr("tabindex")
}
eventsEngine.one($focusElement, "blur", e => {
if (e.relatedTarget) {
$focusElement.removeClass("dx-cell-focus-disabled")
}
});
if (!skipFocusEvent) {
this._applyTabIndexToElement($focusElement);
eventsEngine.trigger($focusElement, "focus")
}
if (disableFocus) {
$focusElement.addClass("dx-cell-focus-disabled");
if (isRowFocus) {
$cell.addClass("dx-cell-focus-disabled")
}
} else {
this._editorFactory.focus($focusElement)
}
}
},
_updateFocus(isRenderView) {
this._updateFocusTimeout = setTimeout(() => {
const editingController = this._editingController;
const isCellEditMode = "cell" === editingController.getEditMode();
const isBatchEditMode = "batch" === editingController.getEditMode();
if (isCellEditMode && editingController.hasChanges() || isBatchEditMode && editingController.isNewRowInEditMode()) {
editingController._focusEditingCell();
return
}
let $cell = this._getFocusedCell();
const isEditing = editingController.isEditing();
if ($cell && !(this._isMasterDetailCell($cell) && !this._isRowEditMode())) {
if (this._hasSkipRow($cell.parent())) {
const direction = this._focusedCellPosition && this._focusedCellPosition.rowIndex > 0 ? "upArrow" : "downArrow";
$cell = this._getNextCell(direction)
}
if (isElementDefined($cell)) {
if ($cell.is("td") || $cell.hasClass(this.addWidgetPrefix("edit-form-item"))) {
const isCommandCell = $cell.is("[class^=dx-command]");
const $focusedElementInsideCell = $cell.find(":focus");
const isFocusedElementDefined = isElementDefined($focusedElementInsideCell);
if ((isRenderView || !isCommandCell) && this._editorFactory.focus()) {
if (isCommandCell && isFocusedElementDefined) {
gridCoreUtils.focusAndSelectElement(this, $focusedElementInsideCell);
return
}!isFocusedElementDefined && this._focus($cell)
} else if (!isFocusedElementDefined && (this._isNeedFocus || this._isHiddenFocus)) {
this._focus($cell, this._isHiddenFocus)
}
if (isEditing) {
this._focusInteractiveElement.bind(this)($cell)
}
} else {
eventsEngine.trigger($cell, "focus")
}
}
}
})
},
_getFocusedCell() {
return $(this._getCell(this._focusedCellPosition))
},
_updateFocusedCellPositionByTarget(target) {
var _a;
const elementType = this._getElementType(target);
if ("row" === elementType && isDefined(null === (_a = this._focusedCellPosition) || void 0 === _a ? void 0 : _a.columnIndex)) {
const $row = $(target);
this._focusedView && isGroupRow($row) && this.setFocusedRowIndex(this._getRowIndex($row))
} else {
this._updateFocusedCellPosition(this._getCellElementFromTarget(target))
}
},
_updateFocusedCellPosition($cell, direction) {
const position = this._getCellPosition($cell, direction);
if (position) {
if (!$cell.length || position.rowIndex >= 0 && position.columnIndex >= 0) {
this.setFocusedCellPosition(position.rowIndex, position.columnIndex)
}
}
return position
},
_getFocusedColumnIndexOffset(columnIndex) {
let offset = 0;
const column = this._columnsController.getVisibleColumns()[columnIndex];
if (column && column.fixed) {
offset = this._getFixedColumnIndexOffset(column)
} else if (columnIndex >= 0) {
offset = this._columnsController.getColumnIndexOffset()
}
return offset
},
_getFixedColumnIndexOffset(column) {
const offset = isFixedColumnIndexOffsetRequired(this, column) ? this._getVisibleColumnCount() - this._columnsController.getVisibleColumns().length : 0;
return offset
},
_getCellPosition($cell, direction) {
let columnIndex;
const $row = isElementDefined($cell) && $cell.closest("tr");
const rowsView = this.getView("rowsView");
if (isElementDefined($row)) {
const rowIndex = this._getRowIndex($row);
columnIndex = rowsView.getCellIndex($cell, rowIndex);
columnIndex += this._getFocusedColumnIndexOffset(columnIndex);
if (direction) {
columnIndex = "previous" === direction ? columnIndex - 1 : columnIndex + 1;
columnIndex = this._applyColumnIndexBoundaries(columnIndex)
}
return {
rowIndex: rowIndex,
columnIndex: columnIndex
}
}
},
_focusCell($cell, isDisabled) {
if (this._isCellValid($cell)) {
this._focus($cell, isDisabled);
return true
}
},
_focusEditFormCell($cell) {
if ($cell.hasClass("dx-master-detail-cell")) {
this._editorFactory.focus($cell, true)
}
},
_resetFocusedCell(preventScroll) {
var _a;
const $cell = this._getFocusedCell();
isElementDefined($cell) && $cell.removeAttr("tabindex");
this._isNeedFocus = false;
this._isNeedScroll = false;
this._focusedCellPosition = {};
clearTimeout(this._updateFocusTimeout);
null === (_a = th