使用 :focus-within 时更新 aria-expanded 属性的最佳方法 [英] Best way to update aria-expanded attribute when using :focus-within

查看:17
本文介绍了使用 :focus-within 时更新 aria-expanded 属性的最佳方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个组合框,为此,我想使用新的 :focus-within 伪选择器来管理显示与组合框关联的可扩展列表框.

:focus-within 是一个很好的解决方案,而且效果很好.我遇到的唯一问题是弄清楚如何更新列表框上的 aria-expanded 属性.由于伪选择器,所有隐藏/显示功能都发生在浏览器中,我不确定可以在 Javascript 中使用的钩子来确定项目何时可见或不可见,以便更新该属性.

有谁知道一个优雅的解决方案吗?我不想复制 :focus-within 正在处理的焦点/模糊逻辑,只是为了更新这个属性.我也担心他们可能会不同步.有没有办法用 :focus-within 或类似的东西来监视元素?

.list {显示:无;}.combobox-container:focus-within .list {显示:块;}

<div role="combobox" aria-expanded="false" aria-owns="listbox" aria-haspopup="listbox"><标签>富<input type="text" aria-autocomplete="list" aria-controls="listbox"/>

<ul class="list" id="listbox" role="listbox" tabindex="0" aria-multiselectable="true"><!-- 用于自动完成的项目.li 标签内的可聚焦锚点.--><li><a href="#">Javascript</a></li><li><a href="#">HTML</a></li><li><a href="#">CSS</a></li></section>

无障碍指南一个组合框

解决方案

focus-within 只有 84% 浏览器覆盖率

因此,这会立即使您的解决方案无法访问,因为许多屏幕阅读器用户仍在使用 JAWs 和 Internet Explorer.

此外,您还遇到了一个问题,虽然这作为演示工作,但在现实世界中,将通过 AJAX 或通过过滤的预加载列表填充自动完成列表.

这意味着列表将始终在您聚焦 <input> 时显示,即使未在组合框中输入任何内容(这不是预期的行为).

这是少数可以接受仅依赖 JavaScript 的情况之一(有一个回退,即表单仍然可以在没有 JavaScript 的情况下提交).

当您返回一些建议然后使用 标准 CSS3 选择器,用于显示和隐藏结果.

以下示例显示了实现此目的的 CSS.+ 操作符是关键,它是 Adjacent Sibling Combinator 选择父元素中的下一个兄弟元素.

CSS: .combobox-container div[aria-expanded="true"]+.list

对于下面的示例,我已经做了这样的设置,一旦您在框中键入超过 1 个字符,它就会将 aria-expanded 属性更改为 true(然后返回再次,如果输入为空) - 这让它感觉更像是一个真实世界"的例子.

旁注:您不需要向

    添加 tabindex,预期的行为是直接使用 Tab第一个建议的项目,我在下面的例子中删除了它.

    //忽略这一点,这是我对代码片段的标准 jQuery 替换if(typeof $=="undefined"){!function(b,c,d,e,f){f=b['add'+e]函数 i(a,d,i){for(d=(a&&a.nodeType?[a]:''+a===a?b.querySelectorAll(a):c),i=d.长度;i--;c.unshift.call(this,d[i]));}$=function(a){return/^f/.test(typeof a)?/in/.test(b.readyState)?setTimeout(function(){$(a);},9):a():new i(a);};$[d]=i[d]={on:function(a,b){return this.each(function(c){f?c['add'+e](a,b,false):c.attachEvent('on'+a,b)})},off:function(a,b){return this.each(function(c){f?c['remove'+e](a,b):c.detachEvent('on'+a,b)})},each:function(a,b){for(var c=this,d=0,e=c.length;d<e;++d){a.call(b||c[d],c[d],d,c)}return c},splice:c.splice}}(document,[],'prototype','EventListener');var props=['add','remove','toggle','has'],maps=['add','remove','toggle','contains'];props.forEach(function(prop,index){$.prototype[prop+'Class']=function(a){return this.each(function(b){if(a){b.classList[maps[index]](a);}});};});$.prototype.hasClass=function(a){return this[0].classList.contains(一种);};}$.prototype.find=function(selector){return $(selector,this);};$.prototype.parent=function(){return(this.length==1)?$(this[0].parentNode):[];};$.prototype.findWithin=function(a){console.log("THIS IS",this[0],a);return this[0].getElementsByClassName(a);};$.prototype.first=function(){return $(this[0]);};$.prototype.focus=function(){return this[0].focus();};$.prototype.css=function(a,b){if(typeof(a)==='object'){for(var prop in a){this.each(function(c){c.style[prop]=a[prop];});}return this;}else{return b===[]._?this[0].style[a]:this.each(function(c){c.style[a]=b;});}};$.prototype.text=function(a){return a===[]._?this[0].textContent:this.each(function(b){b.textContent=a;});};$.prototype.html=function(a){return a===[]._?this[0].innerHTML:this.each(function(b){b.innerHTML=a;});};$.prototype.attr=function(a,b){return b===[]._?this[0].getAttribute(a):this.each(function(c){c.setAttribute(a,b);});};$.param=function(obj,prefix){var str=[];for(var p in obj){var k=prefix?prefix+"["+p+"]":p,v=obj[p];str.push(typeof v=="object"?$.param(v,k):encodeURIComponent(k)+"="+encodeURIComponent(v));}return str.join("&");};$.prototype.append=function(a){return this.each(function(b){b.appendChild(a[0]);});};$.ajax=function(a,b,c,d){var xhr=new XMLHttpRequest();var type=(typeof(b)==='object')?1:0;var gp=['GET','POST'];xhr.open(gp[type],a,true);if(type==1){xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");}xhr.responseType=(typeof(c)==='string')?c:'';var cb=(!type)?b:c;xhr.onerror=function(){cb(this,true);};xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status>=200&&this.status<400){cb(this,false);}else{cb(this,true);}}};if(type){xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');xhr.send($.param(b));}其他{xhr.send();}xhr=null;};//只是演示的一部分,不用于生产用途$('input').on('keyup', function(e){if($(this)[0].value.length > 0){$('div[role=combobox]').attr('aria-expanded', true);返回;}$('div[role=combobox]').attr('aria-expanded', false);返回;});

    .list {显示:无;}.combobox-container div[aria-expanded="true"]+.list{显示:块;边框:2px 实心 #333;}

    <div role="combobox" aria-expanded="false" aria-owns="listbox" aria-haspopup="listbox"><标签>富<input type="text" aria-autocomplete="list" aria-controls="listbox"/>

    <ul class="list" id="listbox" role="listbox" aria-multiselectable="true"><!-- 用于自动完成的项目.li 标签内的可聚焦锚点.--><li><a href="#">Javascript</a></li><li><a href="#">HTML</a></li><li><a href="#">CSS</a></li>

</section>

I'm working on a combobox, and in doing so, I want to use the new :focus-within pseudo selector to manage displaying the expandable listbox that's associated with the combobox.

:focus-within is a great solution and works like a charm. The only problem I'm having is figuring out how to keep the aria-expanded attribute on the listbox updated. Because all of the hide/show functionality is happening in browser-land due to the pseudo-selector, I'm unsure of a hook I can use in Javascript to determine when the item is visible or not in order to update that property.

Is there an elegant solution anyone knows about? I'd hate to have to replicate the logic for focus/blur that :focus-within is handling right now just to update this attribute. I'm also concerned they may get out of sync. There any way to spy on an element with :focus-within or something like that?

.list {
  display: none;
}
.combobox-container:focus-within .list {
  display: block;
}

<section class="combobox-container">
  <div role="combobox" aria-expanded="false" aria-owns="listbox" aria-haspopup="listbox">
     <label> Foo
       <input type="text" aria-autocomplete="list" aria-controls="listbox" />
     </label>
  </div>
  <ul class="list" id="listbox" role="listbox" tabindex="0" aria-multiselectable="true">
    <!-- items for autocomplete. focusable anchors inside li tags. -->
    <li><a href="#">Javascript</a></li>
    <li><a href="#">HTML</a></li>
    <li><a href="#">CSS</a></li>
  </ul>
</section>

Accessibility guidelines for a combobox

解决方案

focus-within only has 84% browser coverage

For this reason that instantly makes your solution inaccessible as a lot of screen reader users still use JAWs with Internet Explorer.

Additionally you have the problem that while this works as a demo, in the real world an auto-complete list will be populated via AJAX or via a preloaded list that is filtered.

This means that the list will always be shown the second you focus the <input>, even when nothing has been typed into the combobox (which is not expected behaviour).

This is one of the few circumstances where relying solely on JavaScript is acceptable (with a fallback that the form can still be submitted without JavaScript).

Instead of trying to use :focus-within you can instead use JavaScript to toggle aria-expanded="true" when you return some suggestions and then use standard CSS3 selectors to show and hide the results.

The below example shows the CSS to achieve this. The + operator is the key, it is the Adjacent Sibling Combinator that selects the next sibling within a parent element.

CSS: .combobox-container div[aria-expanded="true"]+.list

For the example below I have made it so that once you type more than 1 character into the box it will change the aria-expanded attribute to true (and back again if the input is empty) - this makes it feel more like a 'real world' example.

Side note: You do not need to add a tabindex to the <ul>, the expected behaviour is to tab directly to the first suggested item, I have removed that in the example below.

//ignore this, this is my standard jQuery replacement for snippets
if(typeof $=="undefined"){!function(b,c,d,e,f){f=b['add'+e]
function i(a,d,i){for(d=(a&&a.nodeType?[a]:''+a===a?b.querySelectorAll(a):c),i=d.length;i--;c.unshift.call(this,d[i]));}
$=function(a){return /^f/.test(typeof a)?/in/.test(b.readyState)?setTimeout(function(){$(a);},9):a():new i(a);};$[d]=i[d]={on:function(a,b){return this.each(function(c){f?c['add'+e](a,b,false):c.attachEvent('on'+a,b)})},off:function(a,b){return this.each(function(c){f?c['remove'+e](a,b):c.detachEvent('on'+a,b)})},each:function(a,b){for(var c=this,d=0,e=c.length;d<e;++d){a.call(b||c[d],c[d],d,c)}
return c},splice:c.splice}}(document,[],'prototype','EventListener');var props=['add','remove','toggle','has'],maps=['add','remove','toggle','contains'];props.forEach(function(prop,index){$.prototype[prop+'Class']=function(a){return this.each(function(b){if(a){b.classList[maps[index]](a);}});};});$.prototype.hasClass=function(a){return this[0].classList.contains(a);};}
$.prototype.find=function(selector){return $(selector,this);};$.prototype.parent=function(){return(this.length==1)?$(this[0].parentNode):[];};$.prototype.findWithin=function(a){console.log("THIS IS",this[0],a);return this[0].getElementsByClassName(a);};$.prototype.first=function(){return $(this[0]);};$.prototype.focus=function(){return this[0].focus();};$.prototype.css=function(a,b){if(typeof(a)==='object'){for(var prop in a){this.each(function(c){c.style[prop]=a[prop];});}
return this;}else{return b===[]._?this[0].style[a]:this.each(function(c){c.style[a]=b;});}};$.prototype.text=function(a){return a===[]._?this[0].textContent:this.each(function(b){b.textContent=a;});};$.prototype.html=function(a){return a===[]._?this[0].innerHTML:this.each(function(b){b.innerHTML=a;});};$.prototype.attr=function(a,b){return b===[]._?this[0].getAttribute(a):this.each(function(c){c.setAttribute(a,b);});};$.param=function(obj,prefix){var str=[];for(var p in obj){var k=prefix?prefix+"["+p+"]":p,v=obj[p];str.push(typeof v=="object"?$.param(v,k):encodeURIComponent(k)+"="+encodeURIComponent(v));}
return str.join("&");};$.prototype.append=function(a){return this.each(function(b){b.appendChild(a[0]);});};$.ajax=function(a,b,c,d){var xhr=new XMLHttpRequest();var type=(typeof(b)==='object')?1:0;var gp=['GET','POST'];xhr.open(gp[type],a,true);if(type==1){xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");}
xhr.responseType=(typeof(c)==='string')?c:'';var cb=(!type)?b:c;xhr.onerror=function(){cb(this,true);};xhr.onreadystatechange=function(){if(this.readyState===4){if(this.status>=200&&this.status<400){cb(this,false);}else{cb(this,true);}}};if(type){xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');xhr.send($.param(b));}else{xhr.send();}
xhr=null;};

//only part of the demo, not for production use
$('input').on('keyup', function(e){
    if($(this)[0].value.length > 0){
        $('div[role=combobox]').attr('aria-expanded', true);
        return; 
    }
    $('div[role=combobox]').attr('aria-expanded', false);
    return;
});

.list {
  display: none;
}
.combobox-container div[aria-expanded="true"]+.list{
  display: block;
  border:2px solid #333;
}

<section class="combobox-container">
  <div role="combobox" aria-expanded="false" aria-owns="listbox" aria-haspopup="listbox">
     <label> Foo
       <input type="text" aria-autocomplete="list" aria-controls="listbox" />
     </label>
  </div>
  <ul class="list" id="listbox" role="listbox" aria-multiselectable="true">
    <!-- items for autocomplete. focusable anchors inside li tags. -->
    <li><a href="#">Javascript</a></li>
    <li><a href="#">HTML</a></li>
    <li><a href="#">CSS</a></li>
  </ul>
</section>

这篇关于使用 :focus-within 时更新 aria-expanded 属性的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆