77 TemplateRef ,
88 AfterViewInit ,
99 ViewChild ,
10- ElementRef
10+ ElementRef ,
11+ ViewChildren ,
12+ QueryList
1113} from "@angular/core" ;
1214
1315import { 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)"
@@ -118,6 +122,16 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
118122 * Event to emit selection of a list item within the `DropdownList`.
119123 */
120124 @Output ( ) select : EventEmitter < Object > = new EventEmitter < Object > ( ) ;
125+ /**
126+ * Event to suggest a blur on the view.
127+ * Emits _after_ the first/last item has been focused.
128+ * ex.
129+ * ArrowUp -> focus first item
130+ * ArrowUp -> emit event
131+ *
132+ * When this event fires focus should be placed on some element outside of the list - blurring the list as a result
133+ */
134+ @Output ( ) blurIntent = new EventEmitter < "top" | "bottom" > ( ) ;
121135 /**
122136 * Maintains a reference to the view DOM element for the unordered list of items within the `DropdownList`.
123137 */
@@ -148,7 +162,7 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
148162 /**
149163 * An array holding the HTML list elements in the view.
150164 */
151- protected listElementList : HTMLElement [ ] ;
165+ @ ViewChildren ( "listItem" ) protected listElementList : QueryList < ElementRef > ;
152166 /**
153167 * Observable bound to keydown events to control filtering.
154168 */
@@ -176,7 +190,6 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
176190 * Additionally, any Observables for the `DropdownList` are initialized.
177191 */
178192 ngAfterViewInit ( ) {
179- this . listElementList = Array . from ( this . list . nativeElement . querySelectorAll ( "li" ) ) as HTMLElement [ ] ;
180193 this . index = this . getListItems ( ) . findIndex ( item => item . selected ) ;
181194 this . setupFocusObservable ( ) ;
182195 }
@@ -196,9 +209,6 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
196209 updateList ( items ) {
197210 this . _items = items . map ( item => Object . assign ( { } , item ) ) ;
198211 this . displayItems = this . _items ;
199- setTimeout ( ( ) => {
200- this . listElementList = Array . from ( this . list . nativeElement . querySelectorAll ( "li" ) ) as HTMLElement [ ] ;
201- } , 0 ) ;
202212 this . index = this . _items . findIndex ( item => item . selected ) ;
203213 this . setupFocusObservable ( ) ;
204214 setTimeout ( ( ) => {
@@ -219,6 +229,8 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
219229 } else {
220230 this . displayItems = this . getListItems ( ) ;
221231 }
232+ // reset the index since the list has changed visually
233+ this . index = 0 ;
222234 }
223235
224236 /**
@@ -240,18 +252,18 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
240252 * Returns the `ListItem` that is subsequent to the selected item in the `DropdownList`.
241253 */
242254 getNextItem ( ) : ListItem {
243- if ( this . index < this . getListItems ( ) . length - 1 ) {
255+ if ( this . index < this . displayItems . length - 1 ) {
244256 this . index ++ ;
245257 }
246- return this . getListItems ( ) [ this . index ] ;
258+ return this . displayItems [ this . index ] ;
247259 }
248260
249261 /**
250262 * Returns `true` if the selected item is not the last item in the `DropdownList`.
251263 * TODO: standardize
252264 */
253265 hasNextElement ( ) : boolean {
254- if ( this . index < this . getListItems ( ) . length - 1 ) {
266+ if ( this . index < this . displayItems . length - 1 ) {
255267 return true ;
256268 }
257269 return false ;
@@ -261,11 +273,11 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
261273 * Returns the `HTMLElement` for the item that is subsequent to the selected item.
262274 */
263275 getNextElement ( ) : HTMLElement {
264- if ( this . index < this . getListItems ( ) . length - 1 ) {
276+ if ( this . index < this . displayItems . length - 1 ) {
265277 this . index ++ ;
266278 }
267- let elem = this . listElementList [ this . index ] ;
268- let item = this . getListItems ( ) [ this . index ] ;
279+ let elem = this . listElementList . toArray ( ) [ this . index ] . nativeElement ;
280+ let item = this . displayItems [ this . index ] ;
269281 if ( item . disabled ) {
270282 return this . getNextElement ( ) ;
271283 }
@@ -279,7 +291,7 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
279291 if ( this . index > 0 ) {
280292 this . index -- ;
281293 }
282- return this . getListItems ( ) [ this . index ] ;
294+ return this . displayItems [ this . index ] ;
283295 }
284296
285297 /**
@@ -300,8 +312,8 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
300312 if ( this . index > 0 ) {
301313 this . index -- ;
302314 }
303- let elem = this . listElementList [ this . index ] ;
304- let item = this . getListItems ( ) [ this . index ] ;
315+ let elem = this . listElementList . toArray ( ) [ this . index ] . nativeElement ;
316+ let item = this . displayItems [ this . index ] ;
305317 if ( item . disabled ) {
306318 return this . getPrevElement ( ) ;
307319 }
@@ -313,19 +325,19 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
313325 */
314326 getCurrentItem ( ) : ListItem {
315327 if ( this . index < 0 ) {
316- return this . getListItems ( ) [ 0 ] ;
328+ return this . displayItems [ 0 ] ;
317329 }
318- return this . getListItems ( ) [ this . index ] ;
330+ return this . displayItems [ this . index ] ;
319331 }
320332
321333 /**
322334 * Returns the `HTMLElement` for the item that is selected within the `DropdownList`.
323335 */
324336 getCurrentElement ( ) : HTMLElement {
325337 if ( this . index < 0 ) {
326- return this . listElementList [ 0 ] ;
338+ return this . listElementList . first . nativeElement ;
327339 }
328- return this . listElementList [ this . index ] ;
340+ return this . listElementList . toArray ( ) [ this . index ] . nativeElement ;
329341 }
330342
331343 /**
@@ -376,6 +388,10 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
376388 * Initializes focus in the list, effectively a wrapper for `getCurrentElement().focus()`
377389 */
378390 initFocus ( ) {
391+ // ensure we start at this first item if nothing is already selected
392+ if ( this . index < 0 ) {
393+ this . index = 0 ;
394+ }
379395 this . getCurrentElement ( ) . focus ( ) ;
380396 }
381397
@@ -391,14 +407,17 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
391407 }
392408 } else if ( event . key === "ArrowDown" || event . key === "ArrowUp" || event . key === "Down" || event . key === "Up" ) {
393409 event . preventDefault ( ) ;
394- // this.checkScrollArrows();
395- if ( ( event . key === "ArrowDown" || event . key === "Down" ) && this . hasNextElement ( ) ) {
396- this . getNextElement ( ) . focus ( ) ;
410+ if ( event . key === "ArrowDown" || event . key === "Down" ) {
411+ if ( this . hasNextElement ( ) ) {
412+ this . getNextElement ( ) . focus ( ) ;
413+ } else {
414+ this . blurIntent . emit ( "bottom" ) ;
415+ }
397416 } else if ( event . key === "ArrowUp" || event . key === "Up" ) {
398417 if ( this . hasPrevElement ( ) ) {
399418 this . getPrevElement ( ) . focus ( ) ;
400- } else if ( this . getSelected ( ) ) {
401- this . clearSelected . nativeElement . focus ( ) ;
419+ } else {
420+ this . blurIntent . emit ( "top" ) ;
402421 }
403422 }
404423 }
@@ -427,12 +446,14 @@ export class DropdownList implements AbstractDropdownView, AfterViewInit, OnDest
427446 }
428447
429448 onItemFocus ( index ) {
430- this . listElementList [ index ] . classList . add ( "bx--list-box__menu-item--highlighted" ) ;
431- this . listElementList [ index ] . tabIndex = 0 ;
449+ const element = this . listElementList . toArray ( ) [ index ] . nativeElement ;
450+ element . classList . add ( "bx--list-box__menu-item--highlighted" ) ;
451+ element . tabIndex = 0 ;
432452 }
433453
434454 onItemBlur ( index ) {
435- this . listElementList [ index ] . classList . remove ( "bx--list-box__menu-item--highlighted" ) ;
436- this . listElementList [ index ] . tabIndex = - 1 ;
455+ const element = this . listElementList . toArray ( ) [ index ] . nativeElement ;
456+ element . classList . remove ( "bx--list-box__menu-item--highlighted" ) ;
457+ element . tabIndex = - 1 ;
437458 }
438459}
0 commit comments