Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(select): Allow non-english characters for keyboard selection. (#8893
Browse files Browse the repository at this point in the history
)

Our previous method for checking whether or not the typed character
matched the first letter of an `<md-option>` was constrained to the
letters `[a-zA-Z]`.

Fix by allowing more characters and checking the validity via the
`String.fromCharCode()` method.

Add tests for Spanish and Simplified Chinese, as well as not swallowing
certain keys like function keys (F3), meta, ctrl and comma (`,`).

Fixes #7730.
  • Loading branch information
topherfangio authored and mmalerba committed Feb 23, 2017
1 parent f0facb2 commit 89538d6
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 6 deletions.
13 changes: 11 additions & 2 deletions src/components/select/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $
e.preventDefault();
openSelect(e);
} else {
if ($mdConstant.isInputKey(e) || $mdConstant.isNumPadKey(e)) {
if (shouldHandleKey(e, $mdConstant)) {
e.preventDefault();

var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
Expand Down Expand Up @@ -1396,7 +1396,7 @@ function SelectProvider($$interimElementProvider) {
$mdUtil.nextTick($mdSelect.hide, true);
break;
default:
if ($mdConstant.isInputKey(ev) || $mdConstant.isNumPadKey(ev)) {
if (shouldHandleKey(ev, $mdConstant)) {
var optNode = dropDown.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
opts.focusedNode = optNode || opts.focusedNode;
optNode && optNode.focus();
Expand Down Expand Up @@ -1673,4 +1673,13 @@ function SelectProvider($$interimElementProvider) {
}
return isScrollable;
}

}

function shouldHandleKey(ev, $mdConstant) {
var char = String.fromCharCode(ev.keyCode);
var isNonUsefulKey = (ev.keyCode <= 31);

return (char && char.length && !isNonUsefulKey &&
!$mdConstant.isMetaKey(ev) && !$mdConstant.isFnLockKey(ev) && !$mdConstant.hasModifierKey(ev));
}
64 changes: 61 additions & 3 deletions src/components/select/select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,62 @@ describe('<md-select>', function() {
expect($rootScope.someModel).toBe(2);
});

it('supports typing non-english option names', inject(function($document, $rootScope) {
var words = ['algebra', 'álgebra'];
var el = setupSelect('ng-model="someModel"', words).find('md-select');

pressKey(el, words[1].charCodeAt(0));
expect($rootScope.someModel).toBe(words[1]);
}));

it('supports typing unicode option names', inject(function($document, $rootScope) {
var words = ['algebra', '太阳'];
var el = setupSelect('ng-model="someModel"', words).find('md-select');

pressKey(el, words[1].charCodeAt(0));
expect($rootScope.someModel).toBe(words[1]);
}));

// Note, this test is designed to check the shouldHandleKey() method which is the default
// method if the keypress doesn't match one of the KNOWN keys such as up/down/enter/escape/etc.
it('does not swallow useful keys (fn, arrow, etc)', function() {
var keyCodes = [17, 92, 113]; // ctrl, comma (`,`), and F3
var customEvent = {
type: 'keydown',
preventDefault: jasmine.createSpy('preventDefault')
};

var words = ['algebra', 'math', 'science'];
var el = setupSelect('ng-model="someModel"', words).find('md-select');

keyCodes.forEach(function(code) {
customEvent.keyCode = code;
pressKey(el, null, customEvent);
expect(customEvent.preventDefault).not.toHaveBeenCalled();
});
});

it('does not swallow modifier keys', function() {
var customEvent = {
type: 'keydown',
preventDefault: jasmine.createSpy('preventDefault')
};

var words = ['algebra', 'math', 'science'];
var el = setupSelect('ng-model="someModel"', words).find('md-select');

customEvent.keyCode = 70;
customEvent.ctrlKey = true;
pressKey(el, null, customEvent);
expect(customEvent.preventDefault).not.toHaveBeenCalled();

customEvent.keyCode = 82;
customEvent.ctrlKey = false;
customEvent.metaKey = true;
pressKey(el, null, customEvent);
expect(customEvent.preventDefault).not.toHaveBeenCalled();
});

it('disallows selection of disabled options', function() {
var optsTemplate =
'<md-option value="1">1</md-option>' +
Expand Down Expand Up @@ -1385,11 +1441,13 @@ describe('<md-select>', function() {
}


function pressKey(el, code) {
el.triggerHandler({
function pressKey(el, code, customEvent) {
var event = customEvent || {
type: 'keydown',
keyCode: code
});
};

el.triggerHandler(event);
}

function clickOption(select, index) {
Expand Down
7 changes: 6 additions & 1 deletion src/core/util/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ function MdConstantFactory() {

var self = {
isInputKey : function(e) { return (e.keyCode >= 31 && e.keyCode <= 90); },
isNumPadKey : function (e){ return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },
isNumPadKey : function(e) { return (3 === e.location && e.keyCode >= 97 && e.keyCode <= 105); },
isMetaKey: function(e) { return (e.keyCode >= 91 && e.keyCode <= 93); },
isFnLockKey: function(e) { return (e.keyCode >= 112 && e.keyCode <= 145); },
isNavigationKey : function(e) {
var kc = self.KEY_CODE, NAVIGATION_KEYS = [kc.SPACE, kc.ENTER, kc.UP_ARROW, kc.DOWN_ARROW];
return (NAVIGATION_KEYS.indexOf(e.keyCode) != -1);
},
hasModifierKey: function(e) {
return e.ctrlKey || e.metaKey || e.altKey;
},

/**
* Maximum size, in pixels, that can be explicitly set to an element. The actual value varies
Expand Down

0 comments on commit 89538d6

Please sign in to comment.