Skip to content

Commit 1f2328e

Browse files
committed
feat(popover-edit): Arrow keys move focus to neighboring cells
1 parent 1a84770 commit 1f2328e

File tree

5 files changed

+241
-35
lines changed

5 files changed

+241
-35
lines changed

src/cdk-experimental/popover-edit/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@
99
/** Selector for finding table cells. */
1010
export const CELL_SELECTOR = '.cdk-cell, .mat-cell, td';
1111

12+
/** Selector for finding editable table cells. */
13+
export const EDITABLE_CELL_SELECTOR = '.cdk-popover-edit-cell, .mat-popover-edit-cell';
14+
1215
/** Selector for finding table rows. */
1316
export const ROW_SELECTOR = '.cdk-row, .mat-row, tr';
1417

18+
/** Selector for finding the table element. */
19+
export const TABLE_SELECTOR = 'table, cdk-table, mat-table';
20+
1521
/** CSS class added to the edit lens pane. */
1622
export const EDIT_PANE_CLASS = 'cdk-edit-pane';
1723

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Directionality} from '@angular/cdk/bidi';
10+
import {Injectable} from '@angular/core';
11+
import {PartialObserver} from 'rxjs';
12+
13+
import {EDITABLE_CELL_SELECTOR, ROW_SELECTOR, TABLE_SELECTOR} from './constants';
14+
import {closest} from './polyfill';
15+
16+
/**
17+
* Service responsible for moving cell focus around in response to keyboard events.
18+
* May be overridden to customize the keyboard behavior of popover edit.
19+
*/
20+
@Injectable({providedIn: 'root'})
21+
export class FocusDispatcher {
22+
/** Observes keydown events triggered from the table. */
23+
readonly keyObserver: PartialObserver<KeyboardEvent>;
24+
25+
constructor(protected readonly directionality: Directionality) {
26+
this.keyObserver = {next: (event) => this.handleKeyboardEvent(event)};
27+
}
28+
29+
/**
30+
* Moves focus to earlier or later cells (in dom order) by offset cells relative to
31+
* currentCell.
32+
*/
33+
moveFocusHorizontally(currentCell: HTMLElement, offset: number): void {
34+
const cells = Array.from(closest(currentCell, TABLE_SELECTOR)!.querySelectorAll(
35+
EDITABLE_CELL_SELECTOR)) as HTMLElement[];
36+
const currentIndex = cells.indexOf(currentCell);
37+
const newIndex = currentIndex + offset;
38+
39+
if (cells[newIndex]) {
40+
cells[newIndex].focus();
41+
}
42+
}
43+
44+
/** Moves focus to up or down by row by offset cells relative to currentCell. */
45+
moveFocusVertically(currentCell: HTMLElement, offset: number): void {
46+
const currentRow = closest(currentCell, ROW_SELECTOR)!;
47+
const rows = Array.from(closest(currentRow, TABLE_SELECTOR)!.querySelectorAll(ROW_SELECTOR));
48+
const currentRowIndex = rows.indexOf(currentRow);
49+
const currentIndexWithinRow =
50+
Array.from(currentRow.querySelectorAll(EDITABLE_CELL_SELECTOR)).indexOf(currentCell);
51+
const newRowIndex = currentRowIndex + offset;
52+
53+
if (rows[newRowIndex]) {
54+
const rowToFocus =
55+
Array.from(rows[newRowIndex].querySelectorAll(EDITABLE_CELL_SELECTOR)) as HTMLElement[];
56+
57+
if (rowToFocus[currentIndexWithinRow]) {
58+
rowToFocus[currentIndexWithinRow].focus();
59+
}
60+
}
61+
}
62+
63+
/** Translates arrow keydown events into focus move operations. */
64+
protected handleKeyboardEvent(event: KeyboardEvent): void {
65+
const cell = closest(event.target, EDITABLE_CELL_SELECTOR) as HTMLElement | null;
66+
67+
if (!cell) {
68+
return;
69+
}
70+
71+
switch (event.key) {
72+
case 'ArrowUp':
73+
this.moveFocusVertically(cell, -1);
74+
break;
75+
case 'ArrowDown':
76+
this.moveFocusVertically(cell, 1);
77+
break;
78+
case 'ArrowLeft':
79+
this.moveFocusHorizontally(cell, this.directionality.value === 'ltr' ? -1 : 1);
80+
break;
81+
case 'ArrowRight':
82+
this.moveFocusHorizontally(cell, this.directionality.value === 'ltr' ? 1 : -1);
83+
break;
84+
default:
85+
// If the keyboard event is not handled, return now so that we don't `preventDefault`.
86+
return;
87+
}
88+
89+
event.preventDefault();
90+
}
91+
}

0 commit comments

Comments
 (0)