@@ -3,242 +3,104 @@ import layout from './template';
3
3
import { computed } from '@ember/object' ;
4
4
import { equal } from '@ember/object/computed' ;
5
5
import { inject as service } from '@ember/service' ;
6
- import { next } from '@ember/runloop' ;
6
+ import { schedule , next } from '@ember/runloop' ;
7
7
8
8
export default Component . extend ( {
9
9
layout,
10
10
tagName : 'li' ,
11
- tabIndex : 0 ,
12
-
13
- role : 'menuitem' ,
14
-
15
- attributeBindings : [ 'role' ] ,
11
+ classNames : [ 'navbar-list-item' ] ,
16
12
classNameBindings : [ 'isDropdown:dropdown' ] ,
17
13
isDropdown : equal ( 'link.type' , 'dropdown' ) ,
14
+ isDropdownOpen : false ,
18
15
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' ;
32
19
} ) ,
33
20
34
21
navbar : service ( ) ,
35
22
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' ) ;
41
26
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 ( ) {
45
30
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
+ }
49
36
}
50
37
} ,
51
38
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 ) ;
62
42
} ,
63
43
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 ( ) ;
76
48
} ,
77
49
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' ) ) ;
86
54
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 ( ) ;
91
58
}
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
+ } ) ;
110
60
} ,
111
61
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
114
64
} ,
115
65
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 ( ) ;
129
70
} ,
130
71
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' ) ;
136
75
137
- let nextIndex = focusedIndex - 1 ;
76
+ //...for certain keypress events
77
+ dropdownList . addEventListener ( 'keydown' , event => {
138
78
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 ( ) ;
142
83
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 ( ) ;
148
87
149
- nextItem . querySelector ( 'a' ) . focus ( ) ;
88
+ } else {
89
+ return ;
90
+ }
91
+ } ) ;
150
92
} ,
151
93
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
+ } ) ;
232
100
} ,
233
101
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);
243
105
}
244
106
} ) ;
0 commit comments