Skip to content

Commit 9ada371

Browse files
committed
Single select accessibility
Now the Select2 instance is correctly recognized as a combobox, and the selected option is read aloud when it is focused, just like in a standard select box. This works by generating semi-random ids that are used for the ARIA attributes. These are not intended to be consistent by any means, they are just generated to make the instance accessible by screen readers.
1 parent 8dfd6d6 commit 9ada371

10 files changed

Lines changed: 283 additions & 13 deletions

File tree

dist/js/select2.amd.full.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,11 @@ define('select2/results',[
258258
}
259259

260260
if (data.id == null) {
261-
$option.removeClass('aria-selected');
261+
$option.removeAttr('aria-selected');
262+
}
263+
264+
if (data._resultId != null) {
265+
$option.attr('id', data._resultId);
262266
}
263267

264268
$option.data('data', data);
@@ -363,6 +367,8 @@ define('select2/results',[
363367
var $next = $options.eq(nextIndex);
364368

365369
$next.trigger('mouseenter');
370+
console.log($next.offset().top, self.$results.parent().scrollTop());
371+
//self.$results.parents().scrollTop($next.offset().top);
366372
});
367373

368374
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@@ -482,13 +488,24 @@ define('select2/selection/single',[
482488

483489
SingleSelection.prototype.render = function () {
484490
var $selection = $(
485-
'<span class="single-select" tabindex="0">' +
491+
'<span class="single-select" tabindex="0" role="combobox" ' +
492+
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
486493
'<span class="rendered-selection"></span>' +
487494
'</span>'
488495
);
489496

490497
$selection.attr('title', this.$element.attr('title'));
491498

499+
var id = 'select2-container-';
500+
501+
for (var i = 0; i < 4; i++) {
502+
var r = Math.floor(Math.random() * 16);
503+
id += r.toString(16);
504+
}
505+
506+
$selection.find('.rendered-selection').attr('id', id);
507+
$selection.attr('aria-labelledby', id);
508+
492509
this.$selection = $selection;
493510

494511
return $selection;
@@ -510,6 +527,16 @@ define('select2/selection/single',[
510527
});
511528
});
512529

530+
container.on('open', function () {
531+
// When the dropdown is open, aria-expended="true"
532+
self.$selection.attr('aria-expanded', 'true');
533+
});
534+
535+
container.on('close', function () {
536+
// When the dropdown is closed, aria-expended="false"
537+
self.$selection.attr('aria-expanded', 'false');
538+
});
539+
513540
this.$selection.on('focus', function (evt) {
514541
// User focuses on the container
515542
});
@@ -564,6 +591,10 @@ define('select2/selection/single',[
564591
var formatted = this.display(selection);
565592

566593
this.$selection.find('.rendered-selection').html(formatted);
594+
595+
if (data[0]._resultId != null) {
596+
this.$selection.attr('aria-activedescendent', data[0]._resultId);
597+
}
567598
};
568599

569600
return SingleSelection;
@@ -727,6 +758,25 @@ define('select2/data/base',[
727758
// Can be implemented in subclasses
728759
};
729760

761+
BaseAdapter.prototype.generateResultId = function (data) {
762+
var id = '';
763+
764+
for (var i = 0; i < 4; i++) {
765+
var r = Math.floor(Math.random() * 16);
766+
id += r.toString(16);
767+
}
768+
769+
if (data.id != null) {
770+
id += '-' + data.id.toString();
771+
} else {
772+
for (var s = 0; s < 4; s++) {
773+
var idChar = Math.floor(Math.random() * 16);
774+
id += idChar.toString(16);
775+
}
776+
}
777+
return id;
778+
};
779+
730780
return BaseAdapter;
731781
});
732782

@@ -878,6 +928,10 @@ define('select2/data/select',[
878928
data.children = children;
879929
}
880930

931+
if (data.id) {
932+
data._resultId = this.generateResultId(data);
933+
}
934+
881935
$option.data('data', data);
882936
}
883937

dist/js/select2.amd.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,11 @@ define('select2/results',[
258258
}
259259

260260
if (data.id == null) {
261-
$option.removeClass('aria-selected');
261+
$option.removeAttr('aria-selected');
262+
}
263+
264+
if (data._resultId != null) {
265+
$option.attr('id', data._resultId);
262266
}
263267

264268
$option.data('data', data);
@@ -363,6 +367,8 @@ define('select2/results',[
363367
var $next = $options.eq(nextIndex);
364368

365369
$next.trigger('mouseenter');
370+
console.log($next.offset().top, self.$results.parent().scrollTop());
371+
//self.$results.parents().scrollTop($next.offset().top);
366372
});
367373

368374
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@@ -482,13 +488,24 @@ define('select2/selection/single',[
482488

483489
SingleSelection.prototype.render = function () {
484490
var $selection = $(
485-
'<span class="single-select" tabindex="0">' +
491+
'<span class="single-select" tabindex="0" role="combobox" ' +
492+
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
486493
'<span class="rendered-selection"></span>' +
487494
'</span>'
488495
);
489496

490497
$selection.attr('title', this.$element.attr('title'));
491498

499+
var id = 'select2-container-';
500+
501+
for (var i = 0; i < 4; i++) {
502+
var r = Math.floor(Math.random() * 16);
503+
id += r.toString(16);
504+
}
505+
506+
$selection.find('.rendered-selection').attr('id', id);
507+
$selection.attr('aria-labelledby', id);
508+
492509
this.$selection = $selection;
493510

494511
return $selection;
@@ -510,6 +527,16 @@ define('select2/selection/single',[
510527
});
511528
});
512529

530+
container.on('open', function () {
531+
// When the dropdown is open, aria-expended="true"
532+
self.$selection.attr('aria-expanded', 'true');
533+
});
534+
535+
container.on('close', function () {
536+
// When the dropdown is closed, aria-expended="false"
537+
self.$selection.attr('aria-expanded', 'false');
538+
});
539+
513540
this.$selection.on('focus', function (evt) {
514541
// User focuses on the container
515542
});
@@ -564,6 +591,10 @@ define('select2/selection/single',[
564591
var formatted = this.display(selection);
565592

566593
this.$selection.find('.rendered-selection').html(formatted);
594+
595+
if (data[0]._resultId != null) {
596+
this.$selection.attr('aria-activedescendent', data[0]._resultId);
597+
}
567598
};
568599

569600
return SingleSelection;
@@ -727,6 +758,25 @@ define('select2/data/base',[
727758
// Can be implemented in subclasses
728759
};
729760

761+
BaseAdapter.prototype.generateResultId = function (data) {
762+
var id = '';
763+
764+
for (var i = 0; i < 4; i++) {
765+
var r = Math.floor(Math.random() * 16);
766+
id += r.toString(16);
767+
}
768+
769+
if (data.id != null) {
770+
id += '-' + data.id.toString();
771+
} else {
772+
for (var s = 0; s < 4; s++) {
773+
var idChar = Math.floor(Math.random() * 16);
774+
id += idChar.toString(16);
775+
}
776+
}
777+
return id;
778+
};
779+
730780
return BaseAdapter;
731781
});
732782

@@ -878,6 +928,10 @@ define('select2/data/select',[
878928
data.children = children;
879929
}
880930

931+
if (data.id) {
932+
data._resultId = this.generateResultId(data);
933+
}
934+
881935
$option.data('data', data);
882936
}
883937

dist/js/select2.full.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9796,7 +9796,11 @@ define('select2/results',[
97969796
}
97979797

97989798
if (data.id == null) {
9799-
$option.removeClass('aria-selected');
9799+
$option.removeAttr('aria-selected');
9800+
}
9801+
9802+
if (data._resultId != null) {
9803+
$option.attr('id', data._resultId);
98009804
}
98019805

98029806
$option.data('data', data);
@@ -9901,6 +9905,8 @@ define('select2/results',[
99019905
var $next = $options.eq(nextIndex);
99029906

99039907
$next.trigger('mouseenter');
9908+
console.log($next.offset().top, self.$results.parent().scrollTop());
9909+
//self.$results.parents().scrollTop($next.offset().top);
99049910
});
99059911

99069912
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
@@ -10020,13 +10026,24 @@ define('select2/selection/single',[
1002010026

1002110027
SingleSelection.prototype.render = function () {
1002210028
var $selection = $(
10023-
'<span class="single-select" tabindex="0">' +
10029+
'<span class="single-select" tabindex="0" role="combobox" ' +
10030+
'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
1002410031
'<span class="rendered-selection"></span>' +
1002510032
'</span>'
1002610033
);
1002710034

1002810035
$selection.attr('title', this.$element.attr('title'));
1002910036

10037+
var id = 'select2-container-';
10038+
10039+
for (var i = 0; i < 4; i++) {
10040+
var r = Math.floor(Math.random() * 16);
10041+
id += r.toString(16);
10042+
}
10043+
10044+
$selection.find('.rendered-selection').attr('id', id);
10045+
$selection.attr('aria-labelledby', id);
10046+
1003010047
this.$selection = $selection;
1003110048

1003210049
return $selection;
@@ -10048,6 +10065,16 @@ define('select2/selection/single',[
1004810065
});
1004910066
});
1005010067

10068+
container.on('open', function () {
10069+
// When the dropdown is open, aria-expended="true"
10070+
self.$selection.attr('aria-expanded', 'true');
10071+
});
10072+
10073+
container.on('close', function () {
10074+
// When the dropdown is closed, aria-expended="false"
10075+
self.$selection.attr('aria-expanded', 'false');
10076+
});
10077+
1005110078
this.$selection.on('focus', function (evt) {
1005210079
// User focuses on the container
1005310080
});
@@ -10102,6 +10129,10 @@ define('select2/selection/single',[
1010210129
var formatted = this.display(selection);
1010310130

1010410131
this.$selection.find('.rendered-selection').html(formatted);
10132+
10133+
if (data[0]._resultId != null) {
10134+
this.$selection.attr('aria-activedescendent', data[0]._resultId);
10135+
}
1010510136
};
1010610137

1010710138
return SingleSelection;
@@ -10265,6 +10296,25 @@ define('select2/data/base',[
1026510296
// Can be implemented in subclasses
1026610297
};
1026710298

10299+
BaseAdapter.prototype.generateResultId = function (data) {
10300+
var id = '';
10301+
10302+
for (var i = 0; i < 4; i++) {
10303+
var r = Math.floor(Math.random() * 16);
10304+
id += r.toString(16);
10305+
}
10306+
10307+
if (data.id != null) {
10308+
id += '-' + data.id.toString();
10309+
} else {
10310+
for (var s = 0; s < 4; s++) {
10311+
var idChar = Math.floor(Math.random() * 16);
10312+
id += idChar.toString(16);
10313+
}
10314+
}
10315+
return id;
10316+
};
10317+
1026810318
return BaseAdapter;
1026910319
});
1027010320

@@ -10416,6 +10466,10 @@ define('select2/data/select',[
1041610466
data.children = children;
1041710467
}
1041810468

10469+
if (data.id) {
10470+
data._resultId = this.generateResultId(data);
10471+
}
10472+
1041910473
$option.data('data', data);
1042010474
}
1042110475

dist/js/select2.full.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)