Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit c08509a

Browse files
committed
feat(tooltip): hide tooltip when esc is hit
- Hide tooltip when `esc` is hit for accessibility Closes #4367 Resolves #4248
1 parent f269983 commit c08509a

File tree

5 files changed

+118
-61
lines changed

5 files changed

+118
-61
lines changed

src/modal/modal.js

+1-56
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,4 @@
1-
angular.module('ui.bootstrap.modal', [])
2-
3-
/**
4-
* A helper, internal data structure that acts as a map but also allows getting / removing
5-
* elements in the LIFO order
6-
*/
7-
.factory('$$stackedMap', function() {
8-
return {
9-
createNew: function() {
10-
var stack = [];
11-
12-
return {
13-
add: function(key, value) {
14-
stack.push({
15-
key: key,
16-
value: value
17-
});
18-
},
19-
get: function(key) {
20-
for (var i = 0; i < stack.length; i++) {
21-
if (key == stack[i].key) {
22-
return stack[i];
23-
}
24-
}
25-
},
26-
keys: function() {
27-
var keys = [];
28-
for (var i = 0; i < stack.length; i++) {
29-
keys.push(stack[i].key);
30-
}
31-
return keys;
32-
},
33-
top: function() {
34-
return stack[stack.length - 1];
35-
},
36-
remove: function(key) {
37-
var idx = -1;
38-
for (var i = 0; i < stack.length; i++) {
39-
if (key == stack[i].key) {
40-
idx = i;
41-
break;
42-
}
43-
}
44-
return stack.splice(idx, 1)[0];
45-
},
46-
removeTop: function() {
47-
return stack.splice(stack.length - 1, 1)[0];
48-
},
49-
length: function() {
50-
return stack.length;
51-
}
52-
};
53-
}
54-
};
55-
})
56-
1+
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
572
/**
583
* A helper, internal data structure that stores all references attached to key
594
*/

src/stackedMap/stackedMap.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
angular.module('ui.bootstrap.stackedMap', [])
2+
/**
3+
* A helper, internal data structure that acts as a map but also allows getting / removing
4+
* elements in the LIFO order
5+
*/
6+
.factory('$$stackedMap', function() {
7+
return {
8+
createNew: function() {
9+
var stack = [];
10+
11+
return {
12+
add: function(key, value) {
13+
stack.push({
14+
key: key,
15+
value: value
16+
});
17+
},
18+
get: function(key) {
19+
for (var i = 0; i < stack.length; i++) {
20+
if (key == stack[i].key) {
21+
return stack[i];
22+
}
23+
}
24+
},
25+
keys: function() {
26+
var keys = [];
27+
for (var i = 0; i < stack.length; i++) {
28+
keys.push(stack[i].key);
29+
}
30+
return keys;
31+
},
32+
top: function() {
33+
return stack[stack.length - 1];
34+
},
35+
remove: function(key) {
36+
var idx = -1;
37+
for (var i = 0; i < stack.length; i++) {
38+
if (key == stack[i].key) {
39+
idx = i;
40+
break;
41+
}
42+
}
43+
return stack.splice(idx, 1)[0];
44+
},
45+
removeTop: function() {
46+
return stack.splice(stack.length - 1, 1)[0];
47+
},
48+
length: function() {
49+
return stack.length;
50+
}
51+
};
52+
}
53+
};
54+
});

src/modal/test/stackedMap.spec.js src/stackedMap/test/stackedMap.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ describe('stacked map', function() {
4949
it('should ignore removal of non-existing elements', function() {
5050
expect(stackedMap.remove('non-existing')).toBeUndefined();
5151
});
52-
});
52+
});

src/tooltip/test/tooltip.spec.js

+39-2
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ describe('tooltip', function() {
33
elmBody,
44
scope,
55
elmScope,
6-
tooltipScope;
6+
tooltipScope,
7+
$document;
78

89
// load the tooltip code
910
beforeEach(module('ui.bootstrap.tooltip'));
1011

1112
// load the template
1213
beforeEach(module('template/tooltip/tooltip-popup.html'));
1314

14-
beforeEach(inject(function($rootScope, $compile) {
15+
beforeEach(inject(function($rootScope, $compile, _$document_) {
1516
elmBody = angular.element(
1617
'<div><span tooltip="tooltip text" tooltip-animation="false">Selector Text</span></div>'
1718
);
1819

20+
$document = _$document_;
1921
scope = $rootScope;
2022
$compile(elmBody)(scope);
2123
scope.$digest();
@@ -319,6 +321,41 @@ describe('tooltip', function() {
319321

320322
expect(tooltipScope.isOpen).toBe(true);
321323
});
324+
325+
it('should close the tooltips in order', inject(function($compile) {
326+
var elm2 = $compile('<div><span tooltip="tooltip #2" tooltip-is-open="isOpen2">Selector Text</span></div>')(scope);
327+
scope.$digest();
328+
elm2 = elm2.find('span');
329+
var tooltipScope2 = elm2.scope().$$childTail;
330+
tooltipScope2.isOpen = false;
331+
scope.$digest();
332+
333+
trigger(elm, 'mouseenter');
334+
$timeout.flush();
335+
expect(tooltipScope.isOpen).toBe(true);
336+
expect(tooltipScope2.isOpen).toBe(false);
337+
338+
trigger(elm2, 'mouseenter');
339+
$timeout.flush();
340+
expect(tooltipScope.isOpen).toBe(true);
341+
expect(tooltipScope2.isOpen).toBe(true);
342+
343+
var evt = $.Event('keypress');
344+
evt.which = 27;
345+
346+
$document.trigger(evt);
347+
348+
expect(tooltipScope.isOpen).toBe(true);
349+
expect(tooltipScope2.isOpen).toBe(false);
350+
351+
var evt2 = $.Event('keypress');
352+
evt2.which = 27;
353+
354+
$document.trigger(evt2);
355+
356+
expect(tooltipScope.isOpen).toBe(false);
357+
expect(tooltipScope2.isOpen).toBe(false);
358+
}));
322359
});
323360

324361
describe('with an is-open attribute', function() {

src/tooltip/tooltip.js

+23-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* function, placement as a function, inside, support for more triggers than
44
* just mouse enter/leave, html tooltips, and selector delegation.
55
*/
6-
angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position'])
6+
angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
77

88
/**
99
* The $tooltip service creates tooltip- and popover-like directives as well as
@@ -66,7 +66,19 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position'])
6666
* Returns the actual instance of the $tooltip service.
6767
* TODO support multiple triggers
6868
*/
69-
this.$get = ['$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', '$parse', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse) {
69+
this.$get = ['$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
70+
var openedTooltips = $$stackedMap.createNew();
71+
$document.on('keypress', function(e) {
72+
if (e.which === 27) {
73+
var last = openedTooltips.top();
74+
if (last) {
75+
last.value.close();
76+
openedTooltips.removeTop();
77+
last = null;
78+
}
79+
}
80+
});
81+
7082
return function $tooltip(type, prefix, defaultTriggerShow, options) {
7183
options = angular.extend({}, defaultOptions, globalOptions, options);
7284

@@ -166,6 +178,9 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position'])
166178
// By default, the tooltip is not open.
167179
// TODO add ability to start tooltip opened
168180
ttScope.isOpen = false;
181+
openedTooltips.add(ttScope, {
182+
close: hideTooltipBind
183+
});
169184

170185
function toggleTooltipBind() {
171186
if (!ttScope.isOpen) {
@@ -413,6 +428,12 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position'])
413428
element[0].addEventListener(trigger, hideTooltipBind);
414429
});
415430
}
431+
432+
element.on('keypress', function(e) {
433+
if (e.which === 27) {
434+
hideTooltipBind();
435+
}
436+
});
416437
});
417438
}
418439
}

0 commit comments

Comments
 (0)