Skip to content

Commit

Permalink
refactor: clean up keyboard navigation mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen committed Oct 16, 2024
1 parent e45e4cd commit db03eb9
Showing 1 changed file with 34 additions and 49 deletions.
83 changes: 34 additions & 49 deletions packages/grid/src/vaadin-grid-keyboard-navigation-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';
import { get } from '@vaadin/component-base/src/path-utils.js';

function isRow(element) {
return element instanceof HTMLTableRowElement;
}

function isCell(element) {
return element instanceof HTMLTableCellElement;
}

function isDetailsCell(element) {
return element.matches('[part~="details-cell"]');
}

/**
* @polymerMixin
*/
Expand Down Expand Up @@ -84,25 +96,23 @@ export const KeyboardNavigationMixin = (superClass) =>

/** @private */
get __rowFocusMode() {
return (
this.__isRow(this._itemsFocusable) || this.__isRow(this._headerFocusable) || this.__isRow(this._footerFocusable)
);
return isRow(this._itemsFocusable) || isRow(this._headerFocusable) || isRow(this._footerFocusable);
}

set __rowFocusMode(value) {
['_itemsFocusable', '_footerFocusable', '_headerFocusable'].forEach((prop) => {
const focusable = this[prop];
if (value) {
const parent = focusable && focusable.parentElement;
if (this.__isCell(focusable)) {
if (isCell(focusable)) {
// Cell itself focusable (default)
this[prop] = parent;
} else if (this.__isCell(parent)) {
} else if (isCell(parent)) {
// Focus button mode is enabled for the column,
// button element inside the cell is focusable.
this[prop] = parent.parentElement;
}
} else if (!value && this.__isRow(focusable)) {
} else if (!value && isRow(focusable)) {
const cell = focusable.firstElementChild;
this[prop] = cell._focusButton || cell;
}
Expand Down Expand Up @@ -200,7 +210,7 @@ export const KeyboardNavigationMixin = (superClass) =>
if (parent) {
// Focus button mode is enabled for the column,
// button element inside the cell is focusable.
if (this.__isCell(parent)) {
if (isCell(parent)) {
cell = parent;
parent = parent.parentElement;
}
Expand Down Expand Up @@ -285,33 +295,13 @@ export const KeyboardNavigationMixin = (superClass) =>
return this._isExpanded(row._item);
}

/** @private */
__isDetailsCell(element) {
return element.matches('[part~="details-cell"]');
}

/** @private */
__isCell(element) {
return element instanceof HTMLTableCellElement;
}

/** @private */
__isRow(element) {
return element instanceof HTMLTableRowElement;
}

/** @private */
__getIndexOfChildElement(el) {
return Array.prototype.indexOf.call(el.parentNode.children, el);
}

/** @private */
_onNavigationKeyDown(e, key) {
e.preventDefault();

const isRTL = this.__isRTL;
const activeRow = e.composedPath().find((el) => this.__isRow(el));
const activeCell = e.composedPath().find((el) => this.__isCell(el));
const activeRow = e.composedPath().find(isRow);
const activeCell = e.composedPath().find(isCell);

// Handle keyboard interaction as defined in:
// https://w3c.github.io/aria-practices/#keyboard-interaction-24
Expand Down Expand Up @@ -406,7 +396,7 @@ export const KeyboardNavigationMixin = (superClass) =>
} else {
// In cell focus mode
const activeRowCells = [...activeRow.children].sort((a, b) => a._order - b._order);
if (activeCell === activeRowCells[0] || this.__isDetailsCell(activeCell)) {
if (activeCell === activeRowCells[0] || isDetailsCell(activeCell)) {
// "If focus is on the first cell in a row and row focus is supported, moves focus to the row."
this.__rowFocusMode = true;
this._onRowNavigation(activeRow, 0);
Expand Down Expand Up @@ -445,7 +435,7 @@ export const KeyboardNavigationMixin = (superClass) =>
if (rowGroup === this.$.items) {
return bodyFallbackIndex !== undefined ? bodyFallbackIndex : row.index;
}
return this.__getIndexOfChildElement(row);
return [...rowGroup.children].indexOf(row);
}

/**
Expand Down Expand Up @@ -486,7 +476,7 @@ export const KeyboardNavigationMixin = (superClass) =>

let dstIsRowDetails = false;
if (activeCell) {
const isRowDetails = this.__isDetailsCell(activeCell);
const isRowDetails = isDetailsCell(activeCell);
// Row details navigation logic
if (activeRowGroup === this.$.items) {
const item = activeRow._item;
Expand Down Expand Up @@ -536,13 +526,13 @@ export const KeyboardNavigationMixin = (superClass) =>
return;
}

let columnIndex = this.__getIndexOfChildElement(activeCell);
let columnIndex = [...activeRow.children].indexOf(activeCell);
if (this.$.items.contains(activeCell)) {
// lazy column rendering may be enabled, so we need use the always visible sizer cells to find the column index
columnIndex = [...this.$.sizer.children].findIndex((sizerCell) => sizerCell._column === activeCell._column);
}

const isCurrentCellRowDetails = this.__isDetailsCell(activeCell);
const isCurrentCellRowDetails = isDetailsCell(activeCell);
const activeRowGroup = activeRow.parentNode;
const currentRowIndex = this.__getIndexInGroup(activeRow, this._focusedItemIndex);

Expand All @@ -560,7 +550,7 @@ export const KeyboardNavigationMixin = (superClass) =>

if (dstIsRowDetails) {
// Focusing a row details cell on the destination row
const dstCell = [...dstRow.children].find((el) => this.__isDetailsCell(el));
const dstCell = [...dstRow.children].find(isDetailsCell);
dstCell.focus();
} else {
// Focusing a regular cell on the destination row
Expand Down Expand Up @@ -738,9 +728,9 @@ export const KeyboardNavigationMixin = (superClass) =>
this.$.focusexit.focus();
} else if (focusTarget === this._itemsFocusable) {
let itemsFocusTarget = focusTarget;
const targetRow = this.__isRow(focusTarget) ? focusTarget : focusTarget.parentNode;
const targetRow = isRow(focusTarget) ? focusTarget : focusTarget.parentNode;
this._ensureScrolledToIndex(this._focusedItemIndex);
if (targetRow.index !== this._focusedItemIndex && this.__isCell(focusTarget)) {
if (targetRow.index !== this._focusedItemIndex && isCell(focusTarget)) {
// The target row, which is about to be focused next, has been
// assigned with a new index since last focus, probably because of
// scrolling. Focus the row for the stored focused item index instead.
Expand All @@ -767,15 +757,10 @@ export const KeyboardNavigationMixin = (superClass) =>
e.preventDefault();

const element = e.composedPath()[0];
const isRow = this.__isRow(element);
if (isRow || !element._content || !element._content.firstElementChild) {
this.dispatchEvent(
new CustomEvent(isRow ? 'row-activate' : 'cell-activate', {
detail: {
model: this.__getRowModel(isRow ? element : element.parentElement),
},
}),
);
const elementType = isRow(element) ? 'row' : 'cell';
if (elementType === 'row' || !element._content || !element._content.firstElementChild) {
const model = this.__getRowModel(elementType === 'row' ? element : element.parentElement);
this.dispatchEvent(new CustomEvent(`${elementType}-activate`, { detail: { model } }));
}
}

Expand Down Expand Up @@ -1017,7 +1002,7 @@ export const KeyboardNavigationMixin = (superClass) =>
* @protected
*/
_scrollHorizontallyToCell(dstCell) {
if (dstCell.hasAttribute('frozen') || dstCell.hasAttribute('frozen-to-end') || this.__isDetailsCell(dstCell)) {
if (dstCell.hasAttribute('frozen') || dstCell.hasAttribute('frozen-to-end') || isDetailsCell(dstCell)) {
// These cells are, by design, always visible, no need to scroll.
return;
}
Expand All @@ -1030,7 +1015,7 @@ export const KeyboardNavigationMixin = (superClass) =>
rightBoundary = tableRect.right;
for (let i = dstCellIndex - 1; i >= 0; i--) {
const cell = dstRow.children[i];
if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
if (cell.hasAttribute('hidden') || isDetailsCell(cell)) {
continue;
}
if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
Expand All @@ -1040,7 +1025,7 @@ export const KeyboardNavigationMixin = (superClass) =>
}
for (let i = dstCellIndex + 1; i < dstRow.children.length; i++) {
const cell = dstRow.children[i];
if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
if (cell.hasAttribute('hidden') || isDetailsCell(cell)) {
continue;
}
if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
Expand Down

0 comments on commit db03eb9

Please sign in to comment.