Skip to content

Commit e310a9a

Browse files
committed
Merge branch 'dropdown' of github.com:cal-smith/carbon-components-angular into dropdown
2 parents 2d34a38 + 3a9c92d commit e310a9a

File tree

3 files changed

+64
-33
lines changed

3 files changed

+64
-33
lines changed

src/combobox/combobox.component.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { AbstractDropdownView } from "./../dropdown/abstract-dropdown-view.class";
1717
import { ListItem } from "./../dropdown/list-item.interface";
1818
import { NG_VALUE_ACCESSOR } from "@angular/forms";
19+
import { filter } from "rxjs/operators";
1920

2021
/**
2122
* ComboBoxes are similar to dropdowns, except a combobox provides an input field for users to search items and (optionally) add their own.
@@ -262,6 +263,10 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
262263
setTimeout(() => {
263264
this.updateSelected();
264265
});
266+
267+
this.view.blur.pipe(filter(v => v === "top")).subscribe(() => {
268+
this.elementRef.nativeElement.querySelector(".bx--text-input").focus();
269+
});
265270
}
266271
}
267272

@@ -291,10 +296,6 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
291296
ev.stopPropagation();
292297
this.openDropdown();
293298
setTimeout(() => this.view.getCurrentElement().focus(), 0);
294-
} else if ((ev.key === "ArrowUp" || ev.key === "Up") // `"Up"` is IE specific value
295-
&& this.dropdownMenu.nativeElement.contains(ev.target)
296-
&& !this.view.hasPrevElement()) {
297-
this.elementRef.nativeElement.querySelector(".bx--text-input").focus();
298299
}
299300
}
300301

src/dropdown/abstract-dropdown-view.class.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ export class AbstractDropdownView {
1919
* Emits selection events to other class.
2020
*/
2121
@Output() select: EventEmitter<Object>;
22+
/**
23+
* Event to suggest a blur on the view.
24+
* Emits _after_ the first/last item has been focused.
25+
* ex.
26+
* ArrowUp -> focus first item
27+
* ArrowUp -> emit event
28+
*
29+
* It's recommended that the implementing view include a specific type union of possible blurs
30+
* ex. `@Output() blur = new EventEmitter<"top" | "bottom">();`
31+
*/
32+
@Output() blur: EventEmitter<any>;
2233
/**
2334
* Specifies whether or not the `DropdownList` supports selecting multiple items as opposed to single
2435
* item selection.

src/dropdown/list/dropdown-list.component.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
TemplateRef,
88
AfterViewInit,
99
ViewChild,
10-
ElementRef
10+
ElementRef,
11+
ViewChildren,
12+
QueryList
1113
} from "@angular/core";
1214

1315
import { I18n } from "../../i18n/i18n.module";
@@ -51,7 +53,9 @@ import { Observable, isObservable, Subscription } from "rxjs";
5153
role="listbox"
5254
class="bx--list-box__menu"
5355
[attr.aria-label]="ariaLabel">
54-
<li tabindex="-1"
56+
<li
57+
#listItem
58+
tabindex="-1"
5559
role="option"
5660
*ngFor="let item of displayItems; let i = index"
5761
(click)="doClick($event, item)"
@@ -116,6 +120,14 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
116120
* Event to emit selection of a list item within the `DropdownList`.
117121
*/
118122
@Output() select: EventEmitter<Object> = new EventEmitter<Object>();
123+
/**
124+
* Event to suggest a blur on the view.
125+
* Emits _after_ the first/last item has been focused.
126+
* ex.
127+
* ArrowUp -> focus first item
128+
* ArrowUp -> emit event
129+
*/
130+
@Output() blur = new EventEmitter<"top" | "bottom">();
119131
/**
120132
* Maintains a reference to the view DOM element for the unordered list of items within the `DropdownList`.
121133
*/
@@ -146,7 +158,7 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
146158
/**
147159
* An array holding the HTML list elements in the view.
148160
*/
149-
protected listElementList: HTMLElement[];
161+
@ViewChildren("listItem") protected listElementList: QueryList<ElementRef>;
150162
/**
151163
* Observable bound to keydown events to control filtering.
152164
*/
@@ -174,7 +186,6 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
174186
* Additionally, any Observables for the `DropdownList` are initialized.
175187
*/
176188
ngAfterViewInit() {
177-
this.listElementList = Array.from(this.list.nativeElement.querySelectorAll("li")) as HTMLElement[];
178189
this.index = this.getListItems().findIndex(item => item.selected);
179190
this.setupFocusObservable();
180191
}
@@ -194,9 +205,6 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
194205
updateList(items) {
195206
this._items = items.map(item => Object.assign({}, item));
196207
this.displayItems = this._items;
197-
setTimeout(() => {
198-
this.listElementList = Array.from(this.list.nativeElement.querySelectorAll("li")) as HTMLElement[];
199-
}, 0);
200208
this.index = this._items.findIndex(item => item.selected);
201209
this.setupFocusObservable();
202210
setTimeout(() => {
@@ -217,6 +225,8 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
217225
} else {
218226
this.displayItems = this.getListItems();
219227
}
228+
// reset the index since the list has changed visually
229+
this.index = 0;
220230
}
221231

222232
/**
@@ -238,18 +248,18 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
238248
* Returns the `ListItem` that is subsequent to the selected item in the `DropdownList`.
239249
*/
240250
getNextItem(): ListItem {
241-
if (this.index < this.getListItems().length - 1) {
251+
if (this.index < this.displayItems.length - 1) {
242252
this.index++;
243253
}
244-
return this.getListItems()[this.index];
254+
return this.displayItems[this.index];
245255
}
246256

247257
/**
248258
* Returns `true` if the selected item is not the last item in the `DropdownList`.
249259
* TODO: standardize
250260
*/
251261
hasNextElement(): boolean {
252-
if (this.index < this.getListItems().length - 1) {
262+
if (this.index < this.displayItems.length - 1) {
253263
return true;
254264
}
255265
return false;
@@ -259,11 +269,11 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
259269
* Returns the `HTMLElement` for the item that is subsequent to the selected item.
260270
*/
261271
getNextElement(): HTMLElement {
262-
if (this.index < this.getListItems().length - 1) {
272+
if (this.index < this.displayItems.length - 1) {
263273
this.index++;
264274
}
265-
let elem = this.listElementList[this.index];
266-
let item = this.getListItems()[this.index];
275+
let elem = this.listElementList.toArray()[this.index].nativeElement;
276+
let item = this.displayItems[this.index];
267277
if (item.disabled) {
268278
return this.getNextElement();
269279
}
@@ -277,7 +287,7 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
277287
if (this.index > 0) {
278288
this.index--;
279289
}
280-
return this.getListItems()[this.index];
290+
return this.displayItems[this.index];
281291
}
282292

283293
/**
@@ -298,8 +308,8 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
298308
if (this.index > 0) {
299309
this.index--;
300310
}
301-
let elem = this.listElementList[this.index];
302-
let item = this.getListItems()[this.index];
311+
let elem = this.listElementList.toArray()[this.index].nativeElement;
312+
let item = this.displayItems[this.index];
303313
if (item.disabled) {
304314
return this.getPrevElement();
305315
}
@@ -311,19 +321,19 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
311321
*/
312322
getCurrentItem(): ListItem {
313323
if (this.index < 0) {
314-
return this.getListItems()[0];
324+
return this.displayItems[0];
315325
}
316-
return this.getListItems()[this.index];
326+
return this.displayItems[this.index];
317327
}
318328

319329
/**
320330
* Returns the `HTMLElement` for the item that is selected within the `DropdownList`.
321331
*/
322332
getCurrentElement(): HTMLElement {
323333
if (this.index < 0) {
324-
return this.listElementList[0];
334+
return this.listElementList.first.nativeElement;
325335
}
326-
return this.listElementList[this.index];
336+
return this.listElementList.toArray()[this.index].nativeElement;
327337
}
328338

329339
/**
@@ -374,6 +384,10 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
374384
* Initializes focus in the list, effectively a wrapper for `getCurrentElement().focus()`
375385
*/
376386
initFocus() {
387+
// ensure we start at this first item if nothing is already selected
388+
if (this.index < 0) {
389+
this.index = 0;
390+
}
377391
this.getCurrentElement().focus();
378392
}
379393

@@ -389,14 +403,17 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
389403
}
390404
} else if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Down" || event.key === "Up") {
391405
event.preventDefault();
392-
// this.checkScrollArrows();
393-
if ((event.key === "ArrowDown" || event.key === "Down") && this.hasNextElement()) {
394-
this.getNextElement().focus();
406+
if (event.key === "ArrowDown" || event.key === "Down") {
407+
if (this.hasNextElement()) {
408+
this.getNextElement().focus();
409+
} else {
410+
this.blur.emit("bottom");
411+
}
395412
} else if (event.key === "ArrowUp" || event.key === "Up") {
396413
if (this.hasPrevElement()) {
397414
this.getPrevElement().focus();
398-
} else if (this.getSelected()) {
399-
this.clearSelected.nativeElement.focus();
415+
} else {
416+
this.blur.emit("top");
400417
}
401418
}
402419
}
@@ -425,12 +442,14 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
425442
}
426443

427444
onItemFocus(index) {
428-
this.listElementList[index].classList.add("bx--list-box__menu-item--highlighted");
429-
this.listElementList[index].tabIndex = 0;
445+
const element = this.listElementList.toArray()[index].nativeElement;
446+
element.classList.add("bx--list-box__menu-item--highlighted");
447+
element.tabIndex = 0;
430448
}
431449

432450
onItemBlur(index) {
433-
this.listElementList[index].classList.remove("bx--list-box__menu-item--highlighted");
434-
this.listElementList[index].tabIndex = -1;
451+
const element = this.listElementList.toArray()[index].nativeElement;
452+
element.classList.remove("bx--list-box__menu-item--highlighted");
453+
element.tabIndex = -1;
435454
}
436455
}

0 commit comments

Comments
 (0)