Этот ответ получился довольно длинным, первые 3 балла, скорее всего, проблема, остальные - другие соображения / наблюдения
Есть несколько вещей, которые могут вызвать эту проблему , хотя не видя сгенерированный HTML, а не Angular Source, могут быть и другие.
Скорее всего, виновником является то, что ваши якоря недействительны. Вы не можете иметь пустой HREF (href=""
), чтобы он был действительным. Глядя на свой исходный код, не могли бы вы удалить это и настроить CSS или изменить его на <div>
?
Второй наиболее вероятный виновник в том, что role="option"
должен быть на прямом потомке на role="listbox"
. Переместите его на <li>
s и сделайте его выбираемым с помощью tabindex="-1"
(см. Ниже пункт на tabindex="0"
). (на самом деле, почему бы просто не удалить окружающие <div>
и применить все ваши директивы angular непосредственно к <li>
).
Третьим наиболее вероятным виновником является тот факт, что aria-label
не требуется и может фактически мешать, программа чтения с экрана будет читать текст в вашем <span>
без этого. Золотое правило - не используйте aria
, если вы не можете изобразить информацию другим способом.
Вам также необходимо добавить aria-selected="true"
(или false) к каждому <li role="option">
, чтобы указать, выбран ли элемент или нет.
Также вы должны добавить aria-multiselectable="true"
к <ul>
, чтобы указать, что это множественный выбор.
Пока вы на нем, удалите атрибут title
, он не Не добавляйте ничего полезного.
aria-activedescendant="id"
следует использовать, чтобы указать, какой элемент в данный момент находится в фокусе.
Будьте осторожны с tabindex="0"
- я не вижу, применяется ли это к все, но на самом деле это должно быть tabindex="-1"
, и вы программно управляете фокусом, иначе пользователи могут переходить к элементам, для которых они не предназначены. tabindex="0"
должно быть на главном <ul>
.
Из-за сложной природы множественного выбора вам будет намного лучше использовать группу флажков, поскольку они предоставляют множество функциональных возможностей бесплатно, но это это всего лишь предложение.
Следующий пример , который я нашел на codepen.io , покрывает 95% всего, если вы используете вместо этого флажок, и будет хорошей основой для того, чтобы вы могли выделить и адаптировать как вы можете видеть, чекбоксы значительно облегчают жизнь, так как встроены все выбранные, а не выбранные функции.
(function($){
'use strict';
const DataStatePropertyName = 'multiselect';
const EventNamespace = '.multiselect';
const PluginName = 'MultiSelect';
var old = $.fn[PluginName];
$.fn[PluginName] = plugin;
$.fn[PluginName].Constructor = MultiSelect;
$.fn[PluginName].noConflict = function () {
$.fn[PluginName] = old;
return this;
};
// Defaults
$.fn[PluginName].defaults = {
};
// Static members
$.fn[PluginName].EventNamespace = function () {
return EventNamespace.replace(/^\./ig, '');
};
$.fn[PluginName].GetNamespacedEvents = function (eventsArray) {
return getNamespacedEvents(eventsArray);
};
function getNamespacedEvents(eventsArray) {
var event;
var namespacedEvents = "";
while (event = eventsArray.shift()) {
namespacedEvents += event + EventNamespace + " ";
}
return namespacedEvents.replace(/\s+$/g, '');
}
function plugin(option) {
this.each(function () {
var $target = $(this);
var multiSelect = $target.data(DataStatePropertyName);
var options = (typeof option === typeof {} && option) || {};
if (!multiSelect) {
$target.data(DataStatePropertyName, multiSelect = new MultiSelect(this, options));
}
if (typeof option === typeof "") {
if (!(option in multiSelect)) {
throw "MultiSelect does not contain a method named '" + option + "'";
}
return multiSelect[option]();
}
});
}
function MultiSelect(element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn[PluginName].defaults, options);
this.destroyFns = [];
this.$toggle = this.$element.children('.toggle');
this.$toggle.attr('id', this.$element.attr('id') + 'multi-select-label');
this.$backdrop = null;
this.$allToggle = null;
init.apply(this);
}
MultiSelect.prototype.open = open;
MultiSelect.prototype.close = close;
function init() {
this.$element
.addClass('multi-select')
.attr('tabindex', 0);
initAria.apply(this);
initEvents.apply(this);
updateLabel.apply(this);
injectToggleAll.apply(this);
this.destroyFns.push(function() {
return '|'
});
}
function injectToggleAll() {
if(this.$allToggle && !this.$allToggle.parent()) {
this.$allToggle = null;
}
this.$allToggle = $("<li><label><input type='checkbox'/>(all)</label><li>");
this.$element
.children('ul:first')
.prepend(this.$allToggle);
}
function initAria() {
this.$element
.attr('role', 'combobox')
.attr('aria-multiselect', true)
.attr('aria-expanded', false)
.attr('aria-haspopup', false)
.attr('aria-labeledby', this.$element.attr("aria-labeledby") + " " + this.$toggle.attr('id'));
this.$toggle
.attr('aria-label', '');
}
function initEvents() {
var that = this;
this.$element
.on(getNamespacedEvents(['click']), function($event) {
if($event.target !== that.$toggle[0] && !that.$toggle.has($event.target).length) {
return;
}
if($(this).hasClass('in')) {
that.close();
} else {
that.open();
}
})
.on(getNamespacedEvents(['keydown']), function($event) {
var next = false;
switch($event.keyCode) {
case 13:
if($(this).hasClass('in')) {
that.close();
} else {
that.open();
}
break;
case 9:
if($event.target !== that.$element[0] ) {
$event.preventDefault();
}
case 27:
that.close();
break;
case 40:
next = true;
case 38:
var $items = $(this)
.children("ul:first")
.find(":input, button, a");
var foundAt = $.inArray(document.activeElement, $items);
if(next && ++foundAt === $items.length) {
foundAt = 0;
} else if(!next && --foundAt < 0) {
foundAt = $items.length - 1;
}
$($items[foundAt])
.trigger('focus');
}
})
.on(getNamespacedEvents(['focus']), 'a, button, :input', function() {
$(this)
.parents('li:last')
.addClass('focused');
})
.on(getNamespacedEvents(['blur']), 'a, button, :input', function() {
$(this)
.parents('li:last')
.removeClass('focused');
})
.on(getNamespacedEvents(['change']), ':checkbox', function() {
if(that.$allToggle && $(this).is(that.$allToggle.find(':checkbox'))) {
var allChecked = that.$allToggle
.find(':checkbox')
.prop("checked");
that.$element
.find(':checkbox')
.not(that.$allToggle.find(":checkbox"))
.each(function(){
$(this).prop("checked", allChecked);
$(this)
.parents('li:last')
.toggleClass('selected', $(this).prop('checked'));
});
updateLabel.apply(that);
return;
}
$(this)
.parents('li:last')
.toggleClass('selected', $(this).prop('checked'));
var checkboxes = that.$element
.find(":checkbox")
.not(that.$allToggle.find(":checkbox"))
.filter(":checked");
that.$allToggle.find(":checkbox").prop("checked", checkboxes.length === checkboxes.end().length);
updateLabel.apply(that);
})
.on(getNamespacedEvents(['mouseover']), 'ul', function() {
$(this)
.children(".focused")
.removeClass("focused");
});
}
function updateLabel() {
var pluralize = function(wordSingular, count) {
if(count !== 1) {
switch(true) {
case /y$/.test(wordSingular):
wordSingular = wordSingular.replace(/y$/, "ies");
default:
wordSingular = wordSingular + "s";
}
}
return wordSingular;
}
var $checkboxes = this.$element
.find('ul :checkbox');
var allCount = $checkboxes.length;
var checkedCount = $checkboxes.filter(":checked").length
var label = checkedCount + " " + pluralize("item", checkedCount) + " selected";
this.$toggle
.children("label")
.text(checkedCount ? (checkedCount === allCount ? '(all)' : label) : 'Select a value');
this.$element
.children('ul')
.attr("aria-label", label + " of " + allCount + " " + pluralize("item", allCount));
}
function ensureFocus() {
this.$element
.children("ul:first")
.find(":input, button, a")
.first()
.trigger('focus')
.end()
.end()
.find(":checked")
.first()
.trigger('focus');
}
function addBackdrop() {
if(this.$backdrop) {
return;
}
var that = this;
this.$backdrop = $("<div class='multi-select-backdrop'/>");
this.$element.append(this.$backdrop);
this.$backdrop
.on('click', function() {
$(this)
.off('click')
.remove();
that.$backdrop = null;
that.close();
});
}
function open() {
if(this.$element.hasClass('in')) {
return;
}
this.$element
.addClass('in');
this.$element
.attr('aria-expanded', true)
.attr('aria-haspopup', true);
addBackdrop.apply(this);
//ensureFocus.apply(this);
}
function close() {
this.$element
.removeClass('in')
.trigger('focus');
this.$element
.attr('aria-expanded', false)
.attr('aria-haspopup', false);
if(this.$backdrop) {
this.$backdrop.trigger('click');
}
}
})(jQuery);
$(document).ready(function(){
$('#multi-select-plugin')
.MultiSelect();
});
* {
box-sizing: border-box;
}
.multi-select, .multi-select-plugin {
display: inline-block;
position: relative;
}
.multi-select > span, .multi-select-plugin > span {
border: none;
background: none;
position: relative;
padding: .25em .5em;
padding-right: 1.5em;
display: block;
border: solid 1px #000;
cursor: default;
}
.multi-select > span > .chevron, .multi-select-plugin > span > .chevron {
display: inline-block;
transform: rotate(-90deg) scale(1, 2) translate(-50%, 0);
font-weight: bold;
font-size: .75em;
position: absolute;
top: .2em;
right: .75em;
}
.multi-select > ul, .multi-select-plugin > ul {
position: absolute;
list-style: none;
padding: 0;
margin: 0;
left: 0;
top: 100%;
min-width: 100%;
z-index: 1000;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.15);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
display: none;
max-height: 320px;
overflow-x: hidden;
overflow-y: auto;
}
.multi-select > ul > li, .multi-select-plugin > ul > li {
white-space: nowrap;
}
.multi-select > ul > li.selected > label, .multi-select-plugin > ul > li.selected > label {
background-color: LightBlue;
}
.multi-select > ul > li.focused > label, .multi-select-plugin > ul > li.focused > label {
background-color: DodgerBlue;
}
.multi-select > ul > li > label, .multi-select-plugin > ul > li > label {
padding: .25em .5em;
display: block;
}
.multi-select > ul > li > label:focus, .multi-select > ul > li > label:hover, .multi-select-plugin > ul > li > label:focus, .multi-select-plugin > ul > li > label:hover {
background-color: DodgerBlue;
}
.multi-select.in > ul, .multi-select-plugin.in > ul {
display: block;
}
.multi-select-backdrop, .multi-select-plugin-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 900;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label id="multi-select-plugin-label" style="display:block;">Multi Select</label>
<div id="multi-select-plugin" aria-labeledby="multi-select-plugin-label">
<span class="toggle">
<label>Select a value</label>
<span class="chevron"><</span>
</span>
<ul>
<li>
<label>
<input type="checkbox" name="selected" value="0"/>
Item 1
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="1"/>
Item 2
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="2"/>
Item 3
</label>
</li>
<li>
<label>
<input type="checkbox" name="selected" value="3"/>
Item 4
</label>
</li>
</ul>
</div>
Также вы увидите, что gov.uk использует шаблон флажка (внутри фильтра организации слева на связанном страница) для их множественного выбора (с фильтром - то, что вы можете рассмотреть с 100 различными вариантами, так как они выделили некоторые ключевые проблемы в этой статье ).
Как видите (и я еще не закончил), есть над чем подумать.
Надеюсь, я вас не сильно напугал, и первые несколько моментов решат проблему, с которой вы изначально столкнулись. спросил о!