@ni/nimble-components 29.6.0 → 29.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-components-bundle.js +1302 -41
- package/dist/all-components-bundle.js.map +1 -1
- package/dist/all-components-bundle.min.js +4053 -3810
- package/dist/all-components-bundle.min.js.map +1 -1
- package/dist/esm/table/components/cell/index.d.ts +9 -0
- package/dist/esm/table/components/cell/index.js +20 -0
- package/dist/esm/table/components/cell/index.js.map +1 -1
- package/dist/esm/table/components/cell/styles.js +17 -1
- package/dist/esm/table/components/cell/styles.js.map +1 -1
- package/dist/esm/table/components/cell/template.js +10 -2
- package/dist/esm/table/components/cell/template.js.map +1 -1
- package/dist/esm/table/components/group-row/index.d.ts +11 -3
- package/dist/esm/table/components/group-row/index.js +13 -1
- package/dist/esm/table/components/group-row/index.js.map +1 -1
- package/dist/esm/table/components/group-row/styles.js +7 -1
- package/dist/esm/table/components/group-row/styles.js.map +1 -1
- package/dist/esm/table/components/group-row/template.js +1 -1
- package/dist/esm/table/components/group-row/template.js.map +1 -1
- package/dist/esm/table/components/header/styles.js +7 -1
- package/dist/esm/table/components/header/styles.js.map +1 -1
- package/dist/esm/table/components/row/index.d.ts +16 -4
- package/dist/esm/table/components/row/index.js +34 -6
- package/dist/esm/table/components/row/index.js.map +1 -1
- package/dist/esm/table/components/row/styles.js +39 -1
- package/dist/esm/table/components/row/styles.js.map +1 -1
- package/dist/esm/table/components/row/template.js +4 -2
- package/dist/esm/table/components/row/template.js.map +1 -1
- package/dist/esm/table/index.d.ts +22 -2
- package/dist/esm/table/index.js +58 -1
- package/dist/esm/table/index.js.map +1 -1
- package/dist/esm/table/models/keyboard-navigation-manager.d.ts +96 -0
- package/dist/esm/table/models/keyboard-navigation-manager.js +1015 -0
- package/dist/esm/table/models/keyboard-navigation-manager.js.map +1 -0
- package/dist/esm/table/models/table-update-tracker.d.ts +2 -1
- package/dist/esm/table/models/table-update-tracker.js +20 -3
- package/dist/esm/table/models/table-update-tracker.js.map +1 -1
- package/dist/esm/table/models/virtualizer.d.ts +6 -2
- package/dist/esm/table/models/virtualizer.js +16 -22
- package/dist/esm/table/models/virtualizer.js.map +1 -1
- package/dist/esm/table/styles.js +21 -0
- package/dist/esm/table/styles.js.map +1 -1
- package/dist/esm/table/template.js +21 -3
- package/dist/esm/table/template.js.map +1 -1
- package/dist/esm/table/testing/table.pageobject.d.ts +7 -2
- package/dist/esm/table/testing/table.pageobject.js +16 -9
- package/dist/esm/table/testing/table.pageobject.js.map +1 -1
- package/dist/esm/table/types.d.ts +38 -0
- package/dist/esm/table/types.js +14 -0
- package/dist/esm/table/types.js.map +1 -1
- package/dist/esm/table-column/anchor/cell-view/index.d.ts +3 -0
- package/dist/esm/table-column/anchor/cell-view/index.js +13 -0
- package/dist/esm/table-column/anchor/cell-view/index.js.map +1 -1
- package/dist/esm/table-column/anchor/cell-view/template.js +4 -2
- package/dist/esm/table-column/anchor/cell-view/template.js.map +1 -1
- package/dist/esm/table-column/base/cell-view/index.d.ts +5 -0
- package/dist/esm/table-column/base/cell-view/index.js +7 -0
- package/dist/esm/table-column/base/cell-view/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
import { Observable } from '@microsoft/fast-element';
|
|
2
|
+
import { keyArrowDown, keyArrowLeft, keyArrowRight, keyArrowUp, keyEnd, keyEnter, keyEscape, keyFunction2, keyHome, keyPageDown, keyPageUp, keySpace, keyTab } from '@microsoft/fast-web-utilities';
|
|
3
|
+
import { FoundationElement } from '@microsoft/fast-foundation';
|
|
4
|
+
import { TableFocusType } from '../types';
|
|
5
|
+
import { TableGroupRow } from '../components/group-row';
|
|
6
|
+
import { TableRow } from '../components/row';
|
|
7
|
+
import { TableCell } from '../components/cell';
|
|
8
|
+
import { tableHeaderTag } from '../components/header';
|
|
9
|
+
import { TableCellView } from '../../table-column/base/cell-view';
|
|
10
|
+
/**
|
|
11
|
+
* Manages the keyboard navigation and focus within the table.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export class KeyboardNavigationManager {
|
|
15
|
+
get inNavigationMode() {
|
|
16
|
+
return (this.focusType !== TableFocusType.cellActionMenu
|
|
17
|
+
&& this.focusType !== TableFocusType.cellContent);
|
|
18
|
+
}
|
|
19
|
+
constructor(table, virtualizer) {
|
|
20
|
+
this.table = table;
|
|
21
|
+
this.virtualizer = virtualizer;
|
|
22
|
+
this.focusType = TableFocusType.none;
|
|
23
|
+
this.headerActionIndex = -1;
|
|
24
|
+
this.rowIndex = -1;
|
|
25
|
+
this.cellContentIndex = -1;
|
|
26
|
+
this.columnIndex = -1;
|
|
27
|
+
this.focusWithinTable = false;
|
|
28
|
+
this.isCurrentlyFocusingElement = false;
|
|
29
|
+
this.visibleRowNotifiers = [];
|
|
30
|
+
this.onTableFocusIn = (event) => {
|
|
31
|
+
this.focusWithinTable = true;
|
|
32
|
+
this.updateFocusStateFromActiveElement(false);
|
|
33
|
+
// Sets initial focus on the appropriate table content
|
|
34
|
+
const actionMenuOpen = this.table.openActionMenuRecordId !== undefined;
|
|
35
|
+
if ((event.target === this.table
|
|
36
|
+
|| this.focusType === TableFocusType.none)
|
|
37
|
+
&& !actionMenuOpen) {
|
|
38
|
+
let focusHeader = true;
|
|
39
|
+
if (this.hasRowOrCellFocusType()
|
|
40
|
+
&& this.scrollToAndFocusRow(this.rowIndex)) {
|
|
41
|
+
focusHeader = false;
|
|
42
|
+
}
|
|
43
|
+
if (focusHeader && !this.setFocusOnHeader()) {
|
|
44
|
+
// nothing to focus
|
|
45
|
+
this.table.blur();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this.onTableFocusOut = () => {
|
|
50
|
+
this.focusWithinTable = false;
|
|
51
|
+
};
|
|
52
|
+
this.onCellActionMenuBlur = (event) => {
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
const cell = event.detail;
|
|
55
|
+
if (cell.actionMenuButton) {
|
|
56
|
+
// Ensure that action menu buttons get hidden when no longer focused
|
|
57
|
+
this.setActionMenuButtonFocused(cell.actionMenuButton, false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
this.onCellViewFocusIn = (event) => {
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
this.updateFocusStateFromActiveElement(false);
|
|
63
|
+
};
|
|
64
|
+
this.onCellFocusIn = (event) => {
|
|
65
|
+
event.stopPropagation();
|
|
66
|
+
const cell = event.detail;
|
|
67
|
+
this.updateFocusStateFromActiveElement(true);
|
|
68
|
+
// Currently, clicking a non-interactive cell only updates the focus state to that row, it
|
|
69
|
+
// doesn't focus the cell. If we revisit this, we most likely need to set the cells to tabindex=-1
|
|
70
|
+
// upfront too, so their focusing behavior is consistent whether they've been previously keyboard
|
|
71
|
+
// focused or not.
|
|
72
|
+
if (this.focusType === TableFocusType.row
|
|
73
|
+
&& this.getActiveElement() === cell) {
|
|
74
|
+
this.focusCurrentRow(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
this.onCellBlur = (event) => {
|
|
78
|
+
event.stopPropagation();
|
|
79
|
+
const cell = event.detail;
|
|
80
|
+
this.setElementFocusable(cell, false);
|
|
81
|
+
};
|
|
82
|
+
this.onCaptureKeyDown = (event) => {
|
|
83
|
+
let handled = false;
|
|
84
|
+
if (event.key === keyTab) {
|
|
85
|
+
handled = this.onTabPressed(event.shiftKey);
|
|
86
|
+
}
|
|
87
|
+
else if (this.inNavigationMode) {
|
|
88
|
+
switch (event.key) {
|
|
89
|
+
case keyArrowLeft:
|
|
90
|
+
handled = this.onLeftArrowPressed();
|
|
91
|
+
break;
|
|
92
|
+
case keyArrowRight:
|
|
93
|
+
handled = this.onRightArrowPressed();
|
|
94
|
+
break;
|
|
95
|
+
case keyArrowUp:
|
|
96
|
+
handled = this.onUpArrowPressed();
|
|
97
|
+
break;
|
|
98
|
+
case keyArrowDown:
|
|
99
|
+
handled = this.onDownArrowPressed();
|
|
100
|
+
break;
|
|
101
|
+
case keyPageUp:
|
|
102
|
+
handled = this.onPageUpPressed();
|
|
103
|
+
break;
|
|
104
|
+
case keyPageDown:
|
|
105
|
+
handled = this.onPageDownPressed();
|
|
106
|
+
break;
|
|
107
|
+
case keyHome:
|
|
108
|
+
handled = this.onHomePressed(event.ctrlKey);
|
|
109
|
+
break;
|
|
110
|
+
case keyEnd:
|
|
111
|
+
handled = this.onEndPressed(event.ctrlKey);
|
|
112
|
+
break;
|
|
113
|
+
case keyEnter:
|
|
114
|
+
handled = this.onEnterPressed(event.ctrlKey);
|
|
115
|
+
break;
|
|
116
|
+
case keySpace:
|
|
117
|
+
handled = this.onSpacePressed(event.shiftKey);
|
|
118
|
+
break;
|
|
119
|
+
case keyFunction2:
|
|
120
|
+
handled = this.onF2Pressed();
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (handled) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
this.onKeyDown = (event) => {
|
|
131
|
+
if (!this.inNavigationMode && !event.defaultPrevented) {
|
|
132
|
+
if (event.key === keyEscape || event.key === keyFunction2) {
|
|
133
|
+
const row = this.getCurrentRow();
|
|
134
|
+
if (row) {
|
|
135
|
+
this.trySetCellFocus(row.getFocusableElements());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
this.onViewportKeyDown = (event) => {
|
|
141
|
+
let handleEvent = !this.inNavigationMode
|
|
142
|
+
&& (event.key === keyArrowUp || event.key === keyArrowDown);
|
|
143
|
+
switch (event.key) {
|
|
144
|
+
case keyPageUp:
|
|
145
|
+
case keyPageDown:
|
|
146
|
+
case keyHome:
|
|
147
|
+
case keyEnd:
|
|
148
|
+
handleEvent = true;
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
if (handleEvent) {
|
|
154
|
+
// Swallow key presses that would cause table scrolling, independently of keyboard navigation
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
event.stopImmediatePropagation();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
this.tableNotifier = Observable.getNotifier(this.table);
|
|
160
|
+
this.tableNotifier.subscribe(this, 'rowElements');
|
|
161
|
+
this.virtualizerNotifier = Observable.getNotifier(this.virtualizer);
|
|
162
|
+
this.virtualizerNotifier.subscribe(this, 'visibleItems');
|
|
163
|
+
}
|
|
164
|
+
resetFocusState() {
|
|
165
|
+
this.focusType = TableFocusType.none;
|
|
166
|
+
const activeElement = this.getActiveElement();
|
|
167
|
+
if (activeElement && this.isInTable(activeElement)) {
|
|
168
|
+
this.setDefaultFocus();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
get hasActiveRowOrCellFocus() {
|
|
172
|
+
return this.focusWithinTable && this.hasRowOrCellFocusType();
|
|
173
|
+
}
|
|
174
|
+
connect() {
|
|
175
|
+
this.table.addEventListener('keydown', this.onCaptureKeyDown, { capture: true });
|
|
176
|
+
this.table.addEventListener('keydown', this.onKeyDown);
|
|
177
|
+
this.table.addEventListener('focusin', this.onTableFocusIn);
|
|
178
|
+
this.table.addEventListener('focusout', this.onTableFocusOut);
|
|
179
|
+
this.table.viewport.addEventListener('keydown', this.onViewportKeyDown);
|
|
180
|
+
this.table.viewport.addEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
|
|
181
|
+
this.table.viewport.addEventListener('cell-view-focus-in', this.onCellViewFocusIn);
|
|
182
|
+
this.table.viewport.addEventListener('cell-focus-in', this.onCellFocusIn);
|
|
183
|
+
this.table.viewport.addEventListener('cell-blur', this.onCellBlur);
|
|
184
|
+
}
|
|
185
|
+
disconnect() {
|
|
186
|
+
this.table.removeEventListener('keydown', this.onCaptureKeyDown, { capture: true });
|
|
187
|
+
this.table.removeEventListener('keydown', this.onKeyDown);
|
|
188
|
+
this.table.removeEventListener('focusin', this.onTableFocusIn);
|
|
189
|
+
this.table.removeEventListener('focusout', this.onTableFocusOut);
|
|
190
|
+
this.table.viewport.removeEventListener('keydown', this.onViewportKeyDown);
|
|
191
|
+
this.table.viewport.removeEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
|
|
192
|
+
this.table.viewport.removeEventListener('cell-view-focus-in', this.onCellViewFocusIn);
|
|
193
|
+
this.table.viewport.removeEventListener('cell-focus-in', this.onCellFocusIn);
|
|
194
|
+
this.table.viewport.removeEventListener('cell-blur', this.onCellBlur);
|
|
195
|
+
}
|
|
196
|
+
handleChange(source, args) {
|
|
197
|
+
let focusRowAndCell = false;
|
|
198
|
+
if (source === this.virtualizer && args === 'visibleItems') {
|
|
199
|
+
focusRowAndCell = true;
|
|
200
|
+
}
|
|
201
|
+
else if (source === this.table && args === 'rowElements') {
|
|
202
|
+
for (const notifier of this.visibleRowNotifiers) {
|
|
203
|
+
notifier.unsubscribe(this);
|
|
204
|
+
}
|
|
205
|
+
this.visibleRowNotifiers = [];
|
|
206
|
+
for (const visibleRow of this.table.rowElements) {
|
|
207
|
+
const rowNotifier = Observable.getNotifier(visibleRow);
|
|
208
|
+
rowNotifier.subscribe(this, 'resolvedRowIndex');
|
|
209
|
+
if (visibleRow.resolvedRowIndex === this.rowIndex) {
|
|
210
|
+
focusRowAndCell = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (args === 'resolvedRowIndex'
|
|
215
|
+
&& this.isResolvedRowType(source)) {
|
|
216
|
+
if (source.resolvedRowIndex === this.rowIndex) {
|
|
217
|
+
focusRowAndCell = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (focusRowAndCell) {
|
|
221
|
+
// Focusable elements in cells, and action menus, are both blurred on scroll. To maintain our row/cell focus state,
|
|
222
|
+
// we focus the cell instead here. (We also don't want to refocus the cell content when the focusedRecycleCallback just
|
|
223
|
+
// blurred it.)
|
|
224
|
+
if (this.focusType === TableFocusType.cellActionMenu
|
|
225
|
+
|| this.focusType === TableFocusType.cellContent) {
|
|
226
|
+
this.setCellFocusState(this.columnIndex, this.rowIndex, false);
|
|
227
|
+
}
|
|
228
|
+
if (this.inNavigationMode && this.hasRowOrCellFocusType()) {
|
|
229
|
+
if (this.rowIndex > this.table.tableData.length - 1) {
|
|
230
|
+
// Focused row index no longer valid, coerce to 1st row if possible
|
|
231
|
+
if (this.table.tableData.length > 0) {
|
|
232
|
+
this.rowIndex = 0;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
if (this.focusWithinTable) {
|
|
236
|
+
this.setDefaultFocus();
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.focusType = TableFocusType.none;
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (this.focusWithinTable) {
|
|
245
|
+
this.focusCurrentRow(false);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
handleFocusedCellRecycling(hadRowOrCellFocus) {
|
|
251
|
+
if (hadRowOrCellFocus && !this.focusWithinTable) {
|
|
252
|
+
this.focusCurrentRow(false);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
onRowFocusIn(event) {
|
|
256
|
+
if (this.isCurrentlyFocusingElement) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const row = event.target;
|
|
260
|
+
if (this.isResolvedRowType(row)) {
|
|
261
|
+
if (this.rowIndex !== row.resolvedRowIndex) {
|
|
262
|
+
// If user focuses a row some other way (e.g. mouse), update our focus state so future keyboard nav
|
|
263
|
+
// will start from that row
|
|
264
|
+
this.setRowFocusState(row.resolvedRowIndex);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
onRowBlur(event) {
|
|
269
|
+
const row = event.target;
|
|
270
|
+
if (this.isResolvedRowType(row)) {
|
|
271
|
+
this.setElementFocusable(row, false);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
onRowActionMenuToggle(event) {
|
|
275
|
+
const isOpen = event.detail.newState;
|
|
276
|
+
if (isOpen) {
|
|
277
|
+
const row = event.target;
|
|
278
|
+
const columnIndex = this.table.visibleColumns.findIndex(column => column.columnId === event.detail.columnId);
|
|
279
|
+
this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
onEnterPressed(ctrlKey) {
|
|
283
|
+
let row;
|
|
284
|
+
let rowElements;
|
|
285
|
+
if (this.hasRowOrCellFocusType()) {
|
|
286
|
+
row = this.getCurrentRow();
|
|
287
|
+
rowElements = row?.getFocusableElements();
|
|
288
|
+
}
|
|
289
|
+
if (this.focusType === TableFocusType.row) {
|
|
290
|
+
if (row instanceof TableGroupRow) {
|
|
291
|
+
this.toggleRowExpanded(row);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (this.focusType === TableFocusType.cell) {
|
|
296
|
+
if (ctrlKey) {
|
|
297
|
+
const cell = rowElements?.cells[this.columnIndex];
|
|
298
|
+
if (cell?.actionMenuButton && !cell.actionMenuButton.open) {
|
|
299
|
+
cell.actionMenuButton.toggleButton.control.click();
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return this.focusFirstInteractiveElementInCurrentCell(rowElements);
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
onF2Pressed() {
|
|
308
|
+
if (this.focusType === TableFocusType.cell) {
|
|
309
|
+
const row = this.getCurrentRow();
|
|
310
|
+
const rowElements = row?.getFocusableElements();
|
|
311
|
+
return this.focusFirstInteractiveElementInCurrentCell(rowElements);
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
onSpacePressed(shiftKey) {
|
|
316
|
+
if (this.focusType === TableFocusType.row
|
|
317
|
+
|| this.focusType === TableFocusType.cell) {
|
|
318
|
+
if (this.focusType === TableFocusType.row || shiftKey) {
|
|
319
|
+
const row = this.getCurrentRow();
|
|
320
|
+
if (row instanceof TableRow && row.selectable) {
|
|
321
|
+
row.onSelectionChange(row.selected, !row.selected);
|
|
322
|
+
}
|
|
323
|
+
else if (row instanceof TableGroupRow) {
|
|
324
|
+
this.toggleRowExpanded(row);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Default Space behavior scrolls down, which is redundant given the rest of our keyboard nav code, and we'd still try to focus a
|
|
328
|
+
// row that you scrolled away from. So suppress default Space behavior if a row or cell is selected, regardless of if we're
|
|
329
|
+
// toggling selection or not.
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
onLeftArrowPressed() {
|
|
335
|
+
let row;
|
|
336
|
+
let rowElements;
|
|
337
|
+
let headerElements;
|
|
338
|
+
if (this.hasRowOrCellFocusType()) {
|
|
339
|
+
row = this.getCurrentRow();
|
|
340
|
+
rowElements = row?.getFocusableElements();
|
|
341
|
+
}
|
|
342
|
+
else if (this.hasHeaderFocusType()) {
|
|
343
|
+
headerElements = this.getTableHeaderFocusableElements();
|
|
344
|
+
}
|
|
345
|
+
switch (this.focusType) {
|
|
346
|
+
case TableFocusType.headerActions:
|
|
347
|
+
return this.trySetHeaderActionFocus(headerElements, this.headerActionIndex - 1);
|
|
348
|
+
case TableFocusType.columnHeader:
|
|
349
|
+
return (this.trySetColumnHeaderFocus(headerElements, this.columnIndex - 1)
|
|
350
|
+
|| this.trySetHeaderActionFocus(headerElements, headerElements.headerActions.length - 1));
|
|
351
|
+
case TableFocusType.row:
|
|
352
|
+
if (this.isRowExpanded(row) === true) {
|
|
353
|
+
this.toggleRowExpanded(row);
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
358
|
+
this.setRowFocusState();
|
|
359
|
+
return this.focusCurrentRow(true);
|
|
360
|
+
case TableFocusType.cell:
|
|
361
|
+
if (!this.trySetCellFocus(rowElements, this.columnIndex - 1)
|
|
362
|
+
&& !this.trySetRowSelectionCheckboxFocus(rowElements)) {
|
|
363
|
+
this.setRowFocusState();
|
|
364
|
+
this.focusCurrentRow(true);
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
default:
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
onRightArrowPressed() {
|
|
373
|
+
let row;
|
|
374
|
+
let rowElements;
|
|
375
|
+
let headerElements;
|
|
376
|
+
if (this.hasRowOrCellFocusType()) {
|
|
377
|
+
row = this.getCurrentRow();
|
|
378
|
+
rowElements = row?.getFocusableElements();
|
|
379
|
+
}
|
|
380
|
+
else if (this.hasHeaderFocusType()) {
|
|
381
|
+
headerElements = this.getTableHeaderFocusableElements();
|
|
382
|
+
}
|
|
383
|
+
switch (this.focusType) {
|
|
384
|
+
case TableFocusType.headerActions:
|
|
385
|
+
return (this.trySetHeaderActionFocus(headerElements, this.headerActionIndex + 1) || this.trySetColumnHeaderFocus(headerElements, 0));
|
|
386
|
+
case TableFocusType.columnHeader:
|
|
387
|
+
return this.trySetColumnHeaderFocus(headerElements, this.columnIndex + 1);
|
|
388
|
+
case TableFocusType.row:
|
|
389
|
+
if (this.isRowExpanded(row) === false) {
|
|
390
|
+
this.toggleRowExpanded(row);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
return (this.trySetRowSelectionCheckboxFocus(rowElements)
|
|
394
|
+
|| this.trySetCellFocus(rowElements, 0));
|
|
395
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
396
|
+
return this.trySetCellFocus(rowElements, 0);
|
|
397
|
+
case TableFocusType.cell:
|
|
398
|
+
return this.trySetCellFocus(rowElements, this.columnIndex + 1);
|
|
399
|
+
default:
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
onUpArrowPressed() {
|
|
405
|
+
this.onMoveUp(1);
|
|
406
|
+
// Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
onPageUpPressed() {
|
|
410
|
+
return this.onMoveUp(this.virtualizer.pageSize);
|
|
411
|
+
}
|
|
412
|
+
onHomePressed(ctrlKey) {
|
|
413
|
+
if (this.handleHomeEndWithinRow(ctrlKey)) {
|
|
414
|
+
const row = this.getCurrentRow();
|
|
415
|
+
const rowElements = row?.getFocusableElements();
|
|
416
|
+
return (this.trySetRowSelectionCheckboxFocus(rowElements)
|
|
417
|
+
|| this.trySetCellFocus(rowElements, 0));
|
|
418
|
+
}
|
|
419
|
+
return this.onMoveUp(0, 0);
|
|
420
|
+
}
|
|
421
|
+
onDownArrowPressed() {
|
|
422
|
+
this.onMoveDown(1);
|
|
423
|
+
// Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
onPageDownPressed() {
|
|
427
|
+
return this.onMoveDown(this.virtualizer.pageSize);
|
|
428
|
+
}
|
|
429
|
+
onEndPressed(ctrlKey) {
|
|
430
|
+
if (this.handleHomeEndWithinRow(ctrlKey)) {
|
|
431
|
+
const row = this.getCurrentRow();
|
|
432
|
+
const rowElements = row?.getFocusableElements();
|
|
433
|
+
return this.trySetCellFocus(rowElements, this.table.visibleColumns.length - 1);
|
|
434
|
+
}
|
|
435
|
+
return this.onMoveDown(0, this.table.tableData.length - 1);
|
|
436
|
+
}
|
|
437
|
+
handleHomeEndWithinRow(ctrlKey) {
|
|
438
|
+
return ((this.focusType === TableFocusType.cell
|
|
439
|
+
|| this.focusType === TableFocusType.rowSelectionCheckbox)
|
|
440
|
+
&& !ctrlKey);
|
|
441
|
+
}
|
|
442
|
+
onTabPressed(shiftKeyPressed) {
|
|
443
|
+
const activeElement = this.getActiveElement();
|
|
444
|
+
if (activeElement === null || activeElement === this.table) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
const nextFocusState = this.hasRowOrCellFocusType()
|
|
448
|
+
? this.getNextRowTabStop(shiftKeyPressed)
|
|
449
|
+
: this.getNextHeaderTabStop(shiftKeyPressed);
|
|
450
|
+
if (nextFocusState) {
|
|
451
|
+
this.focusType = nextFocusState.focusType;
|
|
452
|
+
this.rowIndex = nextFocusState.rowIndex ?? this.rowIndex;
|
|
453
|
+
this.columnIndex = nextFocusState.columnIndex ?? this.columnIndex;
|
|
454
|
+
this.headerActionIndex = nextFocusState.headerActionIndex ?? this.headerActionIndex;
|
|
455
|
+
this.cellContentIndex = nextFocusState.cellContentIndex ?? this.cellContentIndex;
|
|
456
|
+
if (this.hasRowOrCellFocusType()) {
|
|
457
|
+
this.focusCurrentRow(false);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
this.focusHeaderElement();
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
this.blurAfterLastTab(activeElement);
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
getNextRowTabStop(shiftKeyPressed) {
|
|
468
|
+
const row = this.getCurrentRow();
|
|
469
|
+
if (row === undefined) {
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
let startIndex = -1;
|
|
473
|
+
const focusStates = [];
|
|
474
|
+
const rowElements = row.getFocusableElements();
|
|
475
|
+
if (rowElements.selectionCheckbox) {
|
|
476
|
+
focusStates.push({
|
|
477
|
+
focusType: TableFocusType.rowSelectionCheckbox
|
|
478
|
+
});
|
|
479
|
+
if (this.focusType === TableFocusType.rowSelectionCheckbox) {
|
|
480
|
+
startIndex = 0;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
let cellIndex = 0;
|
|
484
|
+
while (cellIndex < rowElements.cells.length) {
|
|
485
|
+
const firstCellTabbableIndex = focusStates.length;
|
|
486
|
+
const cellInfo = rowElements.cells[cellIndex];
|
|
487
|
+
const cellViewTabbableChildren = cellInfo.cell.cellView.tabbableChildren;
|
|
488
|
+
for (let i = 0; i < cellViewTabbableChildren.length; i++) {
|
|
489
|
+
focusStates.push({
|
|
490
|
+
focusType: TableFocusType.cellContent,
|
|
491
|
+
columnIndex: cellIndex,
|
|
492
|
+
cellContentIndex: i
|
|
493
|
+
});
|
|
494
|
+
if (this.focusType === TableFocusType.cellContent
|
|
495
|
+
&& this.columnIndex === cellIndex
|
|
496
|
+
&& this.cellContentIndex === i) {
|
|
497
|
+
startIndex = focusStates.length - 1;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (cellInfo.actionMenuButton) {
|
|
501
|
+
focusStates.push({
|
|
502
|
+
focusType: TableFocusType.cellActionMenu,
|
|
503
|
+
columnIndex: cellIndex
|
|
504
|
+
});
|
|
505
|
+
if (this.focusType === TableFocusType.cellActionMenu
|
|
506
|
+
&& this.columnIndex === cellIndex) {
|
|
507
|
+
startIndex = focusStates.length - 1;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const lastCellTabbableIndex = focusStates.length - 1;
|
|
511
|
+
if (this.focusType === TableFocusType.cell
|
|
512
|
+
&& this.columnIndex === cellIndex) {
|
|
513
|
+
startIndex = shiftKeyPressed
|
|
514
|
+
? lastCellTabbableIndex + 1
|
|
515
|
+
: firstCellTabbableIndex - 1;
|
|
516
|
+
}
|
|
517
|
+
cellIndex += 1;
|
|
518
|
+
}
|
|
519
|
+
if (this.focusType === TableFocusType.row) {
|
|
520
|
+
startIndex = shiftKeyPressed ? focusStates.length : -1;
|
|
521
|
+
}
|
|
522
|
+
const direction = shiftKeyPressed ? -1 : 1;
|
|
523
|
+
return focusStates[startIndex + direction];
|
|
524
|
+
}
|
|
525
|
+
getNextHeaderTabStop(shiftKeyPressed) {
|
|
526
|
+
let startIndex = -1;
|
|
527
|
+
const focusStates = [];
|
|
528
|
+
const headerTabbableElements = this.getTableHeaderFocusableElements().headerActions;
|
|
529
|
+
for (let i = 0; i < headerTabbableElements.length; i++) {
|
|
530
|
+
focusStates.push({
|
|
531
|
+
focusType: TableFocusType.headerActions,
|
|
532
|
+
headerActionIndex: i
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (this.focusType === TableFocusType.headerActions) {
|
|
536
|
+
startIndex = this.headerActionIndex;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
// TableFocusType.columnHeader
|
|
540
|
+
startIndex = focusStates.length;
|
|
541
|
+
}
|
|
542
|
+
const direction = shiftKeyPressed ? -1 : 1;
|
|
543
|
+
return focusStates[startIndex + direction];
|
|
544
|
+
}
|
|
545
|
+
blurAfterLastTab(activeElement) {
|
|
546
|
+
// In order to get the desired browser-provided Tab/Shift-Tab behavior of focusing the
|
|
547
|
+
// element before/after the table, the table shouldn't have tabIndex=0 when this event
|
|
548
|
+
// handling ends. However it needs to be tabIndex=0 so we can re-focus the table the next time
|
|
549
|
+
// it's tabbed to, so set tabIndex back to 0 after a rAF.
|
|
550
|
+
// Note: In Chrome this is only needed for Shift-Tab, but in Firefox both Tab and Shift-Tab need this
|
|
551
|
+
// to work as expected.
|
|
552
|
+
this.table.tabIndex = -1;
|
|
553
|
+
window.requestAnimationFrame(() => {
|
|
554
|
+
this.table.tabIndex = 0;
|
|
555
|
+
});
|
|
556
|
+
// Don't explicitly call blur() on activeElement (causes unexpected behavior on Safari / Mac Firefox)
|
|
557
|
+
this.setElementFocusable(activeElement, false);
|
|
558
|
+
}
|
|
559
|
+
onMoveUp(rowDelta, newRowIndex) {
|
|
560
|
+
const coerceRowIndex = rowDelta > 1;
|
|
561
|
+
switch (this.focusType) {
|
|
562
|
+
case TableFocusType.row:
|
|
563
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
564
|
+
case TableFocusType.cell: {
|
|
565
|
+
const scrollOptions = {};
|
|
566
|
+
let rowIndex = this.rowIndex;
|
|
567
|
+
if (newRowIndex !== undefined) {
|
|
568
|
+
rowIndex = newRowIndex;
|
|
569
|
+
}
|
|
570
|
+
rowIndex -= rowDelta;
|
|
571
|
+
if (coerceRowIndex && rowIndex < 0) {
|
|
572
|
+
rowIndex = 0;
|
|
573
|
+
}
|
|
574
|
+
if (rowDelta > 1) {
|
|
575
|
+
scrollOptions.align = 'start';
|
|
576
|
+
}
|
|
577
|
+
if (rowIndex < this.rowIndex && rowIndex >= 0) {
|
|
578
|
+
return this.scrollToAndFocusRow(rowIndex, scrollOptions);
|
|
579
|
+
}
|
|
580
|
+
if (rowIndex === -1) {
|
|
581
|
+
const headerElements = this.getTableHeaderFocusableElements();
|
|
582
|
+
if (this.focusType === TableFocusType.row
|
|
583
|
+
|| this.focusType === TableFocusType.rowSelectionCheckbox) {
|
|
584
|
+
return (this.trySetHeaderActionFocus(headerElements, 0)
|
|
585
|
+
|| this.trySetColumnHeaderFocus(headerElements, 0));
|
|
586
|
+
}
|
|
587
|
+
return this.trySetColumnHeaderFocus(headerElements, this.columnIndex);
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
default:
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
onMoveDown(rowDelta, newRowIndex) {
|
|
597
|
+
const coerceRowIndex = rowDelta > 1;
|
|
598
|
+
switch (this.focusType) {
|
|
599
|
+
case TableFocusType.headerActions: {
|
|
600
|
+
this.setRowFocusState(0);
|
|
601
|
+
return this.scrollToAndFocusRow(0);
|
|
602
|
+
}
|
|
603
|
+
case TableFocusType.columnHeader: {
|
|
604
|
+
this.setCellFocusState(this.columnIndex, 0, false);
|
|
605
|
+
return this.scrollToAndFocusRow(0);
|
|
606
|
+
}
|
|
607
|
+
case TableFocusType.row:
|
|
608
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
609
|
+
case TableFocusType.cell: {
|
|
610
|
+
const scrollOptions = {};
|
|
611
|
+
let rowIndex = this.rowIndex;
|
|
612
|
+
if (newRowIndex !== undefined) {
|
|
613
|
+
rowIndex = newRowIndex;
|
|
614
|
+
}
|
|
615
|
+
rowIndex += rowDelta;
|
|
616
|
+
if (coerceRowIndex && rowIndex >= this.table.tableData.length) {
|
|
617
|
+
rowIndex = this.table.tableData.length - 1;
|
|
618
|
+
}
|
|
619
|
+
if (rowDelta > 1) {
|
|
620
|
+
scrollOptions.align = 'end';
|
|
621
|
+
}
|
|
622
|
+
if (rowIndex > this.rowIndex
|
|
623
|
+
&& rowIndex < this.table.tableData.length) {
|
|
624
|
+
return this.scrollToAndFocusRow(rowIndex, scrollOptions);
|
|
625
|
+
}
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
default:
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
updateFocusStateFromActiveElement(setRowFocus) {
|
|
634
|
+
// If the user is interacting with the table with non-keyboard methods (like mouse), we need to
|
|
635
|
+
// update our focus state based on the current active/focused element
|
|
636
|
+
const activeElement = this.getActiveElement();
|
|
637
|
+
if (activeElement) {
|
|
638
|
+
const row = this.getContainingRow(activeElement);
|
|
639
|
+
if (row) {
|
|
640
|
+
if (!(row instanceof TableGroupRow)) {
|
|
641
|
+
const cell = this.getContainingCell(activeElement);
|
|
642
|
+
if (cell) {
|
|
643
|
+
const columnIndex = this.table.visibleColumns.indexOf(cell.column);
|
|
644
|
+
if (cell.actionMenuButton === activeElement) {
|
|
645
|
+
this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const contentIndex = cell.cellView.tabbableChildren.indexOf(activeElement);
|
|
649
|
+
if (contentIndex > -1) {
|
|
650
|
+
this.setCellContentFocusState(contentIndex, row.resolvedRowIndex, columnIndex, false);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (setRowFocus
|
|
656
|
+
&& this.hasRowOrCellFocusType()
|
|
657
|
+
&& this.rowIndex !== row.resolvedRowIndex) {
|
|
658
|
+
this.setRowFocusState(row.resolvedRowIndex);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
focusElement(element, focusOptions) {
|
|
664
|
+
const previousActiveElement = this.getActiveElement();
|
|
665
|
+
if (previousActiveElement !== element) {
|
|
666
|
+
this.setElementFocusable(element, true);
|
|
667
|
+
this.isCurrentlyFocusingElement = true;
|
|
668
|
+
element.focus(focusOptions);
|
|
669
|
+
this.isCurrentlyFocusingElement = false;
|
|
670
|
+
if (previousActiveElement
|
|
671
|
+
&& this.isInTable(previousActiveElement)) {
|
|
672
|
+
this.setElementFocusable(previousActiveElement, false);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
setElementFocusable(element, focusable) {
|
|
677
|
+
if (element === this.table) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
element.tabIndex = focusable ? 0 : -1;
|
|
681
|
+
}
|
|
682
|
+
setActionMenuButtonFocused(menuButton, focused) {
|
|
683
|
+
// The action MenuButton needs to be visible in order to be focused, so this CSS class styling
|
|
684
|
+
// handles that (see cell/styles.ts).
|
|
685
|
+
if (focused) {
|
|
686
|
+
menuButton.classList.add('cell-action-menu-focused');
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
menuButton.classList.remove('cell-action-menu-focused');
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
setFocusOnHeader() {
|
|
693
|
+
if (this.hasHeaderFocusType()) {
|
|
694
|
+
return this.focusHeaderElement();
|
|
695
|
+
}
|
|
696
|
+
this.setDefaultFocus();
|
|
697
|
+
return this.focusType !== TableFocusType.none;
|
|
698
|
+
}
|
|
699
|
+
setDefaultFocus() {
|
|
700
|
+
const headerElements = this.getTableHeaderFocusableElements();
|
|
701
|
+
if (!this.trySetHeaderActionFocus(headerElements, 0)
|
|
702
|
+
&& !this.trySetColumnHeaderFocus(headerElements, 0)
|
|
703
|
+
&& !this.scrollToAndFocusRow(0)) {
|
|
704
|
+
this.focusType = TableFocusType.none;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
scrollToAndFocusRow(totalRowIndex, scrollOptions) {
|
|
708
|
+
if (totalRowIndex >= 0 && totalRowIndex < this.table.tableData.length) {
|
|
709
|
+
switch (this.focusType) {
|
|
710
|
+
case TableFocusType.none:
|
|
711
|
+
case TableFocusType.headerActions:
|
|
712
|
+
case TableFocusType.columnHeader:
|
|
713
|
+
this.setRowFocusState(totalRowIndex);
|
|
714
|
+
break;
|
|
715
|
+
default:
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
this.rowIndex = totalRowIndex;
|
|
719
|
+
this.virtualizer.scrollToIndex(totalRowIndex, scrollOptions);
|
|
720
|
+
this.focusCurrentRow(true);
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
focusCurrentRow(allowScroll) {
|
|
726
|
+
const visibleRowIndex = this.getCurrentRowVisibleIndex();
|
|
727
|
+
if (visibleRowIndex < 0) {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
const focusedRow = this.table.rowElements[visibleRowIndex];
|
|
731
|
+
let focusRowOnly = false;
|
|
732
|
+
switch (this.focusType) {
|
|
733
|
+
case TableFocusType.row:
|
|
734
|
+
focusRowOnly = true;
|
|
735
|
+
break;
|
|
736
|
+
case TableFocusType.cell:
|
|
737
|
+
case TableFocusType.cellActionMenu:
|
|
738
|
+
case TableFocusType.cellContent:
|
|
739
|
+
focusRowOnly = focusedRow instanceof TableGroupRow;
|
|
740
|
+
break;
|
|
741
|
+
default:
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
const focusOptions = { preventScroll: !allowScroll };
|
|
745
|
+
if (focusRowOnly) {
|
|
746
|
+
this.focusElement(focusedRow, focusOptions);
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
this.focusRowElement(focusedRow, focusOptions);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
focusRowElement(row, focusOptions) {
|
|
753
|
+
const rowElements = row.getFocusableElements();
|
|
754
|
+
let focusableElement;
|
|
755
|
+
switch (this.focusType) {
|
|
756
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
757
|
+
focusableElement = rowElements.selectionCheckbox;
|
|
758
|
+
break;
|
|
759
|
+
case TableFocusType.cell: {
|
|
760
|
+
focusableElement = rowElements.cells[this.columnIndex].cell;
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
case TableFocusType.cellActionMenu: {
|
|
764
|
+
const actionMenuButton = rowElements.cells[this.columnIndex]?.cell.actionMenuButton;
|
|
765
|
+
if (actionMenuButton) {
|
|
766
|
+
focusableElement = actionMenuButton;
|
|
767
|
+
this.setActionMenuButtonFocused(actionMenuButton, true);
|
|
768
|
+
}
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
case TableFocusType.cellContent: {
|
|
772
|
+
focusableElement = rowElements.cells[this.columnIndex]?.cell.cellView
|
|
773
|
+
.tabbableChildren[this.cellContentIndex];
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
default:
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
if (focusableElement) {
|
|
780
|
+
this.focusElement(focusableElement, focusOptions);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
focusHeaderElement() {
|
|
784
|
+
const headerElements = this.getTableHeaderFocusableElements();
|
|
785
|
+
let focusableElement;
|
|
786
|
+
switch (this.focusType) {
|
|
787
|
+
case TableFocusType.headerActions:
|
|
788
|
+
focusableElement = headerElements.headerActions[this.headerActionIndex];
|
|
789
|
+
break;
|
|
790
|
+
case TableFocusType.columnHeader:
|
|
791
|
+
focusableElement = headerElements.columnHeaders[this.columnIndex];
|
|
792
|
+
break;
|
|
793
|
+
default:
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
if (focusableElement) {
|
|
797
|
+
this.focusElement(focusableElement);
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
getCurrentRowVisibleIndex() {
|
|
803
|
+
return this.table.rowElements.findIndex(row => row.resolvedRowIndex === this.rowIndex);
|
|
804
|
+
}
|
|
805
|
+
getTableHeaderFocusableElements() {
|
|
806
|
+
const headerActions = [];
|
|
807
|
+
if (this.table.selectionCheckbox?.getRootNode()
|
|
808
|
+
=== this.table.shadowRoot) {
|
|
809
|
+
headerActions.push(this.table.selectionCheckbox);
|
|
810
|
+
}
|
|
811
|
+
if (this.table.showCollapseAll
|
|
812
|
+
&& this.table.collapseAllButton?.getRootNode()
|
|
813
|
+
=== this.table.shadowRoot) {
|
|
814
|
+
headerActions.push(this.table.collapseAllButton);
|
|
815
|
+
}
|
|
816
|
+
const columnHeaders = [];
|
|
817
|
+
if (this.canFocusColumnHeaders()) {
|
|
818
|
+
this.table.columnHeadersContainer
|
|
819
|
+
.querySelectorAll(tableHeaderTag)
|
|
820
|
+
.forEach(header => columnHeaders.push(header));
|
|
821
|
+
}
|
|
822
|
+
return { headerActions, columnHeaders };
|
|
823
|
+
}
|
|
824
|
+
canFocusColumnHeaders() {
|
|
825
|
+
return (this.table.columns.find(c => !c.columnInternals.sortingDisabled)
|
|
826
|
+
!== undefined);
|
|
827
|
+
}
|
|
828
|
+
getCurrentRow() {
|
|
829
|
+
return this.table.rowElements[this.getCurrentRowVisibleIndex()];
|
|
830
|
+
}
|
|
831
|
+
isRowExpanded(row) {
|
|
832
|
+
if ((row instanceof TableRow && row.isParentRow)
|
|
833
|
+
|| row instanceof TableGroupRow) {
|
|
834
|
+
return row.expanded;
|
|
835
|
+
}
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
toggleRowExpanded(row) {
|
|
839
|
+
if (row instanceof TableGroupRow) {
|
|
840
|
+
row.onGroupExpandToggle();
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
row.onRowExpandToggle();
|
|
844
|
+
}
|
|
845
|
+
this.focusRowElement(row);
|
|
846
|
+
}
|
|
847
|
+
getContainingRow(start) {
|
|
848
|
+
return this.getContainingElement(start, e => this.isResolvedRowType(e));
|
|
849
|
+
}
|
|
850
|
+
getContainingCell(start) {
|
|
851
|
+
return this.getContainingElement(start, e => e instanceof TableCell);
|
|
852
|
+
}
|
|
853
|
+
getContainingElement(start, isElementMatch) {
|
|
854
|
+
let possibleMatch = start;
|
|
855
|
+
while (possibleMatch && possibleMatch !== this.table) {
|
|
856
|
+
if (isElementMatch(possibleMatch)) {
|
|
857
|
+
return possibleMatch;
|
|
858
|
+
}
|
|
859
|
+
possibleMatch = possibleMatch.parentElement
|
|
860
|
+
?? possibleMatch.parentNode?.host;
|
|
861
|
+
}
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
864
|
+
isInTable(start) {
|
|
865
|
+
let possibleMatch = start;
|
|
866
|
+
while (possibleMatch && possibleMatch !== this.table) {
|
|
867
|
+
possibleMatch = possibleMatch.parentElement
|
|
868
|
+
?? possibleMatch.parentNode?.host;
|
|
869
|
+
}
|
|
870
|
+
return possibleMatch === this.table;
|
|
871
|
+
}
|
|
872
|
+
getActiveElement() {
|
|
873
|
+
let activeElement = document.activeElement;
|
|
874
|
+
while (activeElement?.shadowRoot?.activeElement) {
|
|
875
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
876
|
+
// In some cases, the active element may be a sub-part of a control (example: MenuButton -> ToggleButton -> a div with tabindex=0). Stop at the outer control boundary, so that
|
|
877
|
+
// we can more simply check equality against the elements of getTableHeaderFocusableElements() / row.getFocusableElements().
|
|
878
|
+
// (For rows/cells/cell views, we do need to recurse into them, to get to the appropriate focused controls though)
|
|
879
|
+
if (activeElement instanceof FoundationElement
|
|
880
|
+
&& !this.isResolvedRowType(activeElement)
|
|
881
|
+
&& !(activeElement instanceof TableCell)
|
|
882
|
+
&& !(activeElement instanceof TableCellView)) {
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return activeElement;
|
|
887
|
+
}
|
|
888
|
+
focusFirstInteractiveElementInCurrentCell(rowElements) {
|
|
889
|
+
if (!rowElements) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
return (this.trySetCellContentFocus(rowElements, 0)
|
|
893
|
+
|| this.trySetCellActionMenuFocus(rowElements));
|
|
894
|
+
}
|
|
895
|
+
hasRowOrCellFocusType() {
|
|
896
|
+
switch (this.focusType) {
|
|
897
|
+
case TableFocusType.cell:
|
|
898
|
+
case TableFocusType.cellActionMenu:
|
|
899
|
+
case TableFocusType.cellContent:
|
|
900
|
+
case TableFocusType.row:
|
|
901
|
+
case TableFocusType.rowSelectionCheckbox:
|
|
902
|
+
return true;
|
|
903
|
+
default:
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
hasHeaderFocusType() {
|
|
908
|
+
switch (this.focusType) {
|
|
909
|
+
case TableFocusType.headerActions:
|
|
910
|
+
case TableFocusType.columnHeader:
|
|
911
|
+
return true;
|
|
912
|
+
default:
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
trySetRowSelectionCheckboxFocus(rowElements) {
|
|
917
|
+
if (rowElements?.selectionCheckbox) {
|
|
918
|
+
this.focusType = TableFocusType.rowSelectionCheckbox;
|
|
919
|
+
this.focusCurrentRow(true);
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
trySetColumnHeaderFocus(headerElements, columnIndex) {
|
|
925
|
+
if (columnIndex >= 0
|
|
926
|
+
&& columnIndex < headerElements.columnHeaders.length) {
|
|
927
|
+
this.focusType = TableFocusType.columnHeader;
|
|
928
|
+
this.columnIndex = columnIndex;
|
|
929
|
+
this.focusHeaderElement();
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
trySetHeaderActionFocus(headerElements, headerActionIndex) {
|
|
935
|
+
if (headerActionIndex >= 0
|
|
936
|
+
&& headerActionIndex < headerElements.headerActions.length) {
|
|
937
|
+
this.focusType = TableFocusType.headerActions;
|
|
938
|
+
this.headerActionIndex = headerActionIndex;
|
|
939
|
+
this.focusHeaderElement();
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
trySetCellFocus(rowElements, columnIndex, rowIndex) {
|
|
945
|
+
if (!rowElements) {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
const newColumnIndex = columnIndex ?? this.columnIndex;
|
|
949
|
+
const newRowIndex = rowIndex ?? this.rowIndex;
|
|
950
|
+
if (newColumnIndex >= 0 && newColumnIndex < rowElements.cells.length) {
|
|
951
|
+
this.focusType = TableFocusType.cell;
|
|
952
|
+
this.setRowCellFocusState(newColumnIndex, newRowIndex, true);
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
trySetCellContentFocus(rowElements, cellContentIndex, columnIndex, rowIndex) {
|
|
958
|
+
if (!rowElements) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
const newColumnIndex = columnIndex ?? this.columnIndex;
|
|
962
|
+
const newRowIndex = rowIndex ?? this.rowIndex;
|
|
963
|
+
if (newColumnIndex >= 0
|
|
964
|
+
&& newColumnIndex < rowElements.cells.length
|
|
965
|
+
&& cellContentIndex >= 0
|
|
966
|
+
&& cellContentIndex
|
|
967
|
+
< rowElements.cells[newColumnIndex].cell.cellView
|
|
968
|
+
.tabbableChildren.length) {
|
|
969
|
+
this.setCellContentFocusState(cellContentIndex, newRowIndex, newColumnIndex, true);
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
trySetCellActionMenuFocus(rowElements, columnIndex, rowIndex) {
|
|
975
|
+
const newColumnIndex = columnIndex ?? this.columnIndex;
|
|
976
|
+
const newRowIndex = rowIndex ?? this.rowIndex;
|
|
977
|
+
if (newColumnIndex >= 0
|
|
978
|
+
&& newColumnIndex < rowElements.cells.length
|
|
979
|
+
&& rowElements.cells[newColumnIndex].actionMenuButton) {
|
|
980
|
+
this.setCellActionMenuFocusState(newRowIndex, newColumnIndex, true);
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
setCellActionMenuFocusState(rowIndex, columnIndex, focusElement) {
|
|
986
|
+
this.focusType = TableFocusType.cellActionMenu;
|
|
987
|
+
this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
|
|
988
|
+
}
|
|
989
|
+
setCellContentFocusState(cellContentIndex, rowIndex, columnIndex, focusElement) {
|
|
990
|
+
this.focusType = TableFocusType.cellContent;
|
|
991
|
+
this.cellContentIndex = cellContentIndex;
|
|
992
|
+
this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
|
|
993
|
+
}
|
|
994
|
+
setRowFocusState(rowIndex) {
|
|
995
|
+
this.focusType = TableFocusType.row;
|
|
996
|
+
if (rowIndex !== undefined) {
|
|
997
|
+
this.rowIndex = rowIndex;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
setCellFocusState(columnIndex, rowIndex, focusElement) {
|
|
1001
|
+
this.focusType = TableFocusType.cell;
|
|
1002
|
+
this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
|
|
1003
|
+
}
|
|
1004
|
+
setRowCellFocusState(columnIndex, rowIndex, focusElement) {
|
|
1005
|
+
this.rowIndex = rowIndex;
|
|
1006
|
+
this.columnIndex = columnIndex;
|
|
1007
|
+
if (focusElement) {
|
|
1008
|
+
this.focusCurrentRow(true);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
isResolvedRowType(row) {
|
|
1012
|
+
return row instanceof TableRow || row instanceof TableGroupRow;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
//# sourceMappingURL=keyboard-navigation-manager.js.map
|