Skip to content

Commit 9eb6c2a

Browse files
authored
Merge branch 'master' into pagination
2 parents 9ea626a + 8d1631f commit 9eb6c2a

File tree

8 files changed

+357
-35
lines changed

8 files changed

+357
-35
lines changed

src/common/tab.service.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ export let tabbableSelector = "a[href], area[href], input:not([disabled]):not([t
33
"textarea:not([disabled]):not([tabindex=\'-1\']), " +
44
"iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]";
55

6-
export function getFocusElementList(element) {
7-
let elements = element.querySelectorAll(tabbableSelector);
6+
export let tabbableSelectorIgnoreTabIndex = "a[href], area[href], input:not([disabled]), " +
7+
"button:not([disabled]),select:not([disabled]), " +
8+
"textarea:not([disabled]), " +
9+
"iframe, object, embed, *[tabindex], *[contenteditable=true]";
10+
11+
export function getFocusElementList(element, selector = tabbableSelector) {
12+
let elements = element.querySelectorAll(selector);
813
return elements ? Array.prototype.filter.call(elements, el => isVisible(el)) : elements;
914
}
1015

src/dropdown/dropdown.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export class Dropdown implements OnInit, AfterContentInit, OnDestroy {
106106
* Set to `true` for an inline dropdown.
107107
*/
108108
@Input() inline = false;
109+
/**
110+
* Set to `true` for a dropdown without arrow key activation.
111+
*/
112+
@Input() disableArrowKeys = false;
109113
/**
110114
* Deprecated. Dropdown now defaults to appending inline
111115
* Set to `true` if the `Dropdown` is to be appended to the DOM body.
@@ -302,6 +306,9 @@ export class Dropdown implements OnInit, AfterContentInit, OnDestroy {
302306
this.dropdownButton.nativeElement.focus();
303307
} else if (this.menuIsClosed && (event.key === " " || event.key === "ArrowDown" || event.key === "ArrowUp" ||
304308
event.key === "Spacebar" || event.key === "Down" || event.key === "Up")) {
309+
if (this.disableArrowKeys && (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Down" || event.key === "Up")) {
310+
return;
311+
}
305312
event.preventDefault();
306313
this.openMenu();
307314
}

src/slider/slider.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
149149
@Input() shiftMultiplier = 4;
150150
/** Set to `true` for a loading slider */
151151
@Input() skeleton = false;
152+
/** Set to `true` for a slider without arrow key interactions. */
153+
@Input() disableArrowKeys = false;
152154
/** Disables the range visually and functionally */
153155
@Input() set disabled(v) {
154156
this._disabled = v;
@@ -338,6 +340,9 @@ export class Slider implements AfterViewInit, OnDestroy, ControlValueAccessor {
338340

339341
/** Calls `incrementValue` for ArrowRight and ArrowUp, `decrementValue` for ArrowLeft and ArrowDown */
340342
onKeyDown(event: KeyboardEvent) {
343+
if (this.disableArrowKeys) {
344+
return;
345+
}
341346
event.preventDefault();
342347
const multiplier = event.shiftKey ? this.shiftMultiplier : 1;
343348
if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import {
2+
Directive,
3+
Input,
4+
ElementRef,
5+
HostListener,
6+
Output,
7+
EventEmitter
8+
} from "@angular/core";
9+
import { Table } from "./table.component";
10+
import { getFocusElementList, tabbableSelectorIgnoreTabIndex } from "../common/tab.service";
11+
12+
@Directive({
13+
selector: "[ibmDataGridFocus]"
14+
})
15+
export class DataGridFocus {
16+
@Input() ibmDataGridFocus: boolean;
17+
@Input() set columnIndex(value: number) {
18+
const shouldEmit = value !== this._columnIndex;
19+
this._columnIndex = value;
20+
if (shouldEmit) {
21+
this.columnIndexChange.emit(value);
22+
}
23+
}
24+
get columnIndex(): number {
25+
return this._columnIndex;
26+
}
27+
28+
@Output() columnIndexChange: EventEmitter<number> = new EventEmitter();
29+
30+
protected _columnIndex: number;
31+
32+
constructor(protected elementRef: ElementRef) {}
33+
34+
focus(element) {
35+
const focusElementList = getFocusElementList(element, tabbableSelectorIgnoreTabIndex);
36+
if (element.firstElementChild && element.firstElementChild.classList.contains("bx--table-sort-v2")) {
37+
focusElementList[1].focus();
38+
} else if (focusElementList.length > 0) {
39+
focusElementList[0].focus();
40+
} else {
41+
element.focus();
42+
}
43+
}
44+
45+
@HostListener("keyup", ["$event"])
46+
keyUp(event: KeyboardEvent) {
47+
if (!this.ibmDataGridFocus) {
48+
return;
49+
}
50+
const element = this.elementRef.nativeElement;
51+
const rows = element.closest("table").rows;
52+
const closestTr = element.closest("tr");
53+
let rowIndex = Array.from(rows).indexOf(closestTr);
54+
55+
const headerRow = rows[0].querySelectorAll("th");
56+
57+
switch (event.key) {
58+
case "Right": // IE specific value
59+
case "ArrowRight":
60+
if (element.nextElementSibling && Array.from(headerRow).indexOf(element.nextElementSibling) < headerRow.length - 1) {
61+
this.columnIndex++;
62+
const nextSibling = element.nextElementSibling;
63+
Table.setTabIndex(element, -1);
64+
Table.setTabIndex(nextSibling, 0);
65+
this.focus(nextSibling);
66+
}
67+
break;
68+
case "Left": // IE specific value
69+
case "ArrowLeft":
70+
if (element.previousElementSibling) {
71+
this.columnIndex--;
72+
const previousSibling = element.previousElementSibling;
73+
Table.setTabIndex(element, -1);
74+
Table.setTabIndex(previousSibling, 0);
75+
this.focus(previousSibling);
76+
}
77+
break;
78+
case "Down": // IE specific value
79+
case "ArrowDown":
80+
if (rowIndex < rows.length - 1) {
81+
rowIndex++;
82+
const row = rows[rowIndex].querySelectorAll("td");
83+
Table.setTabIndex(element, -1);
84+
if (rows[rowIndex].classList.contains("bx--expandable-row-v2") && !rows[rowIndex].classList.contains("bx--parent-row-v2")) {
85+
Table.setTabIndex(row[0], 0);
86+
this.focus(row[0]);
87+
} else {
88+
if (this.columnIndex > row.length - 1) {
89+
this.columnIndex = row.length - 1;
90+
}
91+
Table.setTabIndex(row[this.columnIndex], 0);
92+
this.focus(row[this.columnIndex]);
93+
}
94+
}
95+
break;
96+
case "Up": // IE specific value
97+
case "ArrowUp":
98+
if ((rowIndex === 1 && Array.from(headerRow).every(th => getFocusElementList(th, tabbableSelectorIgnoreTabIndex).length === 0)) ||
99+
rowIndex === 0) {
100+
return;
101+
}
102+
Table.setTabIndex(element, -1);
103+
rowIndex--;
104+
const row = rows[rowIndex].querySelectorAll("td, th");
105+
if (rows[rowIndex].classList.contains("bx--expandable-row-v2") && !rows[rowIndex].classList.contains("bx--parent-row-v2")) {
106+
Table.setTabIndex(row[0], 0);
107+
this.focus(row[0]);
108+
} else {
109+
if (this.columnIndex > row.length - 1) {
110+
this.columnIndex = row.length - 1;
111+
}
112+
Table.setTabIndex(row[this.columnIndex], 0);
113+
this.focus(row[this.columnIndex]);
114+
}
115+
break;
116+
case "Home":
117+
this.columnIndex = 0;
118+
Table.setTabIndex(element, -1);
119+
if (event.ctrlKey) {
120+
if (Array.from(headerRow).some(th => getFocusElementList(th, tabbableSelectorIgnoreTabIndex).length > 0)) {
121+
Table.setTabIndex(headerRow[0], 0);
122+
this.focus(headerRow[0]);
123+
} else {
124+
const firstBodyCell = rows[1].querySelectorAll("td")[0];
125+
Table.setTabIndex(firstBodyCell, 0);
126+
this.focus(firstBodyCell);
127+
}
128+
} else {
129+
const firstRowCell = rows[rowIndex].querySelectorAll("th, td")[0];
130+
Table.setTabIndex(firstRowCell, 0);
131+
this.focus(firstRowCell);
132+
}
133+
break;
134+
case "End":
135+
const lastRow = rows[rows.length - 1].querySelectorAll("td");
136+
Table.setTabIndex(element, -1);
137+
if (event.ctrlKey) {
138+
this.columnIndex = lastRow.length - 1;
139+
Table.setTabIndex(lastRow[this.columnIndex], 0);
140+
this.focus(lastRow[this.columnIndex]);
141+
} else {
142+
const currentRow = rows[rowIndex].querySelectorAll("th, td");
143+
this.columnIndex = currentRow.length - 1;
144+
Table.setTabIndex(currentRow[this.columnIndex], 0);
145+
this.focus(currentRow[this.columnIndex]);
146+
}
147+
break;
148+
}
149+
}
150+
151+
@HostListener("click", ["$event"])
152+
onClick() {
153+
if (!this.ibmDataGridFocus) {
154+
return;
155+
}
156+
const focusElementList = getFocusElementList(this.elementRef.nativeElement.closest("table"), tabbableSelectorIgnoreTabIndex);
157+
focusElementList.forEach(element => Table.setTabIndex(element, -1));
158+
Table.setTabIndex(this.elementRef.nativeElement, 0);
159+
this.focus(this.elementRef.nativeElement);
160+
}
161+
}

src/table/table.component.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { Component, OnInit } from "@angular/core";
22
import { DialogModule } from "./../dialog/dialog.module";
33
import { TestBed } from "@angular/core/testing";
44
import { FormsModule } from "@angular/forms";
5-
import { TableModule, TableModel, TableHeaderItem, TableItem } from "./table.module";
5+
import {
6+
TableModule,
7+
TableModel,
8+
TableHeaderItem,
9+
TableItem,
10+
DataGridFocus
11+
} from "./table.module";
612
import { Table } from "./table.component";
713
import { StaticIconModule } from "./../icon/static-icon.module";
814

@@ -42,7 +48,8 @@ describe("Table", () => {
4248
],
4349
declarations: [
4450
Table,
45-
TableTest
51+
TableTest,
52+
DataGridFocus
4653
]
4754
});
4855

0 commit comments

Comments
 (0)