diff --git a/lib/KTable/index.vue b/lib/KTable/index.vue index a8acb8814..3fe9bd5be 100644 --- a/lib/KTable/index.vue +++ b/lib/KTable/index.vue @@ -294,156 +294,149 @@ * - header highlight */ handleKeydown(event, rowIndex, colIndex) { - const key = event.key; + switch (event.key) { + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + this.handleArrowKeys(event.key, rowIndex, colIndex); + break; + case 'Enter': + this.handleEnterKey(rowIndex, colIndex); + break; + case 'Tab': + this.handleTabKey(event, rowIndex, colIndex); + break; + default: + break; + } + }, + + handleArrowKeys(key, rowIndex, colIndex) { const totalRows = this.rows.length; const totalCols = this.headers.length; - + const lastRowIndex = totalRows - 1; + const lastColIndex = totalCols - 1; let nextRowIndex = rowIndex; let nextColIndex = colIndex; switch (key) { case 'ArrowUp': - if (rowIndex === -1) { - nextRowIndex = totalRows - 1; - } else { - nextRowIndex = rowIndex - 1; - } + nextRowIndex = rowIndex === -1 ? lastRowIndex : rowIndex - 1; break; case 'ArrowDown': - if (rowIndex === -1) { - nextRowIndex = 0; - } else if (rowIndex === totalRows - 1) { - nextRowIndex = -1; - } else { - nextRowIndex = (rowIndex + 1) % totalRows; - } + nextRowIndex = rowIndex === -1 ? 0 : rowIndex === lastRowIndex ? -1 : rowIndex + 1; break; case 'ArrowLeft': if (rowIndex === -1) { - if (colIndex > 0) { - nextColIndex = colIndex - 1; - } else { - nextColIndex = totalCols - 1; - nextRowIndex = totalRows - 1; - } - } else if (colIndex > 0) { - nextColIndex = colIndex - 1; + nextColIndex = colIndex > 0 ? colIndex - 1 : lastColIndex; + nextRowIndex = colIndex === 0 ? lastRowIndex : -1; } else { - nextColIndex = totalCols - 1; - nextRowIndex = rowIndex > 0 ? rowIndex - 1 : -1; + nextColIndex = colIndex > 0 ? colIndex - 1 : lastColIndex; + nextRowIndex = colIndex === 0 ? (rowIndex > 0 ? rowIndex - 1 : -1) : rowIndex; } break; case 'ArrowRight': - if (colIndex === totalCols - 1) { - if (rowIndex === totalRows - 1) { - nextColIndex = 0; - nextRowIndex = -1; - } else { - nextColIndex = 0; - nextRowIndex = rowIndex + 1; - } + if (colIndex === lastColIndex) { + nextColIndex = 0; + nextRowIndex = rowIndex === lastRowIndex ? -1 : rowIndex + 1; } else { nextColIndex = colIndex + 1; } break; - case 'Enter': - if (rowIndex === -1 && this.sortable) { - this.handleSort(colIndex); - } - break; - case 'Tab': { - // Identify all focusable elements inside the current cell - const currentCell = this.getCell(rowIndex, colIndex); + } + this.updateFocusState(nextRowIndex, nextColIndex); + event.preventDefault(); + }, - // Collect focusable elements using native DOM methods - const focusableElements = []; + handleEnterKey(rowIndex, colIndex) { + if (rowIndex === -1 && this.sortable) { + this.handleSort(colIndex); + } + }, - if (currentCell) { - const buttons = currentCell.getElementsByTagName('button'); - const links = currentCell.getElementsByTagName('a'); - const inputs = currentCell.getElementsByTagName('input'); - const selects = currentCell.getElementsByTagName('select'); - const textareas = currentCell.getElementsByTagName('textarea'); + handleTabKey(event, rowIndex, colIndex) { + const totalRows = this.rows.length; + const totalCols = this.headers.length; + let nextRowIndex = rowIndex; + let nextColIndex = colIndex; - focusableElements.push(...buttons, ...links, ...inputs, ...selects, ...textareas); - } + const currentCell = this.getCell(rowIndex, colIndex); + const focusableElements = this.getFocusableElements(currentCell); + const focusedElement = document.activeElement; - const focusedElementIndex = focusableElements.indexOf(document.activeElement); - if (focusableElements.length > 0) { - if (!event.shiftKey) { - // if navigating between more focusable elements within the cell - if (focusedElementIndex < focusableElements.length - 1) { - focusableElements[focusedElementIndex + 1].focus(); - event.preventDefault(); - return; - } else { - if (colIndex < totalCols - 1) { - nextColIndex = colIndex + 1; - } else if (rowIndex < totalRows - 1) { - nextColIndex = 0; - nextRowIndex = rowIndex + 1; - } else { - // Allow default behavior when reaching the last cell - return; - } - } - } else { - if (focusedElementIndex < focusableElements.length - 1) { - // if navigating between more focusable elements within the cell - focusableElements[focusedElementIndex + 1].focus(); - event.preventDefault(); - return; - } else { - if (colIndex > 0) { - nextColIndex = colIndex - 1; - } else if (rowIndex > 0) { - nextColIndex = totalCols - 1; - nextRowIndex = rowIndex - 1; - } else { - // Allow default behavior when reaching the first cell - return; - } - } - } + // Include the cell itself as the first focusable element + const cellAndFocusableElements = [currentCell, ...focusableElements]; + const focusedIndex = cellAndFocusableElements.indexOf(focusedElement); + + if (!event.shiftKey) { + // Tab key navigation + if (focusedIndex < cellAndFocusableElements.length - 1) { + // Move to the next focusable element within the cell + cellAndFocusableElements[focusedIndex + 1].focus(); + event.preventDefault(); + return; + } else { + // Move to the first focusable element of the next cell + if (colIndex < totalCols - 1) { + nextColIndex = colIndex + 1; + } else if (rowIndex < totalRows - 1) { + nextColIndex = 0; + nextRowIndex = rowIndex + 1; } else { - if (!event.shiftKey) { - if (colIndex < totalCols - 1) { - nextColIndex = colIndex + 1; - } else if (rowIndex < totalRows - 1) { - nextColIndex = 0; - nextRowIndex = rowIndex + 1; - } else { - // Allow default behavior when reaching the last cell - return; - } - } else { - if (colIndex > 0) { - nextColIndex = colIndex - 1; - } else if (rowIndex > 0) { - nextColIndex = totalCols - 1; - nextRowIndex = rowIndex - 1; - } else { - // Allow default behavior when reaching the first cell - return; - } - } + return; // Allow default Tab behavior when reaching the end } - - break; + const nextCell = this.getCell(nextRowIndex, nextColIndex); + const nextFocusableElements = this.getFocusableElements(nextCell); + const nextCellAndFocusableElements = [nextCell, ...nextFocusableElements]; + nextCellAndFocusableElements[0].focus(); + this.updateFocusState(nextRowIndex, nextColIndex); + event.preventDefault(); } - - default: + } else { + // Shift+Tab key navigation + if (focusedIndex > 0) { + // Move to the previous focusable element within the cell + cellAndFocusableElements[focusedIndex - 1].focus(); + event.preventDefault(); return; + } else { + // Move to the last focusable element of the previous cell + if (colIndex > 0) { + nextColIndex = colIndex - 1; + } else if (rowIndex > 0) { + nextColIndex = totalCols - 1; + nextRowIndex = rowIndex - 1; + } else { + return; // Allow default Shift+Tab behavior when at the beginning + } + const prevCell = this.getCell(nextRowIndex, nextColIndex); + const prevFocusableElements = this.getFocusableElements(prevCell); + const prevCellAndFocusableElements = [prevCell, ...prevFocusableElements]; + prevCellAndFocusableElements[prevCellAndFocusableElements.length - 1].focus(); + this.updateFocusState(nextRowIndex, nextColIndex, false); + event.preventDefault(); + } } - - this.focusCell(nextRowIndex, nextColIndex); + }, + updateFocusState(nextRowIndex, nextColIndex, shouldFocusCell = true) { this.focusedRowIndex = nextRowIndex === -1 ? null : nextRowIndex; this.focusedColIndex = nextColIndex; - this.highlightHeader(nextColIndex); + // Focus the cell only if it is necessary + if (shouldFocusCell) { + this.focusCell(nextRowIndex, nextColIndex); + } + }, - event.preventDefault(); + getFocusableElements(cell) { + if (!cell) return []; + const focusableSelectors = ['button', 'a', 'input', 'select', 'textarea']; + return focusableSelectors.flatMap(selector => + Array.from(cell.getElementsByTagName(selector)) + ); }, + getCell(rowIndex, colIndex) { if (rowIndex === -1) { return this.$refs[`header-${colIndex}`][0];