@@ -3,242 +3,104 @@ import layout from './template';
33import { computed } from '@ember/object' ;
44import { equal } from '@ember/object/computed' ;
55import { inject as service } from '@ember/service' ;
6- import { next } from '@ember/runloop' ;
6+ import { schedule , next } from '@ember/runloop' ;
77
88export default Component . extend ( {
99 layout,
1010 tagName : 'li' ,
11- tabIndex : 0 ,
12-
13- role : 'menuitem' ,
14-
15- attributeBindings : [ 'role' ] ,
11+ classNames : [ 'navbar-list-item' ] ,
1612 classNameBindings : [ 'isDropdown:dropdown' ] ,
1713 isDropdown : equal ( 'link.type' , 'dropdown' ) ,
14+ isDropdownOpen : false ,
1815
19- keyCode : Object . freeze ( {
20- 'TAB' : 9 ,
21- 'RETURN' : 13 ,
22- 'ESC' : 27 ,
23- 'SPACE' : 32 ,
24- 'PAGEUP' : 33 ,
25- 'PAGEDOWN' : 34 ,
26- 'END' : 35 ,
27- 'HOME' : 36 ,
28- 'LEFT' : 37 ,
29- 'UP' : 38 ,
30- 'RIGHT' : 39 ,
31- 'DOWN' : 40
16+ // because aria-expanded requires a string value instead of a boolean
17+ isExpanded : computed ( 'isDropdownOpen' , function ( ) {
18+ return this . isDropdownOpen ? 'true' : 'false' ;
3219 } ) ,
3320
3421 navbar : service ( ) ,
3522
36- didInsertElement ( ) {
37- this . element . tabIndex = - 1 ;
38-
39- this . get ( 'navbar' ) . register ( this ) ;
40- this . domNode = this . element . querySelector ( 'ul[role="menu"]' ) ;
23+ actions : {
24+ toggleDropdown ( ) {
25+ this . toggleProperty ( 'isDropdownOpen' ) ;
4126
42- if ( this . domNode ) {
43- this . element . querySelector ( 'a' ) . onmousedown = ( ) => this . expand ( ) ;
44- let links = Array . from ( this . domNode . querySelectorAll ( 'a' ) )
27+ if ( this . isDropdownOpen ) {
28+ // if it's open, let's make sure it can do some things
29+ schedule ( 'afterRender' , this , function ( ) {
4530
46- links . forEach ( ( ancor ) => {
47- ancor . addEventListener ( 'blur' , ( ) => this . handleBlur ( ) ) ;
48- } ) ;
31+ // move focus to the first item in the dropdown
32+ this . processFirstElementFocus ( ) ;
33+ this . processKeyPress ( ) ;
34+ } ) ;
35+ }
4936 }
5037 } ,
5138
52- handleBlur ( ) {
53- next ( this , function ( ) {
54- let subItems = Array . from ( this . element . querySelectorAll ( 'ul[role="menu"] li' ) ) ;
55- let focused = subItems . find ( item => document . activeElement === item . querySelector ( 'a' ) ) ;
56-
57- // debugger
58- if ( ! focused ) {
59- this . closePopupMenu ( ) ;
60- }
61- } )
39+ closeDropdown ( ) {
40+ // set the isDropdownOpen to false, which will make the dropdown go away
41+ this . set ( 'isDropdownOpen' , false ) ;
6242 } ,
6343
64- openPopupMenu ( ) {
65- // Get position and bounding rectangle of controller object's DOM node
66- var rect = this . element . getBoundingClientRect ( ) ;
67-
68- // Set CSS properties
69- if ( this . domNode ) {
70- this . domNode . style . display = 'block' ;
71- this . domNode . style . top = rect . height + 'px' ;
72- this . domNode . style . zIndex = 1000 ;
73- }
74-
75- this . set ( 'expanded' , true ) ;
44+ openDropdown ( ) { //might not need this
45+ // open the dropdown and set the focus to the first item inside
46+ this . set ( 'isDropdownOpen' , true ) ;
47+ this . processFirstElementFocus ( ) ;
7648 } ,
7749
78- closePopupMenu ( force ) {
79- var controllerHasHover = this . hasHover ;
80-
81- var hasFocus = this . hasFocus ;
82-
83- if ( ! this . isMenubarItem ) {
84- controllerHasHover = false ;
85- }
50+ processBlur ( ) {
51+ next ( this , function ( ) {
52+ let subItems = Array . from ( this . element . querySelectorAll ( '.navbar-dropdown-list li' ) ) ;
53+ let focused = subItems . find ( item => document . activeElement === item . querySelector ( 'a' ) ) ;
8654
87- if ( force || ( ! hasFocus && ! this . hasHover && ! controllerHasHover ) ) {
88- if ( this . domNode ) {
89- this . domNode . style . display = 'none' ;
90- this . domNode . style . zIndex = 0 ;
55+ //if the dropdown isn't focused, close it
56+ if ( ! focused ) {
57+ this . closeDropdown ( ) ;
9158 }
92- this . set ( 'expanded' , false ) ;
93- }
94- } ,
95-
96- expanded : computed ( {
97- get ( ) {
98- return this . element . getAttribute ( 'aria-expanded' ) === 'true' ;
99- } ,
100- set ( key , value ) {
101- this . element . setAttribute ( 'aria-expanded' , value ) ;
102- }
103- } ) . volatile ( ) ,
104-
105- setFocusToFirstItem ( ) {
106- let element = this . element . querySelector ( 'ul[role="menu"] li a' )
107- if ( element ) {
108- element . focus ( ) ;
109- }
59+ } ) ;
11060 } ,
11161
112- setFocusToLastItem ( ) {
113- this . element . querySelector ( 'ul[role="menu"] li a:last-of-type' ) . focus ( ) ;
62+ processClick ( ) {
63+ // TODO handle mouseclick outside the current dropdown
11464 } ,
11565
116- setFocusToNextItem ( ) {
117- let subItems = Array . from ( this . element . querySelectorAll ( 'ul[role="menu"] li' ) ) ;
118-
119- let focused = subItems . find ( item => document . activeElement === item . querySelector ( 'a' ) ) ;
120- let focusedIndex = subItems . indexOf ( focused ) ;
121-
122- let nextItem = subItems [ ( focusedIndex + 1 ) % subItems . length ] ;
123-
124- if ( ! nextItem ) {
125- return ;
126- }
127-
128- nextItem . querySelector ( 'a' ) . focus ( ) ;
66+ processFirstElementFocus ( ) {
67+ // Identify the first item in the dropdown list & set focus on it
68+ let firstFocusable = this . element . querySelector ( '.navbar-dropdown-list li:first-of-type a' ) ;
69+ firstFocusable . focus ( ) ;
12970 } ,
13071
131- setFocusToPreviousItem ( ) {
132- let subItems = Array . from ( this . element . querySelectorAll ( 'ul[role="menu"] li' ) ) ;
133-
134- let focused = subItems . find ( item => document . activeElement === item . querySelector ( 'a' ) ) ;
135- let focusedIndex = subItems . indexOf ( focused ) ;
72+ processKeyPress ( ) {
73+ // add event listeners
74+ let dropdownList = this . element . querySelector ( '.navbar-dropdown-list' ) ;
13675
137- let nextIndex = focusedIndex - 1 ;
76+ //...for certain keypress events
77+ dropdownList . addEventListener ( 'keydown' , event => {
13878
139- if ( nextIndex < 0 ) {
140- nextIndex = subItems . length - 1 ;
141- }
79+ // ESC key should close the dropdown and return focus to the toggle
80+ if ( event . keyCode === 27 && this . isDropdownOpen ) {
81+ this . closeDropdown ( ) ;
82+ this . returnFocus ( ) ;
14283
143- let nextItem = subItems [ nextIndex ] ;
144-
145- if ( ! nextItem ) {
146- return ;
147- }
84+ // if focus leaves the open dropdown via keypress, close it (without trying to otherwise control focus)
85+ } else if ( this . isDropdownOpen ) {
86+ this . processBlur ( ) ;
14887
149- nextItem . querySelector ( 'a' ) . focus ( ) ;
88+ } else {
89+ return ;
90+ }
91+ } ) ;
15092 } ,
15193
152- keyDown ( event ) {
153- let flag = false ;
154- let clickEvent ;
155- let mousedownEvent ;
156-
157- switch ( event . keyCode ) {
158- case this . keyCode . RETURN :
159- case this . keyCode . SPACE :
160- // Create simulated mouse event to mimic the behavior of ATs
161- // and let the event handler handleClick do the housekeeping.
162- mousedownEvent = new MouseEvent ( 'mousedown' , {
163- 'view' : window ,
164- 'bubbles' : true ,
165- 'cancelable' : true
166- } ) ;
167- clickEvent = new MouseEvent ( 'click' , {
168- 'view' : window ,
169- 'bubbles' : true ,
170- 'cancelable' : true
171- } ) ;
172-
173- document . activeElement . dispatchEvent ( mousedownEvent ) ;
174- document . activeElement . dispatchEvent ( clickEvent ) ;
175-
176- flag = true ;
177- break ;
178- case this . keyCode . DOWN :
179- if ( this . get ( 'expanded' ) ) {
180- this . setFocusToNextItem ( ) ;
181- } else {
182- this . openPopupMenu ( ) ;
183- this . setFocusToFirstItem ( ) ;
184- }
185- flag = true ;
186- break ;
187-
188- case this . keyCode . LEFT :
189- this . get ( 'navbar' ) . setFocusToPreviousItem ( this ) ;
190- flag = true ;
191- break ;
192-
193- case this . keyCode . RIGHT :
194- this . get ( 'navbar' ) . setFocusToNextItem ( this ) ;
195- flag = true ;
196- break ;
197-
198- case this . keyCode . UP :
199- if ( this . get ( 'expanded' ) ) {
200- this . setFocusToPreviousItem ( ) ;
201- } else {
202- this . openPopupMenu ( ) ;
203- this . setFocusToLastItem ( ) ;
204- }
205- break ;
206-
207- case this . keyCode . HOME :
208- case this . keyCode . PAGEUP :
209- this . setFocusToFirstItem ( ) ;
210- flag = true ;
211- break ;
212-
213- case this . keyCode . END :
214- case this . keyCode . PAGEDOWN :
215- this . setFocusToLastItem ( ) ;
216- flag = true ;
217- break ;
218-
219- case this . keyCode . TAB :
220- this . closePopupMenu ( true ) ;
221- break ;
222-
223- case this . keyCode . ESC :
224- this . closePopupMenu ( true ) ;
225- break ;
226- }
227-
228- if ( flag ) {
229- event . stopPropagation ( ) ;
230- event . preventDefault ( ) ;
231- }
94+ returnFocus ( ) {
95+ // after that rendering bit happens, we need to return the focus to the trigger
96+ schedule ( 'afterRender' , this , function ( ) {
97+ let dropdownTrigger = this . element . querySelector ( '.navbar-list-item-dropdown-toggle' ) ;
98+ dropdownTrigger . focus ( ) ;
99+ } ) ;
232100 } ,
233101
234- expand ( ) {
235- next ( this , ( ) => {
236- if ( this . get ( 'expanded' ) ) {
237- this . closePopupMenu ( ) ;
238- } else {
239- this . openPopupMenu ( ) ;
240- this . setFocusToFirstItem ( ) ;
241- }
242- } )
102+ willDestroyElement ( ) {
103+ document . removeEventListener ( 'keydown' , this . triggerDropdown ) ;
104+ // document.removeEventListener('click', this.triggerDropdown);
243105 }
244106} ) ;
0 commit comments