Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 289374a

Browse files
Eblaxgkalpak
authored andcommitted
fix(aria/ngClick): check if element is contenteditable before blocking spacebar
`ngAria`'s `ngClick` blocks spacebar keypresses on non-blacklisted elements, which is an issue when the element is `contenteditable`. Closes #16762
1 parent 3b333f3 commit 289374a

File tree

2 files changed

+110
-21
lines changed

2 files changed

+110
-21
lines changed

src/ngAria/aria.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
390390

391391
if (keyCode === 13 || keyCode === 32) {
392392
// If the event is triggered on a non-interactive element ...
393-
if (nodeBlackList.indexOf(event.target.nodeName) === -1) {
393+
if (nodeBlackList.indexOf(event.target.nodeName) === -1 && !event.target.isContentEditable) {
394394
// ... prevent the default browser behavior (e.g. scrolling when pressing spacebar)
395395
// See https://github.com/angular/angular.js/issues/16664
396396
event.preventDefault();

test/ngAria/ariaSpec.js

+109-20
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,115 @@ describe('$aria', function() {
954954
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
955955
});
956956

957+
it('should trigger a click in browsers that provide `event.which` instead of `event.keyCode`',
958+
function() {
959+
compileElement(
960+
'<section>' +
961+
'<div ng-click="onClick($event)"></div>' +
962+
'<ul><li ng-click="onClick($event)"></li></ul>' +
963+
'</section>');
964+
965+
var divElement = element.find('div');
966+
var liElement = element.find('li');
967+
968+
divElement.triggerHandler({type: 'keydown', which: 13});
969+
liElement.triggerHandler({type: 'keydown', which: 13});
970+
divElement.triggerHandler({type: 'keydown', which: 32});
971+
liElement.triggerHandler({type: 'keydown', which: 32});
972+
973+
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
974+
}
975+
);
976+
977+
it('should not prevent default keyboard action if the target element has editable content',
978+
inject(function($document) {
979+
// Note:
980+
// `contenteditable` is an enumarated (not a boolean) attribute (see
981+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable).
982+
// We need to check the following conditions:
983+
// - No attribute.
984+
// - Value: ""
985+
// - Value: "true"
986+
// - Value: "false"
987+
988+
function eventFor(keyCode) {
989+
return {bubbles: true, cancelable: true, keyCode: keyCode};
990+
}
991+
992+
compileElement(
993+
'<section>' +
994+
// No attribute.
995+
'<div id="no-attribute">' +
996+
'<div ng-click="onClick($event)"></div>' +
997+
'<ul ng-click="onClick($event)"><li></li></ul>' +
998+
'</div>' +
999+
1000+
// Value: ""
1001+
'<div id="value-empty">' +
1002+
'<div ng-click="onClick($event)" contenteditable=""></div>' +
1003+
'<ul ng-click="onClick($event)"><li contenteditable=""></li></ul>' +
1004+
'</div>' +
1005+
1006+
// Value: "true"
1007+
'<div id="value-true">' +
1008+
'<div ng-click="onClick($event)" contenteditable="true"></div>' +
1009+
'<ul ng-click="onClick($event)"><li contenteditable="true"></li></ul>' +
1010+
'</div>' +
1011+
1012+
// Value: "false"
1013+
'<div id="value-false">' +
1014+
'<div ng-click="onClick($event)" contenteditable="false"></div>' +
1015+
'<ul ng-click="onClick($event)"><li contenteditable="false"></li></ul>' +
1016+
'</div>' +
1017+
'</section>');
1018+
1019+
// Support: Safari 11-12+
1020+
// Attach to DOM, because otherwise Safari will not update the `isContentEditable` property
1021+
// based on the `contenteditable` attribute.
1022+
$document.find('body').append(element);
1023+
1024+
var containers = element.children();
1025+
var container;
1026+
1027+
// Using `browserTrigger()`, because it supports event bubbling.
1028+
1029+
// No attribute | Elements are not editable.
1030+
container = containers.eq(0);
1031+
browserTrigger(container.find('div'), 'keydown', eventFor(13));
1032+
browserTrigger(container.find('ul'), 'keydown', eventFor(32));
1033+
browserTrigger(container.find('li'), 'keydown', eventFor(13));
1034+
1035+
expect(clickEvents).toEqual(['div(true)', 'ul(true)', 'li(true)']);
1036+
1037+
// Value: "" | Elements are editable.
1038+
clickEvents = [];
1039+
container = containers.eq(1);
1040+
browserTrigger(container.find('div'), 'keydown', eventFor(32));
1041+
browserTrigger(container.find('ul'), 'keydown', eventFor(13));
1042+
browserTrigger(container.find('li'), 'keydown', eventFor(32));
1043+
1044+
expect(clickEvents).toEqual(['div(false)', 'ul(true)', 'li(false)']);
1045+
1046+
// Value: "true" | Elements are editable.
1047+
clickEvents = [];
1048+
container = containers.eq(2);
1049+
browserTrigger(container.find('div'), 'keydown', eventFor(13));
1050+
browserTrigger(container.find('ul'), 'keydown', eventFor(32));
1051+
browserTrigger(container.find('li'), 'keydown', eventFor(13));
1052+
1053+
expect(clickEvents).toEqual(['div(false)', 'ul(true)', 'li(false)']);
1054+
1055+
// Value: "false" | Elements are not editable.
1056+
clickEvents = [];
1057+
container = containers.eq(3);
1058+
browserTrigger(container.find('div'), 'keydown', eventFor(32));
1059+
browserTrigger(container.find('ul'), 'keydown', eventFor(13));
1060+
browserTrigger(container.find('li'), 'keydown', eventFor(32));
1061+
1062+
expect(clickEvents).toEqual(['div(true)', 'ul(true)', 'li(true)']);
1063+
})
1064+
);
1065+
9571066
they('should not prevent default keyboard action if an interactive $type element' +
9581067
'is nested inside ng-click', nodeBlackList, function(elementType) {
9591068
function createHTML(type) {
@@ -981,26 +1090,6 @@ describe('$aria', function() {
9811090
}
9821091
);
9831092

984-
it('should trigger a click in browsers that provide `event.which` instead of `event.keyCode`',
985-
function() {
986-
compileElement(
987-
'<section>' +
988-
'<div ng-click="onClick($event)"></div>' +
989-
'<ul><li ng-click="onClick($event)"></li></ul>' +
990-
'</section>');
991-
992-
var divElement = element.find('div');
993-
var liElement = element.find('li');
994-
995-
divElement.triggerHandler({type: 'keydown', which: 13});
996-
liElement.triggerHandler({type: 'keydown', which: 13});
997-
divElement.triggerHandler({type: 'keydown', which: 32});
998-
liElement.triggerHandler({type: 'keydown', which: 32});
999-
1000-
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
1001-
}
1002-
);
1003-
10041093
they('should not bind to key events if there is existing `ng-$prop`',
10051094
['keydown', 'keypress', 'keyup'], function(eventName) {
10061095
scope.onKeyEvent = jasmine.createSpy('onKeyEvent');

0 commit comments

Comments
 (0)