Skip to content

Commit cc94199

Browse files
committed
Working on accessibility
This makes quite a few changes, one of the major ones being the removal of classes for marking options as selected or selectable, and instead using the ARIA attributes which should already be present.
1 parent e601e33 commit cc94199

17 files changed

Lines changed: 774 additions & 102 deletions

File tree

dist/css/select2.css

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@
5555
margin: 0;
5656
padding: 0; }
5757
.select2-container .dropdown .results .options .option {
58-
cursor: pointer;
5958
padding: 6px;
6059
user-select: none;
6160
-webkit-user-select: none; }
61+
.select2-container .dropdown .results .options .option[aria-selected] {
62+
cursor: pointer; }
6263
.select2-container.open .dropdown {
6364
border-top: none;
6465
border-top-left-radius: 0;
@@ -115,11 +116,11 @@
115116
cursor: default;
116117
display: block;
117118
padding: 6px; }
118-
.select2-container.select2-theme-default .dropdown .results .options .option.disabled {
119+
.select2-container.select2-theme-default .dropdown .results .options .option[aria-disabled=true] {
119120
color: #666; }
120-
.select2-container.select2-theme-default .dropdown .results .options .option.selected {
121+
.select2-container.select2-theme-default .dropdown .results .options .option[aria-selected=true] {
121122
background-color: #ddd; }
122-
.select2-container.select2-theme-default .dropdown .results .options .option.highlightable.highlighted {
123+
.select2-container.select2-theme-default .dropdown .results .options .option[aria-selected].highlighted {
123124
background-color: #5897fb;
124125
color: white; }
125126

dist/css/select2.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/js/select2.amd.full.js

Lines changed: 151 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ define('select2/results',[
151151

152152
Results.prototype.render = function () {
153153
var $results = $(
154-
'<ul class="options"></ul>'
154+
'<ul class="options" role="listbox"></ul>'
155155
);
156156

157157
this.$results = $results;
@@ -191,28 +191,42 @@ define('select2/results',[
191191
return s.id.toString();
192192
});
193193

194-
self.$results.find('.option.selected').removeClass('selected');
195-
196-
var $options = self.$results.find('.option');
194+
var $options = self.$results.find('.option[aria-selected]');
197195

198196
$options.each(function () {
199197
var $option = $(this);
200198
var item = $option.data('data');
201199

202200
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
203-
$option.addClass('selected');
201+
$option.attr('aria-selected', 'true');
202+
} else {
203+
$option.attr('aria-selected', 'false');
204204
}
205205
});
206+
207+
var $selected = $options.filter('[aria-selected=true]');
208+
209+
// Check if there are any selected options
210+
if ($selected.length > 0) {
211+
// If there are selected options, highlight the first
212+
$selected.first().trigger('mouseenter');
213+
} else {
214+
// If there are no selected options, highlight the first option
215+
// in the dropdown
216+
$options.first().trigger('mouseenter');
217+
}
206218
});
207219
};
208220

209221
Results.prototype.option = function (data) {
210222
var $option = $(
211-
'<li class="option highlightable selectable"></li>'
223+
'<li class="option" role="option" aria-selected="false"></li>'
212224
);
213225

214226
if (data.children) {
215-
$option.addClass('group').removeClass('highlightable selectable');
227+
$option
228+
.addClass('group')
229+
.removeAttr('aria-selected');
216230

217231
var $label = $('<strong class="group-label"></strong>');
218232
$label.html(data.text);
@@ -238,11 +252,13 @@ define('select2/results',[
238252
}
239253

240254
if (data.disabled) {
241-
$option.removeClass('selectable highlightable').addClass('disabled');
255+
$option
256+
.removeAttr('aria-selected')
257+
.attr('aria-disabled', 'true');
242258
}
243259

244260
if (data.id == null) {
245-
$option.removeClass('selectable highlightable');
261+
$option.removeClass('aria-selected');
246262
}
247263

248264
$option.data('data', data);
@@ -274,11 +290,40 @@ define('select2/results',[
274290
self.setClasses();
275291
});
276292

277-
this.$results.on('mouseup', '.option.selectable', function (evt) {
293+
container.on('open', function () {
294+
// When the dropdown is open, aria-expended="true"
295+
self.$results.attr('aria-expanded', 'true');
296+
297+
self.setClasses();
298+
});
299+
300+
container.on('close', function () {
301+
// When the dropdown is closed, aria-expended="false"
302+
self.$results.attr('aria-expanded', 'false');
303+
});
304+
305+
container.on('results:select', function () {
306+
var $highlighted = self.$results.find('.highlighted');
307+
308+
var data = $highlighted.data('data');
309+
310+
if ($highlighted.attr('aria-selected') == 'true') {
311+
self.trigger('unselected', {
312+
data: data
313+
});
314+
} else {
315+
self.trigger('selected', {
316+
data: data
317+
});
318+
}
319+
});
320+
321+
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
278322
var $this = $(this);
279323

280324
var data = $this.data('data');
281-
if ($this.hasClass('selected')) {
325+
326+
if ($this.attr('aria-selected') === 'true') {
282327
self.trigger('unselected', {
283328
originalEvent: evt,
284329
data: data
@@ -293,7 +338,7 @@ define('select2/results',[
293338
});
294339
});
295340

296-
this.$results.on('mouseenter', '.option.highlightable', function (evt) {
341+
this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
297342
self.$results.find('.option.highlighted').removeClass('highlighted');
298343
$(this).addClass('highlighted');
299344
});
@@ -337,10 +382,51 @@ define('select2/selection/base',[
337382
return BaseSelection;
338383
});
339384

385+
define('select2/keys',[
386+
387+
], function () {
388+
var KEYS = {
389+
BACKSPACE: 8,
390+
TAB: 9,
391+
ENTER: 13,
392+
SHIFT: 16,
393+
CTRL: 17,
394+
ALT: 18,
395+
ESC: 27,
396+
SPACE: 32,
397+
PAGE_UP: 33,
398+
PAGE_DOWN: 34,
399+
END: 35,
400+
HOME: 36,
401+
LEFT: 37,
402+
UP: 38,
403+
RIGHT: 39,
404+
DOWN: 40,
405+
DELETE: 46,
406+
407+
isArrow: function (k) {
408+
k = k.which ? k.which : k;
409+
410+
switch (k) {
411+
case KEY.LEFT:
412+
case KEY.RIGHT:
413+
case KEY.UP:
414+
case KEY.DOWN:
415+
return true;
416+
}
417+
418+
return false;
419+
}
420+
};
421+
422+
return KEYS;
423+
});
424+
340425
define('select2/selection/single',[
341426
'./base',
342-
'../utils'
343-
], function (BaseSelection, Utils) {
427+
'../utils',
428+
'../keys'
429+
], function (BaseSelection, Utils, KEYS) {
344430
function SingleSelection () {
345431
SingleSelection.__super__.constructor.apply(this, arguments);
346432
}
@@ -349,11 +435,13 @@ define('select2/selection/single',[
349435

350436
SingleSelection.prototype.render = function () {
351437
var $selection = $(
352-
'<span class="single-select">' +
438+
'<span class="single-select" tabindex="0">' +
353439
'<span class="rendered-selection"></span>' +
354440
'</span>'
355441
);
356442

443+
$selection.attr('title', this.$element.attr('title'));
444+
357445
this.$selection = $selection;
358446

359447
return $selection;
@@ -375,6 +463,28 @@ define('select2/selection/single',[
375463
});
376464
});
377465

466+
this.$selection.on('focus', function (evt) {
467+
// User focuses on the container
468+
});
469+
470+
this.$selection.on('blur', function (evt) {
471+
// User exits the container
472+
});
473+
474+
this.$selection.on('keyup', function (evt) {
475+
var key = evt.which;
476+
477+
if (container.isOpen()) {
478+
if (key == KEYS.ENTER) {
479+
self.trigger('results:select');
480+
}
481+
} else {
482+
if (key == KEYS.ENTER || key == KEYS.SPACE) {
483+
self.trigger('open');
484+
}
485+
}
486+
});
487+
378488
container.on('selection:update', function (params) {
379489
self.update(params.data);
380490
});
@@ -468,7 +578,7 @@ define('select2/selection/multiple',[
468578
MultipleSelection.prototype.selectionContainer = function () {
469579
var $container = $(
470580
'<li class="choice">' +
471-
'<span class="remove">&times;</span>' +
581+
'<span class="remove" role="presentation">&times;</span>' +
472582
'</li>'
473583
);
474584

@@ -898,7 +1008,7 @@ define('select2/dropdown/search',[
8981008

8991009
var $search = $(
9001010
'<span class="search">' +
901-
'<input type="search" name="search" />' +
1011+
'<input type="search" name="search" tabindex="-1" role="textbox" />' +
9021012
'</span>'
9031013
);
9041014

@@ -921,6 +1031,14 @@ define('select2/dropdown/search',[
9211031
});
9221032
});
9231033

1034+
container.on('open', function () {
1035+
self.$search.attr('tabindex', 0);
1036+
});
1037+
1038+
container.on('close', function () {
1039+
self.$search.attr('tabindex', -1);
1040+
});
1041+
9241042
container.on('results:all', function (params) {
9251043
if (params.query.term == null || params.query.term === '') {
9261044
var showSearch = self.showSearch(params);
@@ -1108,10 +1226,20 @@ define('select2/core',[
11081226
});
11091227
});
11101228

1229+
this.selection.on('open', function () {
1230+
self.trigger('open');
1231+
});
1232+
this.selection.on('close', function () {
1233+
self.trigger('close');
1234+
});
11111235
this.selection.on('toggle', function () {
11121236
self.toggleDropdown();
11131237
});
11141238

1239+
this.selection.on('results:select', function () {
1240+
self.trigger('results:select');
1241+
});
1242+
11151243
this.selection.on('unselected', function (params) {
11161244
self.trigger('unselect', params);
11171245

@@ -1160,18 +1288,23 @@ define('select2/core',[
11601288
// Hide the original select
11611289

11621290
$element.hide();
1291+
$element.attr('tabindex', '-1');
11631292
};
11641293

11651294
Utils.Extend(Select2, Utils.Observable);
11661295

11671296
Select2.prototype.toggleDropdown = function () {
1168-
if (this.$container.hasClass('open')) {
1297+
if (this.isOpen()) {
11691298
this.trigger('close');
11701299
} else {
11711300
this.trigger('open');
11721301
}
11731302
};
11741303

1304+
Select2.prototype.isOpen = function () {
1305+
return this.$container.hasClass('open');
1306+
};
1307+
11751308
Select2.prototype.render = function () {
11761309
var $container = $(
11771310
'<span class="select2 select2-container select2-theme-default">' +

0 commit comments

Comments
 (0)