Skip to content

Commit bdcf08a

Browse files
meldafertrisadams
authored andcommitted
fix(a11y): blur when tabbing out of input
Fixes #1926. To be accessible, it should be possible to easily navigate elements using the keyboard. Previously, when using TAB to navigate through form elements, the input would enter its focused state when focused with TAB (and the dropdown would open when using `openOnFocus`). However, when TAB is used to jump to the next focusable element, it would not lose its focused state, and the dropdown would stay open. This commit restores the behavior before #1813 and ensures that the input leaves its focused state and closes the dropdown when blurred using TAB. This commit also fixes some tests to ensure they are self-contained.
1 parent 20169c3 commit bdcf08a

File tree

3 files changed

+123
-14
lines changed

3 files changed

+123
-14
lines changed

src/selectize.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ $.extend(Selectize.prototype, {
218218
keypress : function() { return self.onKeyPress.apply(self, arguments); },
219219
input : function() { return self.onInput.apply(self, arguments); },
220220
resize : function() { self.positionDropdown.apply(self, []); },
221-
// blur : function() { return self.onBlur.apply(self, arguments); },
222-
focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
221+
blur : function() { return self.onBlur.apply(self, arguments); },
222+
focus : function() { return self.onFocus.apply(self, arguments); },
223223
paste : function() { return self.onPaste.apply(self, arguments); }
224224
});
225225

@@ -243,7 +243,12 @@ $.extend(Selectize.prototype, {
243243
}
244244
// blur on click outside
245245
// do not blur if the dropdown is clicked
246-
if (!self.$dropdown.has(e.target).length && e.target !== self.$control[0]) {
246+
if (self.$dropdown.has(e.target).length) {
247+
self.ignoreBlur = true;
248+
window.setTimeout(function() {
249+
self.ignoreBlur = false;
250+
}, 0);
251+
} else if (e.target !== self.$control[0]) {
247252
self.blur(e.target);
248253
}
249254
}
@@ -685,19 +690,17 @@ $.extend(Selectize.prototype, {
685690
*/
686691
onBlur: function(e, dest) {
687692
var self = this;
693+
694+
if (self.ignoreBlur) {
695+
return;
696+
}
697+
688698
if (!self.isFocused) return;
689699
self.isFocused = false;
690700

691701
if (self.ignoreFocus) {
692702
return;
693703
}
694-
// Bug fix do not blur dropdown here
695-
// else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
696-
// // necessary to prevent IE closing the dropdown when the scrollbar is clicked
697-
// self.ignoreBlur = true;
698-
// self.onFocus(e);
699-
// return;
700-
// }
701704

702705
var deactivate = function() {
703706
self.close();

test/api.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@
633633
test.selectize.search('hello');
634634
}).to.not.throw(Error);
635635
});
636-
it('should normalize a string', function () {
636+
expect('should normalize a string', function () {
637637
var test;
638638

639639
beforeEach(function() {
@@ -642,11 +642,12 @@
642642
'</select>', { normalize: true });
643643
});
644644

645-
it('should return query satinized', function() {
645+
it('should return query satinized', function(done) {
646646
var query = test.selectize.search('héllo').query;
647647

648648
window.setTimeout(function () {
649649
expect(query).to.be.equal('hello');
650+
done();
650651
}, 0);
651652
});
652653
});

test/interaction.js

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
(function() {
22

33
var click = function(el, cb) {
4-
syn.click(el).delay(350, cb);
4+
syn.click(el).delay(1, cb);
5+
};
6+
7+
var tabTo = function(elem) {
8+
// emulating keyboard tabbing using focus
9+
// TODO: it would be better to use something like puppeteer instead, then we could simulate real keyboard interactions
10+
// syn.key() is not reliable enough for tabbing
11+
elem.focus();
12+
return new Promise((resolve) => window.setTimeout(resolve));
513
};
614

715
// These tests are functional simulations of
@@ -41,7 +49,7 @@
4149
});
4250
});
4351
});
44-
52+
4553
it('should reopen dropdown if clicked after being closed by closeAfterSelect: true', function(done) {
4654
var test = setup_test('<select multiple>' +
4755
'<option value="a">A</option>' +
@@ -413,6 +421,103 @@
413421
});
414422
});
415423

424+
describe('simulate tabbing using native focus()', function() {
425+
426+
describe('defaults', function() {
427+
var test, input1, input2;
428+
429+
before(function(done) {
430+
test = setup_test('<select>' +
431+
'<option value="">No selection</option>' +
432+
'<option value="a">A</option>' +
433+
'<option value="b">B</option>' +
434+
'</select>', {});
435+
input1 = $('<input type="text" class="first">');
436+
input2 = $('<input type="text" class="last">');
437+
test.$select.parent().prepend(input1);
438+
test.$select.parent().append(input2);
439+
done();
440+
});
441+
442+
after(function() {
443+
input1.remove();
444+
input2.remove();
445+
});
446+
447+
it('should give the control focus', async function() {
448+
await tabTo(input1[0]);
449+
expect(test.selectize.isFocused).to.be.equal(false);
450+
await tabTo(test.selectize.$control_input[0]);
451+
expect(test.selectize.isFocused).to.be.equal(true);
452+
});
453+
454+
it('should remove the control focus', async function() {
455+
await tabTo(test.selectize.$control_input[0]);
456+
expect(test.selectize.isFocused).to.be.equal(true);
457+
await tabTo(input2[0]);
458+
expect(test.selectize.isFocused).to.be.equal(false);
459+
});
460+
461+
it('should open the control', async function() {
462+
await tabTo(input1[0]);
463+
expect(test.selectize.isOpen).to.be.equal(false);
464+
await tabTo(test.selectize.$control_input[0]);
465+
expect(test.selectize.isOpen).to.be.equal(true);
466+
});
467+
468+
it('should close the control', async function() {
469+
await tabTo(test.selectize.$control_input[0]);
470+
expect(test.selectize.isOpen).to.be.equal(true);
471+
await tabTo(input2[0]);
472+
expect(test.selectize.isOpen).to.be.equal(false);
473+
});
474+
475+
// TODO: this would work if tabTo was using actual keyboard interactions,
476+
// and not just focus()
477+
xit('should select the first value on blur', async function() {
478+
await tabTo(test.selectize.$control_input[0]);
479+
await tabTo(input2[0]);
480+
expect(test.selectize.getValue()).to.be.equal('a');
481+
});
482+
});
483+
484+
describe('openOnFocus is false', function() {
485+
var test, input1, input2;
486+
487+
before(function(done) {
488+
test = setup_test('<select>' +
489+
'<option value="">No selection</option>' +
490+
'<option value="a">A</option>' +
491+
'<option value="b">B</option>' +
492+
'</select>', { openOnFocus: false });
493+
input1 = $('<input type="text" class="first">');
494+
input2 = $('<input type="text" class="last">');
495+
test.$select.parent().prepend(input1);
496+
test.$select.parent().append(input2);
497+
done();
498+
});
499+
500+
after(function() {
501+
input1.remove();
502+
input2.remove();
503+
});
504+
505+
it('should give the control focus', async function() {
506+
await tabTo(input1[0]);
507+
expect(test.selectize.isFocused).to.be.equal(false);
508+
await tabTo(test.selectize.$control_input[0]);
509+
expect(test.selectize.isFocused).to.be.equal(true);
510+
});
511+
512+
it('should not open the control', async function() {
513+
await tabTo(input1[0]);
514+
expect(test.selectize.isOpen).to.be.equal(false);
515+
await tabTo(test.selectize.$control_input[0]);
516+
expect(test.selectize.isOpen).to.be.equal(false);
517+
});
518+
});
519+
});
520+
416521
});
417522

418523
})();

0 commit comments

Comments
 (0)