Skip to content

Commit bfa081c

Browse files
authored
Merge branch 'master' into esulisttests
2 parents 55f2bbc + caa8511 commit bfa081c

File tree

10 files changed

+605
-264
lines changed

10 files changed

+605
-264
lines changed

addon/components/es-navbar/component.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,28 @@ import defaultLinks from '../../constants/links';
66

77
export default Component.extend({
88
layout,
9+
tagName: 'nav',
10+
classNames: ['es-navbar'],
11+
ariaLabel: 'Ember',
12+
navHome: computed('home', function() {
13+
if (this.home) {
14+
return this.home;
15+
}
16+
17+
return 'https://www.emberjs.com';
18+
}),
919
navLinks: computed('links.[]', function() {
1020
if(this.links) {
1121
return this.links;
1222
}
1323

1424
return defaultLinks;
15-
})
25+
}),
26+
actions: {
27+
toggleMenu() {
28+
let menu = this.element.querySelector('ul[role="menubar"]');
29+
30+
menu.setAttribute('aria-expanded', menu.getAttribute('aria-expanded') !== 'true');
31+
}
32+
}
1633
});
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import Component from '@ember/component';
2+
import layout from './template';
3+
import { computed } from '@ember/object';
4+
import { equal } from '@ember/object/computed';
5+
import { inject as service } from '@ember/service';
6+
import { next } from '@ember/runloop';
7+
8+
export default Component.extend({
9+
layout,
10+
tagName: 'li',
11+
tabIndex: 0,
12+
13+
classNameBindings: ['isDropdown:dropdown'],
14+
isDropdown: equal('link.type', 'dropdown'),
15+
16+
keyCode: Object.freeze({
17+
'TAB': 9,
18+
'RETURN': 13,
19+
'ESC': 27,
20+
'SPACE': 32,
21+
'PAGEUP': 33,
22+
'PAGEDOWN': 34,
23+
'END': 35,
24+
'HOME': 36,
25+
'LEFT': 37,
26+
'UP': 38,
27+
'RIGHT': 39,
28+
'DOWN': 40
29+
}),
30+
31+
navbar: service(),
32+
33+
init() {
34+
this._super(...arguments);
35+
36+
this.element.tabIndex = -1;
37+
},
38+
39+
didInsertElement() {
40+
this.get('navbar').register(this);
41+
this.domNode = this.element.querySelector('ul[role="menu"]');
42+
43+
if(this.domNode) {
44+
this.element.querySelector('a').onmousedown = () => this.expand();
45+
let links = Array.from(this.domNode.querySelectorAll('a'))
46+
47+
links.forEach((ancor) => {
48+
ancor.addEventListener('blur', () => this.handleBlur());
49+
});
50+
}
51+
},
52+
53+
handleBlur() {
54+
next(this, function() {
55+
let subItems = Array.from(this.element.querySelectorAll('ul[role="menu"] li'));
56+
let focused = subItems.find(item => document.activeElement === item.querySelector('a'));
57+
58+
// debugger
59+
if(!focused) {
60+
this.closePopupMenu();
61+
}
62+
})
63+
},
64+
65+
openPopupMenu() {
66+
// Get position and bounding rectangle of controller object's DOM node
67+
var rect = this.element.getBoundingClientRect();
68+
69+
// Set CSS properties
70+
if(this.domNode) {
71+
this.domNode.style.display = 'block';
72+
this.domNode.style.top = (rect.height - 1) + 'px';
73+
this.domNode.style.zIndex = 100;
74+
}
75+
76+
this.set('expanded', true);
77+
},
78+
79+
closePopupMenu(force) {
80+
var controllerHasHover = this.hasHover;
81+
82+
var hasFocus = this.hasFocus;
83+
84+
if (!this.isMenubarItem) {
85+
controllerHasHover = false;
86+
}
87+
88+
if (force || (!hasFocus && !this.hasHover && !controllerHasHover)) {
89+
if(this.domNode) {
90+
this.domNode.style.display = 'none';
91+
this.domNode.style.zIndex = 0;
92+
}
93+
this.set('expanded', false);
94+
}
95+
},
96+
97+
expanded: computed({
98+
get() {
99+
return this.element.getAttribute('aria-expanded') === 'true';
100+
},
101+
set(key, value) {
102+
this.element.setAttribute('aria-expanded', value);
103+
}
104+
}).volatile(),
105+
106+
setFocusToFirstItem() {
107+
let element = this.element.querySelector('ul[role="menu"] li a')
108+
if (element) {
109+
element.focus();
110+
}
111+
},
112+
113+
setFocusToLastItem() {
114+
this.element.querySelector('ul[role="menu"] li a:last-of-type').focus();
115+
},
116+
117+
setFocusToNextItem() {
118+
let subItems = Array.from(this.element.querySelectorAll('ul[role="menu"] li'));
119+
120+
let focused = subItems.find(item => document.activeElement === item.querySelector('a'));
121+
let focusedIndex = subItems.indexOf(focused);
122+
123+
let nextItem = subItems[(focusedIndex + 1) % subItems.length];
124+
125+
if (!nextItem) {
126+
return;
127+
}
128+
129+
nextItem.querySelector('a').focus();
130+
},
131+
132+
setFocusToPreviousItem() {
133+
let subItems = Array.from(this.element.querySelectorAll('ul[role="menu"] li'));
134+
135+
let focused = subItems.find(item => document.activeElement === item.querySelector('a'));
136+
let focusedIndex = subItems.indexOf(focused);
137+
138+
let nextIndex = focusedIndex - 1;
139+
140+
if (nextIndex < 0) {
141+
nextIndex = subItems.length - 1;
142+
}
143+
144+
let nextItem = subItems[nextIndex];
145+
146+
if (!nextItem) {
147+
return;
148+
}
149+
150+
nextItem.querySelector('a').focus();
151+
},
152+
153+
keyDown(event) {
154+
let flag = false;
155+
let clickEvent;
156+
let mousedownEvent;
157+
158+
switch (event.keyCode) {
159+
case this.keyCode.RETURN:
160+
case this.keyCode.SPACE:
161+
// Create simulated mouse event to mimic the behavior of ATs
162+
// and let the event handler handleClick do the housekeeping.
163+
mousedownEvent = new MouseEvent('mousedown', {
164+
'view': window,
165+
'bubbles': true,
166+
'cancelable': true
167+
});
168+
clickEvent = new MouseEvent('click', {
169+
'view': window,
170+
'bubbles': true,
171+
'cancelable': true
172+
});
173+
174+
document.activeElement.dispatchEvent(mousedownEvent);
175+
document.activeElement.dispatchEvent(clickEvent);
176+
177+
flag = true;
178+
break;
179+
case this.keyCode.DOWN:
180+
if(this.get('expanded')) {
181+
this.setFocusToNextItem();
182+
} else {
183+
this.openPopupMenu();
184+
this.setFocusToFirstItem();
185+
}
186+
flag = true;
187+
break;
188+
189+
case this.keyCode.LEFT:
190+
this.get('navbar').setFocusToPreviousItem(this);
191+
flag = true;
192+
break;
193+
194+
case this.keyCode.RIGHT:
195+
this.get('navbar').setFocusToNextItem(this);
196+
flag = true;
197+
break;
198+
199+
case this.keyCode.UP:
200+
if(this.get('expanded')) {
201+
this.setFocusToPreviousItem();
202+
} else {
203+
this.openPopupMenu();
204+
this.setFocusToLastItem();
205+
}
206+
break;
207+
208+
case this.keyCode.HOME:
209+
case this.keyCode.PAGEUP:
210+
this.setFocusToFirstItem();
211+
flag = true;
212+
break;
213+
214+
case this.keyCode.END:
215+
case this.keyCode.PAGEDOWN:
216+
this.setFocusToLastItem();
217+
flag = true;
218+
break;
219+
220+
case this.keyCode.TAB:
221+
this.closePopupMenu(true);
222+
break;
223+
224+
case this.keyCode.ESC:
225+
this.closePopupMenu(true);
226+
break;
227+
}
228+
229+
if (flag) {
230+
event.stopPropagation();
231+
event.preventDefault();
232+
}
233+
},
234+
235+
expand() {
236+
next(this, () => {
237+
if(this.get('expanded')) {
238+
this.closePopupMenu();
239+
} else {
240+
this.openPopupMenu();
241+
this.setFocusToFirstItem();
242+
}
243+
})
244+
}
245+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{{#if (eq link.type "link")}}
2+
<a
3+
role="menuitem"
4+
aria-haspopup="true"
5+
aria-expanded="false"
6+
href={{link.href}}
7+
tabindex={{if (eq index 0) 0 -1}}
8+
>
9+
{{link.name}}
10+
</a>
11+
{{/if}}
12+
{{#if (eq link.type "dropdown")}}
13+
<a
14+
role="menuitem"
15+
aria-haspopup="true"
16+
aria-expanded="false"
17+
href="javascript:void(0)"
18+
tabindex={{if (eq index 0) 0 -1}}
19+
>
20+
{{link.name}}
21+
</a>
22+
<ul class="dropdown" role="menu" aria-label={{link.name}}>
23+
{{#each link.items as |item|}}
24+
{{#if (eq item.type "link")}}
25+
<li role="none">
26+
<a
27+
role="menuitem"
28+
href={{item.href}}
29+
tabindex="-1"
30+
>
31+
{{item.name}}
32+
</a>
33+
</li>
34+
{{/if}}
35+
{{#if (eq item.type "divider")}}
36+
<hr>
37+
{{/if}}
38+
{{/each}}
39+
</ul>
40+
{{/if}}

0 commit comments

Comments
 (0)