1
0
mirror of synced 2024-11-29 16:16:02 +03:00

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.
This commit is contained in:
Kevin Brown 2014-10-18 10:49:51 -04:00
parent e601e33ff3
commit cc9419928e
17 changed files with 774 additions and 102 deletions

View File

@ -55,10 +55,11 @@
margin: 0; margin: 0;
padding: 0; } padding: 0; }
.select2-container .dropdown .results .options .option { .select2-container .dropdown .results .options .option {
cursor: pointer;
padding: 6px; padding: 6px;
user-select: none; user-select: none;
-webkit-user-select: none; } -webkit-user-select: none; }
.select2-container .dropdown .results .options .option[aria-selected] {
cursor: pointer; }
.select2-container.open .dropdown { .select2-container.open .dropdown {
border-top: none; border-top: none;
border-top-left-radius: 0; border-top-left-radius: 0;
@ -115,11 +116,11 @@
cursor: default; cursor: default;
display: block; display: block;
padding: 6px; } padding: 6px; }
.select2-container.select2-theme-default .dropdown .results .options .option.disabled { .select2-container.select2-theme-default .dropdown .results .options .option[aria-disabled=true] {
color: #666; } color: #666; }
.select2-container.select2-theme-default .dropdown .results .options .option.selected { .select2-container.select2-theme-default .dropdown .results .options .option[aria-selected=true] {
background-color: #ddd; } background-color: #ddd; }
.select2-container.select2-theme-default .dropdown .results .options .option.highlightable.highlighted { .select2-container.select2-theme-default .dropdown .results .options .option[aria-selected].highlighted {
background-color: #5897fb; background-color: #5897fb;
color: white; } color: white; }

File diff suppressed because one or more lines are too long

View File

@ -151,7 +151,7 @@ define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="options"></ul>' '<ul class="options" role="listbox"></ul>'
); );
this.$results = $results; this.$results = $results;
@ -191,28 +191,42 @@ define('select2/results',[
return s.id.toString(); return s.id.toString();
}); });
self.$results.find('.option.selected').removeClass('selected'); var $options = self.$results.find('.option[aria-selected]');
var $options = self.$results.find('.option');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
var item = $option.data('data'); var item = $option.data('data');
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) { if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
$option.addClass('selected'); $option.attr('aria-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
} }
}); });
var $selected = $options.filter('[aria-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
// If there are selected options, highlight the first
$selected.first().trigger('mouseenter');
} else {
// If there are no selected options, highlight the first option
// in the dropdown
$options.first().trigger('mouseenter');
}
}); });
}; };
Results.prototype.option = function (data) { Results.prototype.option = function (data) {
var $option = $( var $option = $(
'<li class="option highlightable selectable"></li>' '<li class="option" role="option" aria-selected="false"></li>'
); );
if (data.children) { if (data.children) {
$option.addClass('group').removeClass('highlightable selectable'); $option
.addClass('group')
.removeAttr('aria-selected');
var $label = $('<strong class="group-label"></strong>'); var $label = $('<strong class="group-label"></strong>');
$label.html(data.text); $label.html(data.text);
@ -238,11 +252,13 @@ define('select2/results',[
} }
if (data.disabled) { if (data.disabled) {
$option.removeClass('selectable highlightable').addClass('disabled'); $option
.removeAttr('aria-selected')
.attr('aria-disabled', 'true');
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('selectable highlightable'); $option.removeClass('aria-selected');
} }
$option.data('data', data); $option.data('data', data);
@ -274,11 +290,40 @@ define('select2/results',[
self.setClasses(); self.setClasses();
}); });
this.$results.on('mouseup', '.option.selectable', function (evt) { container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$results.attr('aria-expanded', 'true');
self.setClasses();
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$results.attr('aria-expanded', 'false');
});
container.on('results:select', function () {
var $highlighted = self.$results.find('.highlighted');
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
self.trigger('unselected', {
data: data
});
} else {
self.trigger('selected', {
data: data
});
}
});
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.hasClass('selected')) {
if ($this.attr('aria-selected') === 'true') {
self.trigger('unselected', { self.trigger('unselected', {
originalEvent: evt, originalEvent: evt,
data: data data: data
@ -293,7 +338,7 @@ define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.option.highlightable', function (evt) { this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
self.$results.find('.option.highlighted').removeClass('highlighted'); self.$results.find('.option.highlighted').removeClass('highlighted');
$(this).addClass('highlighted'); $(this).addClass('highlighted');
}); });
@ -337,10 +382,51 @@ define('select2/selection/base',[
return BaseSelection; return BaseSelection;
}); });
define('select2/keys',[
], function () {
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
}
};
return KEYS;
});
define('select2/selection/single',[ define('select2/selection/single',[
'./base', './base',
'../utils' '../utils',
], function (BaseSelection, Utils) { '../keys'
], function (BaseSelection, Utils, KEYS) {
function SingleSelection () { function SingleSelection () {
SingleSelection.__super__.constructor.apply(this, arguments); SingleSelection.__super__.constructor.apply(this, arguments);
} }
@ -349,11 +435,13 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select">' + '<span class="single-select" tabindex="0">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title'));
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -375,6 +463,28 @@ define('select2/selection/single',[
}); });
}); });
this.$selection.on('focus', function (evt) {
// User focuses on the container
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
this.$selection.on('keyup', function (evt) {
var key = evt.which;
if (container.isOpen()) {
if (key == KEYS.ENTER) {
self.trigger('results:select');
}
} else {
if (key == KEYS.ENTER || key == KEYS.SPACE) {
self.trigger('open');
}
}
});
container.on('selection:update', function (params) { container.on('selection:update', function (params) {
self.update(params.data); self.update(params.data);
}); });
@ -468,7 +578,7 @@ define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="choice">' + '<li class="choice">' +
'<span class="remove">&times;</span>' + '<span class="remove" role="presentation">&times;</span>' +
'</li>' '</li>'
); );
@ -898,7 +1008,7 @@ define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="search">' + '<span class="search">' +
'<input type="search" name="search" />' + '<input type="search" name="search" tabindex="-1" role="textbox" />' +
'</span>' '</span>'
); );
@ -921,6 +1031,14 @@ define('select2/dropdown/search',[
}); });
}); });
container.on('open', function () {
self.$search.attr('tabindex', 0);
});
container.on('close', function () {
self.$search.attr('tabindex', -1);
});
container.on('results:all', function (params) { container.on('results:all', function (params) {
if (params.query.term == null || params.query.term === '') { if (params.query.term == null || params.query.term === '') {
var showSearch = self.showSearch(params); var showSearch = self.showSearch(params);
@ -1108,10 +1226,20 @@ define('select2/core',[
}); });
}); });
this.selection.on('open', function () {
self.trigger('open');
});
this.selection.on('close', function () {
self.trigger('close');
});
this.selection.on('toggle', function () { this.selection.on('toggle', function () {
self.toggleDropdown(); self.toggleDropdown();
}); });
this.selection.on('results:select', function () {
self.trigger('results:select');
});
this.selection.on('unselected', function (params) { this.selection.on('unselected', function (params) {
self.trigger('unselect', params); self.trigger('unselect', params);
@ -1160,18 +1288,23 @@ define('select2/core',[
// Hide the original select // Hide the original select
$element.hide(); $element.hide();
$element.attr('tabindex', '-1');
}; };
Utils.Extend(Select2, Utils.Observable); Utils.Extend(Select2, Utils.Observable);
Select2.prototype.toggleDropdown = function () { Select2.prototype.toggleDropdown = function () {
if (this.$container.hasClass('open')) { if (this.isOpen()) {
this.trigger('close'); this.trigger('close');
} else { } else {
this.trigger('open'); this.trigger('open');
} }
}; };
Select2.prototype.isOpen = function () {
return this.$container.hasClass('open');
};
Select2.prototype.render = function () { Select2.prototype.render = function () {
var $container = $( var $container = $(
'<span class="select2 select2-container select2-theme-default">' + '<span class="select2 select2-container select2-theme-default">' +

169
dist/js/select2.amd.js vendored
View File

@ -151,7 +151,7 @@ define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="options"></ul>' '<ul class="options" role="listbox"></ul>'
); );
this.$results = $results; this.$results = $results;
@ -191,28 +191,42 @@ define('select2/results',[
return s.id.toString(); return s.id.toString();
}); });
self.$results.find('.option.selected').removeClass('selected'); var $options = self.$results.find('.option[aria-selected]');
var $options = self.$results.find('.option');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
var item = $option.data('data'); var item = $option.data('data');
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) { if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
$option.addClass('selected'); $option.attr('aria-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
} }
}); });
var $selected = $options.filter('[aria-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
// If there are selected options, highlight the first
$selected.first().trigger('mouseenter');
} else {
// If there are no selected options, highlight the first option
// in the dropdown
$options.first().trigger('mouseenter');
}
}); });
}; };
Results.prototype.option = function (data) { Results.prototype.option = function (data) {
var $option = $( var $option = $(
'<li class="option highlightable selectable"></li>' '<li class="option" role="option" aria-selected="false"></li>'
); );
if (data.children) { if (data.children) {
$option.addClass('group').removeClass('highlightable selectable'); $option
.addClass('group')
.removeAttr('aria-selected');
var $label = $('<strong class="group-label"></strong>'); var $label = $('<strong class="group-label"></strong>');
$label.html(data.text); $label.html(data.text);
@ -238,11 +252,13 @@ define('select2/results',[
} }
if (data.disabled) { if (data.disabled) {
$option.removeClass('selectable highlightable').addClass('disabled'); $option
.removeAttr('aria-selected')
.attr('aria-disabled', 'true');
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('selectable highlightable'); $option.removeClass('aria-selected');
} }
$option.data('data', data); $option.data('data', data);
@ -274,11 +290,40 @@ define('select2/results',[
self.setClasses(); self.setClasses();
}); });
this.$results.on('mouseup', '.option.selectable', function (evt) { container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$results.attr('aria-expanded', 'true');
self.setClasses();
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$results.attr('aria-expanded', 'false');
});
container.on('results:select', function () {
var $highlighted = self.$results.find('.highlighted');
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
self.trigger('unselected', {
data: data
});
} else {
self.trigger('selected', {
data: data
});
}
});
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.hasClass('selected')) {
if ($this.attr('aria-selected') === 'true') {
self.trigger('unselected', { self.trigger('unselected', {
originalEvent: evt, originalEvent: evt,
data: data data: data
@ -293,7 +338,7 @@ define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.option.highlightable', function (evt) { this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
self.$results.find('.option.highlighted').removeClass('highlighted'); self.$results.find('.option.highlighted').removeClass('highlighted');
$(this).addClass('highlighted'); $(this).addClass('highlighted');
}); });
@ -337,10 +382,51 @@ define('select2/selection/base',[
return BaseSelection; return BaseSelection;
}); });
define('select2/keys',[
], function () {
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
}
};
return KEYS;
});
define('select2/selection/single',[ define('select2/selection/single',[
'./base', './base',
'../utils' '../utils',
], function (BaseSelection, Utils) { '../keys'
], function (BaseSelection, Utils, KEYS) {
function SingleSelection () { function SingleSelection () {
SingleSelection.__super__.constructor.apply(this, arguments); SingleSelection.__super__.constructor.apply(this, arguments);
} }
@ -349,11 +435,13 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select">' + '<span class="single-select" tabindex="0">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title'));
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -375,6 +463,28 @@ define('select2/selection/single',[
}); });
}); });
this.$selection.on('focus', function (evt) {
// User focuses on the container
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
this.$selection.on('keyup', function (evt) {
var key = evt.which;
if (container.isOpen()) {
if (key == KEYS.ENTER) {
self.trigger('results:select');
}
} else {
if (key == KEYS.ENTER || key == KEYS.SPACE) {
self.trigger('open');
}
}
});
container.on('selection:update', function (params) { container.on('selection:update', function (params) {
self.update(params.data); self.update(params.data);
}); });
@ -468,7 +578,7 @@ define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="choice">' + '<li class="choice">' +
'<span class="remove">&times;</span>' + '<span class="remove" role="presentation">&times;</span>' +
'</li>' '</li>'
); );
@ -898,7 +1008,7 @@ define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="search">' + '<span class="search">' +
'<input type="search" name="search" />' + '<input type="search" name="search" tabindex="-1" role="textbox" />' +
'</span>' '</span>'
); );
@ -921,6 +1031,14 @@ define('select2/dropdown/search',[
}); });
}); });
container.on('open', function () {
self.$search.attr('tabindex', 0);
});
container.on('close', function () {
self.$search.attr('tabindex', -1);
});
container.on('results:all', function (params) { container.on('results:all', function (params) {
if (params.query.term == null || params.query.term === '') { if (params.query.term == null || params.query.term === '') {
var showSearch = self.showSearch(params); var showSearch = self.showSearch(params);
@ -1108,10 +1226,20 @@ define('select2/core',[
}); });
}); });
this.selection.on('open', function () {
self.trigger('open');
});
this.selection.on('close', function () {
self.trigger('close');
});
this.selection.on('toggle', function () { this.selection.on('toggle', function () {
self.toggleDropdown(); self.toggleDropdown();
}); });
this.selection.on('results:select', function () {
self.trigger('results:select');
});
this.selection.on('unselected', function (params) { this.selection.on('unselected', function (params) {
self.trigger('unselect', params); self.trigger('unselect', params);
@ -1160,18 +1288,23 @@ define('select2/core',[
// Hide the original select // Hide the original select
$element.hide(); $element.hide();
$element.attr('tabindex', '-1');
}; };
Utils.Extend(Select2, Utils.Observable); Utils.Extend(Select2, Utils.Observable);
Select2.prototype.toggleDropdown = function () { Select2.prototype.toggleDropdown = function () {
if (this.$container.hasClass('open')) { if (this.isOpen()) {
this.trigger('close'); this.trigger('close');
} else { } else {
this.trigger('open'); this.trigger('open');
} }
}; };
Select2.prototype.isOpen = function () {
return this.$container.hasClass('open');
};
Select2.prototype.render = function () { Select2.prototype.render = function () {
var $container = $( var $container = $(
'<span class="select2 select2-container select2-theme-default">' + '<span class="select2 select2-container select2-theme-default">' +

View File

@ -9689,7 +9689,7 @@ define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="options"></ul>' '<ul class="options" role="listbox"></ul>'
); );
this.$results = $results; this.$results = $results;
@ -9729,28 +9729,42 @@ define('select2/results',[
return s.id.toString(); return s.id.toString();
}); });
self.$results.find('.option.selected').removeClass('selected'); var $options = self.$results.find('.option[aria-selected]');
var $options = self.$results.find('.option');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
var item = $option.data('data'); var item = $option.data('data');
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) { if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
$option.addClass('selected'); $option.attr('aria-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
} }
}); });
var $selected = $options.filter('[aria-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
// If there are selected options, highlight the first
$selected.first().trigger('mouseenter');
} else {
// If there are no selected options, highlight the first option
// in the dropdown
$options.first().trigger('mouseenter');
}
}); });
}; };
Results.prototype.option = function (data) { Results.prototype.option = function (data) {
var $option = $( var $option = $(
'<li class="option highlightable selectable"></li>' '<li class="option" role="option" aria-selected="false"></li>'
); );
if (data.children) { if (data.children) {
$option.addClass('group').removeClass('highlightable selectable'); $option
.addClass('group')
.removeAttr('aria-selected');
var $label = $('<strong class="group-label"></strong>'); var $label = $('<strong class="group-label"></strong>');
$label.html(data.text); $label.html(data.text);
@ -9776,11 +9790,13 @@ define('select2/results',[
} }
if (data.disabled) { if (data.disabled) {
$option.removeClass('selectable highlightable').addClass('disabled'); $option
.removeAttr('aria-selected')
.attr('aria-disabled', 'true');
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('selectable highlightable'); $option.removeClass('aria-selected');
} }
$option.data('data', data); $option.data('data', data);
@ -9812,11 +9828,40 @@ define('select2/results',[
self.setClasses(); self.setClasses();
}); });
this.$results.on('mouseup', '.option.selectable', function (evt) { container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$results.attr('aria-expanded', 'true');
self.setClasses();
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$results.attr('aria-expanded', 'false');
});
container.on('results:select', function () {
var $highlighted = self.$results.find('.highlighted');
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
self.trigger('unselected', {
data: data
});
} else {
self.trigger('selected', {
data: data
});
}
});
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.hasClass('selected')) {
if ($this.attr('aria-selected') === 'true') {
self.trigger('unselected', { self.trigger('unselected', {
originalEvent: evt, originalEvent: evt,
data: data data: data
@ -9831,7 +9876,7 @@ define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.option.highlightable', function (evt) { this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
self.$results.find('.option.highlighted').removeClass('highlighted'); self.$results.find('.option.highlighted').removeClass('highlighted');
$(this).addClass('highlighted'); $(this).addClass('highlighted');
}); });
@ -9875,10 +9920,51 @@ define('select2/selection/base',[
return BaseSelection; return BaseSelection;
}); });
define('select2/keys',[
], function () {
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
}
};
return KEYS;
});
define('select2/selection/single',[ define('select2/selection/single',[
'./base', './base',
'../utils' '../utils',
], function (BaseSelection, Utils) { '../keys'
], function (BaseSelection, Utils, KEYS) {
function SingleSelection () { function SingleSelection () {
SingleSelection.__super__.constructor.apply(this, arguments); SingleSelection.__super__.constructor.apply(this, arguments);
} }
@ -9887,11 +9973,13 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select">' + '<span class="single-select" tabindex="0">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title'));
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -9913,6 +10001,28 @@ define('select2/selection/single',[
}); });
}); });
this.$selection.on('focus', function (evt) {
// User focuses on the container
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
this.$selection.on('keyup', function (evt) {
var key = evt.which;
if (container.isOpen()) {
if (key == KEYS.ENTER) {
self.trigger('results:select');
}
} else {
if (key == KEYS.ENTER || key == KEYS.SPACE) {
self.trigger('open');
}
}
});
container.on('selection:update', function (params) { container.on('selection:update', function (params) {
self.update(params.data); self.update(params.data);
}); });
@ -10006,7 +10116,7 @@ define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="choice">' + '<li class="choice">' +
'<span class="remove">&times;</span>' + '<span class="remove" role="presentation">&times;</span>' +
'</li>' '</li>'
); );
@ -10436,7 +10546,7 @@ define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="search">' + '<span class="search">' +
'<input type="search" name="search" />' + '<input type="search" name="search" tabindex="-1" role="textbox" />' +
'</span>' '</span>'
); );
@ -10459,6 +10569,14 @@ define('select2/dropdown/search',[
}); });
}); });
container.on('open', function () {
self.$search.attr('tabindex', 0);
});
container.on('close', function () {
self.$search.attr('tabindex', -1);
});
container.on('results:all', function (params) { container.on('results:all', function (params) {
if (params.query.term == null || params.query.term === '') { if (params.query.term == null || params.query.term === '') {
var showSearch = self.showSearch(params); var showSearch = self.showSearch(params);
@ -10646,10 +10764,20 @@ define('select2/core',[
}); });
}); });
this.selection.on('open', function () {
self.trigger('open');
});
this.selection.on('close', function () {
self.trigger('close');
});
this.selection.on('toggle', function () { this.selection.on('toggle', function () {
self.toggleDropdown(); self.toggleDropdown();
}); });
this.selection.on('results:select', function () {
self.trigger('results:select');
});
this.selection.on('unselected', function (params) { this.selection.on('unselected', function (params) {
self.trigger('unselect', params); self.trigger('unselect', params);
@ -10698,18 +10826,23 @@ define('select2/core',[
// Hide the original select // Hide the original select
$element.hide(); $element.hide();
$element.attr('tabindex', '-1');
}; };
Utils.Extend(Select2, Utils.Observable); Utils.Extend(Select2, Utils.Observable);
Select2.prototype.toggleDropdown = function () { Select2.prototype.toggleDropdown = function () {
if (this.$container.hasClass('open')) { if (this.isOpen()) {
this.trigger('close'); this.trigger('close');
} else { } else {
this.trigger('open'); this.trigger('open');
} }
}; };
Select2.prototype.isOpen = function () {
return this.$container.hasClass('open');
};
Select2.prototype.render = function () { Select2.prototype.render = function () {
var $container = $( var $container = $(
'<span class="select2 select2-container select2-theme-default">' + '<span class="select2 select2-container select2-theme-default">' +

File diff suppressed because one or more lines are too long

169
dist/js/select2.js vendored
View File

@ -580,7 +580,7 @@ define('select2/results',[
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="options"></ul>' '<ul class="options" role="listbox"></ul>'
); );
this.$results = $results; this.$results = $results;
@ -620,28 +620,42 @@ define('select2/results',[
return s.id.toString(); return s.id.toString();
}); });
self.$results.find('.option.selected').removeClass('selected'); var $options = self.$results.find('.option[aria-selected]');
var $options = self.$results.find('.option');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
var item = $option.data('data'); var item = $option.data('data');
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) { if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
$option.addClass('selected'); $option.attr('aria-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
} }
}); });
var $selected = $options.filter('[aria-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
// If there are selected options, highlight the first
$selected.first().trigger('mouseenter');
} else {
// If there are no selected options, highlight the first option
// in the dropdown
$options.first().trigger('mouseenter');
}
}); });
}; };
Results.prototype.option = function (data) { Results.prototype.option = function (data) {
var $option = $( var $option = $(
'<li class="option highlightable selectable"></li>' '<li class="option" role="option" aria-selected="false"></li>'
); );
if (data.children) { if (data.children) {
$option.addClass('group').removeClass('highlightable selectable'); $option
.addClass('group')
.removeAttr('aria-selected');
var $label = $('<strong class="group-label"></strong>'); var $label = $('<strong class="group-label"></strong>');
$label.html(data.text); $label.html(data.text);
@ -667,11 +681,13 @@ define('select2/results',[
} }
if (data.disabled) { if (data.disabled) {
$option.removeClass('selectable highlightable').addClass('disabled'); $option
.removeAttr('aria-selected')
.attr('aria-disabled', 'true');
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('selectable highlightable'); $option.removeClass('aria-selected');
} }
$option.data('data', data); $option.data('data', data);
@ -703,11 +719,40 @@ define('select2/results',[
self.setClasses(); self.setClasses();
}); });
this.$results.on('mouseup', '.option.selectable', function (evt) { container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$results.attr('aria-expanded', 'true');
self.setClasses();
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$results.attr('aria-expanded', 'false');
});
container.on('results:select', function () {
var $highlighted = self.$results.find('.highlighted');
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
self.trigger('unselected', {
data: data
});
} else {
self.trigger('selected', {
data: data
});
}
});
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.hasClass('selected')) {
if ($this.attr('aria-selected') === 'true') {
self.trigger('unselected', { self.trigger('unselected', {
originalEvent: evt, originalEvent: evt,
data: data data: data
@ -722,7 +767,7 @@ define('select2/results',[
}); });
}); });
this.$results.on('mouseenter', '.option.highlightable', function (evt) { this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
self.$results.find('.option.highlighted').removeClass('highlighted'); self.$results.find('.option.highlighted').removeClass('highlighted');
$(this).addClass('highlighted'); $(this).addClass('highlighted');
}); });
@ -766,10 +811,51 @@ define('select2/selection/base',[
return BaseSelection; return BaseSelection;
}); });
define('select2/keys',[
], function () {
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
}
};
return KEYS;
});
define('select2/selection/single',[ define('select2/selection/single',[
'./base', './base',
'../utils' '../utils',
], function (BaseSelection, Utils) { '../keys'
], function (BaseSelection, Utils, KEYS) {
function SingleSelection () { function SingleSelection () {
SingleSelection.__super__.constructor.apply(this, arguments); SingleSelection.__super__.constructor.apply(this, arguments);
} }
@ -778,11 +864,13 @@ define('select2/selection/single',[
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select">' + '<span class="single-select" tabindex="0">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title'));
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -804,6 +892,28 @@ define('select2/selection/single',[
}); });
}); });
this.$selection.on('focus', function (evt) {
// User focuses on the container
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
this.$selection.on('keyup', function (evt) {
var key = evt.which;
if (container.isOpen()) {
if (key == KEYS.ENTER) {
self.trigger('results:select');
}
} else {
if (key == KEYS.ENTER || key == KEYS.SPACE) {
self.trigger('open');
}
}
});
container.on('selection:update', function (params) { container.on('selection:update', function (params) {
self.update(params.data); self.update(params.data);
}); });
@ -897,7 +1007,7 @@ define('select2/selection/multiple',[
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="choice">' + '<li class="choice">' +
'<span class="remove">&times;</span>' + '<span class="remove" role="presentation">&times;</span>' +
'</li>' '</li>'
); );
@ -1327,7 +1437,7 @@ define('select2/dropdown/search',[
var $search = $( var $search = $(
'<span class="search">' + '<span class="search">' +
'<input type="search" name="search" />' + '<input type="search" name="search" tabindex="-1" role="textbox" />' +
'</span>' '</span>'
); );
@ -1350,6 +1460,14 @@ define('select2/dropdown/search',[
}); });
}); });
container.on('open', function () {
self.$search.attr('tabindex', 0);
});
container.on('close', function () {
self.$search.attr('tabindex', -1);
});
container.on('results:all', function (params) { container.on('results:all', function (params) {
if (params.query.term == null || params.query.term === '') { if (params.query.term == null || params.query.term === '') {
var showSearch = self.showSearch(params); var showSearch = self.showSearch(params);
@ -1537,10 +1655,20 @@ define('select2/core',[
}); });
}); });
this.selection.on('open', function () {
self.trigger('open');
});
this.selection.on('close', function () {
self.trigger('close');
});
this.selection.on('toggle', function () { this.selection.on('toggle', function () {
self.toggleDropdown(); self.toggleDropdown();
}); });
this.selection.on('results:select', function () {
self.trigger('results:select');
});
this.selection.on('unselected', function (params) { this.selection.on('unselected', function (params) {
self.trigger('unselect', params); self.trigger('unselect', params);
@ -1589,18 +1717,23 @@ define('select2/core',[
// Hide the original select // Hide the original select
$element.hide(); $element.hide();
$element.attr('tabindex', '-1');
}; };
Utils.Extend(Select2, Utils.Observable); Utils.Extend(Select2, Utils.Observable);
Select2.prototype.toggleDropdown = function () { Select2.prototype.toggleDropdown = function () {
if (this.$container.hasClass('open')) { if (this.isOpen()) {
this.trigger('close'); this.trigger('close');
} else { } else {
this.trigger('open'); this.trigger('open');
} }
}; };
Select2.prototype.isOpen = function () {
return this.$container.hasClass('open');
};
Select2.prototype.render = function () { Select2.prototype.render = function () {
var $container = $( var $container = $(
'<span class="select2 select2-container select2-theme-default">' + '<span class="select2 select2-container select2-theme-default">' +

File diff suppressed because one or more lines are too long

View File

@ -48,6 +48,10 @@ $(document).ready(function() {
<div class="col-md-4"> <div class="col-md-4">
<h1>Multiple select boxes</h1> <h1>Multiple select boxes</h1>
<p>
<select class="js-states" multiple="multiple"></select>
</p>
<p> <p>
Select2 also supports multi-value select boxes. The select below is declared with the <code>multiple</code> attribute. Select2 also supports multi-value select boxes. The select below is declared with the <code>multiple</code> attribute.
</p> </p>

View File

@ -68,10 +68,20 @@ define([
}); });
}); });
this.selection.on('open', function () {
self.trigger('open');
});
this.selection.on('close', function () {
self.trigger('close');
});
this.selection.on('toggle', function () { this.selection.on('toggle', function () {
self.toggleDropdown(); self.toggleDropdown();
}); });
this.selection.on('results:select', function () {
self.trigger('results:select');
});
this.selection.on('unselected', function (params) { this.selection.on('unselected', function (params) {
self.trigger('unselect', params); self.trigger('unselect', params);
@ -120,18 +130,23 @@ define([
// Hide the original select // Hide the original select
$element.hide(); $element.hide();
$element.attr('tabindex', '-1');
}; };
Utils.Extend(Select2, Utils.Observable); Utils.Extend(Select2, Utils.Observable);
Select2.prototype.toggleDropdown = function () { Select2.prototype.toggleDropdown = function () {
if (this.$container.hasClass('open')) { if (this.isOpen()) {
this.trigger('close'); this.trigger('close');
} else { } else {
this.trigger('open'); this.trigger('open');
} }
}; };
Select2.prototype.isOpen = function () {
return this.$container.hasClass('open');
};
Select2.prototype.render = function () { Select2.prototype.render = function () {
var $container = $( var $container = $(
'<span class="select2 select2-container select2-theme-default">' + '<span class="select2 select2-container select2-theme-default">' +

View File

@ -8,7 +8,7 @@ define([
var $search = $( var $search = $(
'<span class="search">' + '<span class="search">' +
'<input type="search" name="search" />' + '<input type="search" name="search" tabindex="-1" role="textbox" />' +
'</span>' '</span>'
); );
@ -31,6 +31,14 @@ define([
}); });
}); });
container.on('open', function () {
self.$search.attr('tabindex', 0);
});
container.on('close', function () {
self.$search.attr('tabindex', -1);
});
container.on('results:all', function (params) { container.on('results:all', function (params) {
if (params.query.term == null || params.query.term === '') { if (params.query.term == null || params.query.term === '') {
var showSearch = self.showSearch(params); var showSearch = self.showSearch(params);

39
src/js/select2/keys.js vendored Normal file
View File

@ -0,0 +1,39 @@
define([
], function () {
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
isArrow: function (k) {
k = k.which ? k.which : k;
switch (k) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
return true;
}
return false;
}
};
return KEYS;
});

View File

@ -12,7 +12,7 @@ define([
Results.prototype.render = function () { Results.prototype.render = function () {
var $results = $( var $results = $(
'<ul class="options"></ul>' '<ul class="options" role="listbox"></ul>'
); );
this.$results = $results; this.$results = $results;
@ -52,28 +52,42 @@ define([
return s.id.toString(); return s.id.toString();
}); });
self.$results.find('.option.selected').removeClass('selected'); var $options = self.$results.find('.option[aria-selected]');
var $options = self.$results.find('.option');
$options.each(function () { $options.each(function () {
var $option = $(this); var $option = $(this);
var item = $option.data('data'); var item = $option.data('data');
if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) { if (item.id != null && selectedIds.indexOf(item.id.toString()) > -1) {
$option.addClass('selected'); $option.attr('aria-selected', 'true');
} else {
$option.attr('aria-selected', 'false');
} }
}); });
var $selected = $options.filter('[aria-selected=true]');
// Check if there are any selected options
if ($selected.length > 0) {
// If there are selected options, highlight the first
$selected.first().trigger('mouseenter');
} else {
// If there are no selected options, highlight the first option
// in the dropdown
$options.first().trigger('mouseenter');
}
}); });
}; };
Results.prototype.option = function (data) { Results.prototype.option = function (data) {
var $option = $( var $option = $(
'<li class="option highlightable selectable"></li>' '<li class="option" role="option" aria-selected="false"></li>'
); );
if (data.children) { if (data.children) {
$option.addClass('group').removeClass('highlightable selectable'); $option
.addClass('group')
.removeAttr('aria-selected');
var $label = $('<strong class="group-label"></strong>'); var $label = $('<strong class="group-label"></strong>');
$label.html(data.text); $label.html(data.text);
@ -99,11 +113,13 @@ define([
} }
if (data.disabled) { if (data.disabled) {
$option.removeClass('selectable highlightable').addClass('disabled'); $option
.removeAttr('aria-selected')
.attr('aria-disabled', 'true');
} }
if (data.id == null) { if (data.id == null) {
$option.removeClass('selectable highlightable'); $option.removeClass('aria-selected');
} }
$option.data('data', data); $option.data('data', data);
@ -135,11 +151,40 @@ define([
self.setClasses(); self.setClasses();
}); });
this.$results.on('mouseup', '.option.selectable', function (evt) { container.on('open', function () {
// When the dropdown is open, aria-expended="true"
self.$results.attr('aria-expanded', 'true');
self.setClasses();
});
container.on('close', function () {
// When the dropdown is closed, aria-expended="false"
self.$results.attr('aria-expanded', 'false');
});
container.on('results:select', function () {
var $highlighted = self.$results.find('.highlighted');
var data = $highlighted.data('data');
if ($highlighted.attr('aria-selected') == 'true') {
self.trigger('unselected', {
data: data
});
} else {
self.trigger('selected', {
data: data
});
}
});
this.$results.on('mouseup', '.option[aria-selected]', function (evt) {
var $this = $(this); var $this = $(this);
var data = $this.data('data'); var data = $this.data('data');
if ($this.hasClass('selected')) {
if ($this.attr('aria-selected') === 'true') {
self.trigger('unselected', { self.trigger('unselected', {
originalEvent: evt, originalEvent: evt,
data: data data: data
@ -154,7 +199,7 @@ define([
}); });
}); });
this.$results.on('mouseenter', '.option.highlightable', function (evt) { this.$results.on('mouseenter', '.option[aria-selected]', function (evt) {
self.$results.find('.option.highlighted').removeClass('highlighted'); self.$results.find('.option.highlighted').removeClass('highlighted');
$(this).addClass('highlighted'); $(this).addClass('highlighted');
}); });

View File

@ -58,7 +58,7 @@ define([
MultipleSelection.prototype.selectionContainer = function () { MultipleSelection.prototype.selectionContainer = function () {
var $container = $( var $container = $(
'<li class="choice">' + '<li class="choice">' +
'<span class="remove">&times;</span>' + '<span class="remove" role="presentation">&times;</span>' +
'</li>' '</li>'
); );

View File

@ -1,7 +1,8 @@
define([ define([
'./base', './base',
'../utils' '../utils',
], function (BaseSelection, Utils) { '../keys'
], function (BaseSelection, Utils, KEYS) {
function SingleSelection () { function SingleSelection () {
SingleSelection.__super__.constructor.apply(this, arguments); SingleSelection.__super__.constructor.apply(this, arguments);
} }
@ -10,11 +11,13 @@ define([
SingleSelection.prototype.render = function () { SingleSelection.prototype.render = function () {
var $selection = $( var $selection = $(
'<span class="single-select">' + '<span class="single-select" tabindex="0">' +
'<span class="rendered-selection"></span>' + '<span class="rendered-selection"></span>' +
'</span>' '</span>'
); );
$selection.attr('title', this.$element.attr('title'));
this.$selection = $selection; this.$selection = $selection;
return $selection; return $selection;
@ -36,6 +39,28 @@ define([
}); });
}); });
this.$selection.on('focus', function (evt) {
// User focuses on the container
});
this.$selection.on('blur', function (evt) {
// User exits the container
});
this.$selection.on('keyup', function (evt) {
var key = evt.which;
if (container.isOpen()) {
if (key == KEYS.ENTER) {
self.trigger('results:select');
}
} else {
if (key == KEYS.ENTER || key == KEYS.SPACE) {
self.trigger('open');
}
}
});
container.on('selection:update', function (params) { container.on('selection:update', function (params) {
self.update(params.data); self.update(params.data);
}); });

View File

@ -36,11 +36,14 @@
padding: 0; padding: 0;
.option { .option {
cursor: pointer;
padding: 6px; padding: 6px;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
&[aria-selected] {
cursor: pointer;
}
} }
} }
} }

View File

@ -93,15 +93,15 @@
} }
} }
&.disabled { &[aria-disabled=true] {
color: #666; color: #666;
} }
&.selected { &[aria-selected=true] {
background-color: #ddd; background-color: #ddd;
} }
&.highlightable.highlighted { &[aria-selected].highlighted {
background-color: #5897fb; background-color: #5897fb;
color: white; color: white;
} }