Skip to content

Commit fff5b1c

Browse files
authored
[react-interactions] Add FocusTable colSpan support (#17019)
1 parent 4bc52ef commit fff5b1c

File tree

3 files changed

+233
-91
lines changed

3 files changed

+233
-91
lines changed

packages/react-interactions/accessibility/src/FocusTable.js

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import setElementCanTab from './shared/setElementCanTab';
1717
type FocusCellProps = {
1818
children?: React.Node,
1919
onKeyDown?: KeyboardEvent => void,
20+
colSpan?: number,
2021
};
2122

2223
type FocusRowProps = {
@@ -25,12 +26,12 @@ type FocusRowProps = {
2526

2627
type FocusTableProps = {|
2728
children: React.Node,
28-
id?: string,
2929
onKeyboardOut?: (
3030
direction: 'left' | 'right' | 'up' | 'down',
31-
focusTableByID: (id: string) => void,
31+
event: KeyboardEvent,
3232
) => void,
33-
wrap?: boolean,
33+
wrapX?: boolean,
34+
wrapY?: boolean,
3435
tabScope?: ReactScope,
3536
allowModifiers?: boolean,
3637
|};
@@ -69,30 +70,59 @@ function focusScope(cell: ReactScopeMethods, event?: KeyboardEvent): void {
6970
}
7071
}
7172

72-
function focusCellByIndex(
73+
// This takes into account colSpan
74+
function focusCellByColumnIndex(
7375
row: ReactScopeMethods,
74-
cellIndex: number,
76+
columnIndex: number,
7577
event?: KeyboardEvent,
7678
): void {
7779
const cells = row.getChildren();
7880
if (cells !== null) {
79-
const cell = cells[cellIndex];
80-
if (cell) {
81-
focusScope(cell, event);
81+
let colSize = 0;
82+
for (let i = 0; i < cells.length; i++) {
83+
const cell = cells[i];
84+
if (cell) {
85+
colSize += cell.getProps().colSpan || 1;
86+
if (colSize > columnIndex) {
87+
focusScope(cell, event);
88+
return;
89+
}
90+
}
8291
}
8392
}
8493
}
8594

95+
function getCellIndexes(
96+
cells: Array<ReactScopeMethods>,
97+
currentCell: ReactScopeMethods,
98+
): [number, number] {
99+
let totalColSpan = 0;
100+
for (let i = 0; i < cells.length; i++) {
101+
const cell = cells[i];
102+
if (cell === currentCell) {
103+
return [i, i + totalColSpan];
104+
}
105+
const colSpan = cell.getProps().colSpan;
106+
if (colSpan) {
107+
totalColSpan += colSpan - 1;
108+
}
109+
}
110+
return [-1, -1];
111+
}
112+
86113
function getRowCells(currentCell: ReactScopeMethods) {
87114
const row = currentCell.getParent();
88115
if (row !== null && row.getProps().type === 'row') {
89116
const cells = row.getChildren();
90117
if (cells !== null) {
91-
const rowIndex = cells.indexOf(currentCell);
92-
return [cells, rowIndex];
118+
const [rowIndex, rowIndexWithColSpan] = getCellIndexes(
119+
cells,
120+
currentCell,
121+
);
122+
return [cells, rowIndex, rowIndexWithColSpan];
93123
}
94124
}
95-
return [null, 0];
125+
return [null, -1, -1];
96126
}
97127

98128
function getRows(currentCell: ReactScopeMethods) {
@@ -107,7 +137,7 @@ function getRows(currentCell: ReactScopeMethods) {
107137
}
108138
}
109139
}
110-
return [null, 0];
140+
return [null, -1, -1];
111141
}
112142

113143
function triggerNavigateOut(
@@ -122,19 +152,7 @@ function triggerNavigateOut(
122152
const props = table.getProps();
123153
const onKeyboardOut = props.onKeyboardOut;
124154
if (props.type === 'table' && typeof onKeyboardOut === 'function') {
125-
const focusTableByID = (id: string) => {
126-
const topLevelTables = table.getChildrenFromRoot();
127-
if (topLevelTables !== null) {
128-
for (let i = 0; i < topLevelTables.length; i++) {
129-
const topLevelTable = topLevelTables[i];
130-
if (topLevelTable.getProps().id === id) {
131-
focusFirstCellOnTable(topLevelTable);
132-
return;
133-
}
134-
}
135-
}
136-
};
137-
onKeyboardOut(direction, focusTableByID);
155+
onKeyboardOut(direction, event);
138156
return;
139157
}
140158
}
@@ -166,8 +184,8 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
166184
function Table({
167185
children,
168186
onKeyboardOut,
169-
id,
170-
wrap,
187+
wrapX,
188+
wrapY,
171189
tabScope: TabScope,
172190
allowModifiers,
173191
}): FocusTableProps {
@@ -176,8 +194,8 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
176194
<TableScope
177195
type="table"
178196
onKeyboardOut={onKeyboardOut}
179-
id={id}
180-
wrap={wrap}
197+
wrapX={wrapX}
198+
wrapY={wrapY}
181199
tabScopeRef={tabScopeRef}
182200
allowModifiers={allowModifiers}>
183201
{TabScope ? (
@@ -193,7 +211,7 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
193211
return <TableScope type="row">{children}</TableScope>;
194212
}
195213

196-
function Cell({children, onKeyDown}): FocusCellProps {
214+
function Cell({children, onKeyDown, colSpan}): FocusCellProps {
197215
const scopeRef = useRef(null);
198216
const keyboard = useKeyboard({
199217
onKeyDown(event: KeyboardEvent): void {
@@ -232,18 +250,18 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
232250
}
233251
switch (key) {
234252
case 'ArrowUp': {
235-
const [cells, cellIndex] = getRowCells(currentCell);
253+
const [cells, , cellIndexWithColSpan] = getRowCells(currentCell);
236254
if (cells !== null) {
237255
const [rows, rowIndex] = getRows(currentCell);
238256
if (rows !== null) {
239257
if (rowIndex > 0) {
240258
const row = rows[rowIndex - 1];
241-
focusCellByIndex(row, cellIndex, event);
259+
focusCellByColumnIndex(row, cellIndexWithColSpan, event);
242260
} else if (rowIndex === 0) {
243-
const wrap = getTableProps(currentCell).wrap;
244-
if (wrap) {
261+
const wrapY = getTableProps(currentCell).wrapY;
262+
if (wrapY) {
245263
const row = rows[rows.length - 1];
246-
focusCellByIndex(row, cellIndex, event);
264+
focusCellByColumnIndex(row, cellIndexWithColSpan, event);
247265
} else {
248266
triggerNavigateOut(currentCell, 'up', event);
249267
}
@@ -253,22 +271,22 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
253271
return;
254272
}
255273
case 'ArrowDown': {
256-
const [cells, cellIndex] = getRowCells(currentCell);
274+
const [cells, , cellIndexWithColSpan] = getRowCells(currentCell);
257275
if (cells !== null) {
258276
const [rows, rowIndex] = getRows(currentCell);
259277
if (rows !== null) {
260278
if (rowIndex !== -1) {
261279
if (rowIndex === rows.length - 1) {
262-
const wrap = getTableProps(currentCell).wrap;
263-
if (wrap) {
280+
const wrapY = getTableProps(currentCell).wrapY;
281+
if (wrapY) {
264282
const row = rows[0];
265-
focusCellByIndex(row, cellIndex, event);
283+
focusCellByColumnIndex(row, cellIndexWithColSpan, event);
266284
} else {
267285
triggerNavigateOut(currentCell, 'down', event);
268286
}
269287
} else {
270288
const row = rows[rowIndex + 1];
271-
focusCellByIndex(row, cellIndex, event);
289+
focusCellByColumnIndex(row, cellIndexWithColSpan, event);
272290
}
273291
}
274292
}
@@ -282,8 +300,8 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
282300
focusScope(cells[rowIndex - 1]);
283301
event.preventDefault();
284302
} else if (rowIndex === 0) {
285-
const wrap = getTableProps(currentCell).wrap;
286-
if (wrap) {
303+
const wrapX = getTableProps(currentCell).wrapX;
304+
if (wrapX) {
287305
focusScope(cells[cells.length - 1], event);
288306
} else {
289307
triggerNavigateOut(currentCell, 'left', event);
@@ -297,8 +315,8 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
297315
if (cells !== null) {
298316
if (rowIndex !== -1) {
299317
if (rowIndex === cells.length - 1) {
300-
const wrap = getTableProps(currentCell).wrap;
301-
if (wrap) {
318+
const wrapX = getTableProps(currentCell).wrapX;
319+
if (wrapX) {
302320
focusScope(cells[0], event);
303321
} else {
304322
triggerNavigateOut(currentCell, 'right', event);
@@ -317,7 +335,11 @@ export function createFocusTable(scope: ReactScope): Array<React.Component> {
317335
},
318336
});
319337
return (
320-
<TableScope listeners={keyboard} ref={scopeRef} type="cell">
338+
<TableScope
339+
listeners={keyboard}
340+
ref={scopeRef}
341+
type="cell"
342+
colSpan={colSpan}>
321343
{children}
322344
</TableScope>
323345
);

0 commit comments

Comments
 (0)