Skip to content

Commit f6edba3

Browse files
kseamonjelbourn
authored andcommitted
feat(popover-edit): Arrow keys move focus to neighboring cells (#15762)
1 parent 309d564 commit f6edba3

File tree

6 files changed

+243
-35
lines changed

6 files changed

+243
-35
lines changed

src/cdk-experimental/popover-edit/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ ng_module(
1212
"@npm//@angular/forms",
1313
"@npm//rxjs",
1414
"//src/cdk/a11y",
15+
"//src/cdk/bidi",
1516
"//src/cdk/overlay",
17+
"//src/cdk/keycodes",
1618
"//src/cdk/portal",
1719
],
1820
)
@@ -25,8 +27,11 @@ ng_test_library(
2527
"@npm//@angular/common",
2628
"@npm//@angular/forms",
2729
"@npm//rxjs",
30+
"//src/cdk/bidi",
2831
"//src/cdk/collections",
32+
"//src/cdk/keycodes",
2933
"//src/cdk/table",
34+
"//src/cdk/testing",
3035
],
3136
)
3237

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

0 commit comments

Comments
 (0)