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)"
@@ -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