尧图精选

Angular CDK Listbox 完全指南:基于 WAI-ARIA 模式构建可访问的自定义列表框

🕒 发布时间:2026/9/12 14:49:46 📁 来源:尧图网络
Angular CDK Listbox 完全指南基于 WAI-ARIA 模式构建可访问的自定义列表框【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/componentsangular/cdk/listbox是 Angular Component Development KitCDK中用于构建自定义列表框listbox交互的指令集合严格遵循 WAI-ARIA Listbox Pattern。本指南将以仓库中的 listbox.md 为主线结合 listbox.ts 的源码实现与components-examples中的完整示例带你掌握从基础用法cdkListboxcdkOption、值绑定、单选/多选、表单集成到禁用、焦点管理、方向、Typeahead 与键盘导航等全部可访问性细节最终能在自己的 Angular 应用中直接落地一套无样式假设、完全可定制的无障碍列表框。模块概览开箱即用的可访问性基础设施angular/cdk/listbox提供的所有指令都会自动在其宿主元素host element上应用对应的 ARIA role并为你处理实现无障碍体验所需的全部预期行为包括Bidi 布局支持通过Directionality服务感知页面文本方向源码见 listbox.ts横向列表会根据ltr/rtl自动调整左右方向键的语义键盘交互完整实现 ARIA 规范定义的键盘操作包括方向键导航、Home/End、Space/Enter选择、多选下的范围选择等焦点管理默认采用 roving tabindex 策略也支持切换为 aria-activedescendant 策略自动 ARIA 角色指令将对应角色绑定到宿主元素上无需手写role属性。从源码角度看模块的核心是两个指令类CdkOptionlistbox.ts与CdkListboxlistbox.ts它们通过angular/cdk/collections中的SelectionModel管理选中状态通过angular/cdk/a11y中的ActiveDescendantKeyManager驱动键盘导航公开导出统一位于 public-api.ts。支持的 ARIA Rolesangular/cdk/listbox中的指令会在宿主元素上设置相应角色指令ARIA RolecdkOptionoptioncdkListboxlistbox以源码为证CdkOption的宿主绑定直接声明了role: option与class: cdk-optionlistbox.tsCdkListbox同样声明了role: listboxlistbox.ts。快速开始搭建你的第一个 Listboxangular/cdk/listbox被设计为高度可定制它不对元素样式做任何假设你需要自行提供 CSS 样式但指令会施加一些 CSS 类方便你添加自定义样式。一个典型的 listbox 由以下指令组成cdkListbox添加到包含可选项的容器元素上cdkOption添加到 listbox 中每个可选项上。首先在组件中导入CdkListbox和CdkOption现代 Angular 采用独立组件imports数组方式参考示例 cdk-listbox-overview-example.tsimport {Component} from angular/core; import {CdkListbox, CdkOption} from angular/cdk/listbox; Component({ selector: app-fav-color, templateUrl: fav-color.html, imports: [CdkListbox, CdkOption], }) export class FavColorComponent {}模板中最简单的用法完整示例见 cdk-listbox-overview-example.htmllabel classexample-listbox-label idexample-fav-color-label Favorite color /label ul cdkListbox aria-labelledbyexample-fav-color-label classexample-listbox li cdkOptionred classexample-optionRed/li li cdkOptiongreen classexample-optionGreen/li li cdkOptionblue classexample-optionBlue/li /ul这里的ul承担 listbox 容器li则作为可选项。由于指令不假定标签结构你也可以使用div、button等任意元素来承载这些指令。Option 的值listbox 中的每个 option 都绑定到一个值该值即该选项被选中时所代表的值例如li cdkOptionredRed/li中的red。需要记住两条规则同一 listbox 内每个 option 的值必须唯一。开发模式下ngDevMode源码会进行重复值校验CdkListbox在ngAfterContentInit中调用_verifyNoOptionValueCollisions检测到重复值时会在控制台输出警告listbox.ts、listbox.ts。如果未显式指定值则该 option 的值被视为空字符串例如li cdkOptionNo color preference/li。值可以是任意类型源码中CdkOption的value输入为泛型Tlistbox.ts既可以是字符串也可以是对象等复杂类型复杂类型需要配合自定义比较函数见下文Listbox 的值一节。单选与多选默认情况下listbox同一时刻只允许选中一个选项。在 listbox 元素上添加cdkListboxMultiple即可启用多选示例见 cdk-listbox-multiple-example.htmlul cdkListbox cdkListboxMultiple aria-labelledbyexample-fav-cuisine-label classexample-listbox li cdkOptionchinese classexample-optionChinese/li li cdkOptionfrench classexample-optionFrench/li li cdkOptionitalian classexample-optionItalian/li li cdkOptionjapanese classexample-optionJapanese/li /ul从源码层面看cdkListboxMultiple对应CdkListbox.multiple输入其背后是一个ListboxSelectionModellistbox.ts该类继承自SelectionModel但内部始终以多选模型运行——即使处于单选模式也保留完整的选择状态以便用户在初始化之后从单选切换到多选时能够恢复完整选择当实际处于单选模式时select方法被重写为setSelection保证每次只保留一个选中值。需要注意multiple的切换存在边界行为——当值从true切换为false且当前已选中多个选项时所有选项会被取消选中源码注释见 listbox.ts。Listbox 的值listbox 的值是一个数组包含当前被选中选项的值即使处于单选模式值也是只含一个元素的数组。这一点由CdkListbox.value的 getter 返回selectionModel.selected保证listbox.ts。值可通过双向绑定的两侧使用[cdkListboxValue]输入设置当前选中值(cdkListboxValueChange)输出在值变化时发出ListboxValueChangeEvent。完整示例见 cdk-listbox-value-binding-example.htmlul cdkListbox [cdkListboxValue]starter (cdkListboxValueChange)starter $event.value aria-labelledbyexample-starter-pokemon-label classexample-listbox for (pokemon of starters; track pokemon) { li [cdkOption]pokemon classexample-option{{pokemon}}/li } /ul pYour starter pokemon is strong{{starter | json}}/strong/pListboxValueChangeEvent的结构定义在 listbox.ts包含只读属性value新的选中值数组、listbox发出事件的 listbox 实例引用以及option被触发的选项引用可能为null例如通过CtrlA全选时。自定义值比较函数cdkListboxCompareWith内部实现中listbox 使用Object.is将 listbox 的值与各个 option 的值逐一比较以确定哪些选项应该呈现为选中状态。因此当 option 的值是复杂对象如日期、DTO 对象时Object.is的引用比较将无法匹配此时应当通过cdkListboxCompareWith输入提供一个自定义比较函数。示例 cdk-listbox-compare-with-example.html 与配套 TScdk-listbox-compare-with-example.ts演示了以Date对象为值、按时间戳比较的场景ul cdkListbox [cdkListboxValue]appointment [cdkListboxCompareWith]compareDate (cdkListboxValueChange)appointment $event.value aria-labelledbyexample-appointment-label classexample-listbox for (time of slots; track time) { li [cdkOption]time classexample-option{{formatTime(time)}}/li } /ulslots [12, 13, 14, 15].map( hour new Date(today.getFullYear(), today.getMonth(), today.getDate() 1, hour), ); appointment: readonly Date[] [ new Date(today.getFullYear(), today.getMonth(), today.getDate() 1, 14), ]; compareDate(date1: Date, date2: Date) { return date1.getTime() date2.getTime(); }比较函数贯穿整个选择流程无论是选中判定、_updateInternalValue中的排序、还是_getInvalidOptionValues的合法性校验都会优先使用compareWith缺省时才回退到Object.is例如 listbox.ts。Angular Forms 支持CDK Listbox同时支持模板驱动表单与响应式表单因为CdkListbox实现了ControlValueAccessorlistbox.ts 中的NG_VALUE_ACCESSORprovider以及类声明处的implements ... ControlValueAccessor。其实现要点listbox.tsregisterOnChange/registerOnTouched注册表单回调writeValue(value)由表单写入值内部调用_setSelectionsetDisabledState(isDisabled)同步表单禁用状态到 listbox。模板驱动表单使用ngModel进行双向绑定示例见 cdk-listbox-template-forms-example.htmlul cdkListbox cdkListboxMultiple [(ngModel)]order aria-labelledbyexample-toppings-label classexample-listbox for (topping of toppings; track topping) { li [cdkOption]topping classexample-option{{topping}}/li } /ul pYour order: strong{{order | json}}/strong/p响应式表单使用formControl示例见 cdk-listbox-reactive-forms-example.htmlul cdkListbox [formControl]languageCtrl aria-labelledbyexample-language-label classexample-listbox for (language of languages; track language) { li [cdkOption]language classexample-option{{language}}/li } /ul pYour preferred language: strong{{languageCtrl.value | json}}/strong/p注意两种表单模式下 listbox 的值同样是数组形态单选时也如此消费方需要按数组取值例如languageCtrl.value[0]。禁用选项与禁用整个 Listbox通过cdkOptionDisabled禁用单个选项使其不可被选中通过cdkListboxDisabled禁用整个 listbox 控件。示例见 cdk-listbox-disabled-example.html其中 listbox 整体是否禁用由一个复选框联动控制同时 Zinfandel 选项通过cdkOptionDisabled标记为售罄不可选ul cdkListbox [cdkListboxDisabled]!canDrinkCtrl.value aria-labelledbyexample-wine-type-label classexample-listbox li cdkOptioncabernet classexample-optionCabernet Sauvignon/li li cdkOptionsyrah classexample-optionSyrah/li li cdkOptionzinfandel cdkOptionDisabled classexample-option Zinfandel span classexample-sold-out(sold out)/span /li li cdkOptionriesling classexample-optionRiesling/li /ul从源码看CdkOption.disabled的 getter 返回的是listbox 禁用状态与自身禁用状态的并集listbox.ts即只要父 listbox 被禁用所有选项都视为禁用禁用的选项其tabindex会被强制设为-1listbox.ts。可访问性Accessibilityangular/cdk/listbox中的指令遵循 ARIA 规范中定义的无障碍最佳实践并实现了 ARIA Listbox Keyboard Interaction 规范定义的键盘交互但不包含selection follows focus这一可选逻辑。下面逐项说明文档规定的可访问性要求与对应输入。给 Listbox 一个标签务必为 listbox 提供对视障用户有意义供屏幕阅读器读取的标签如果 listbox 有可见的文字标签用aria-labelledby关联它如前面示例中aria-labelledbyexample-fav-color-label关联label的id如果没有可见标签则应使用aria-label提供仅屏幕阅读器可见的标签。Roving tabindex 与 Active Descendant默认情况下CDK listbox 使用roving tabindex策略管理焦点焦点始终落在当前激活的选项上键盘移动焦点时各选项的tabindex会在0与-1之间轮换源码见CdkOption._getTabIndexlistbox.ts。如果你更倾向于aria-activedescendant策略焦点保持在 listbox 容器上通过aria-activedescendant指向激活选项可以设置cdkListboxUseActiveDescendanttrue布尔属性写属性名即开启。示例见 cdk-listbox-activedescendant-example.htmlul cdkListbox cdkListboxMultiple cdkListboxUseActiveDescendant aria-labelledbyexample-spatula-label classexample-listbox for (feature of features; track feature) { li [cdkOption]feature classexample-option{{feature}}/li } /ul源码中该模式通过_getAriaActiveDescendant将激活选项的id输出到aria-activedescendantlistbox.ts同时选项的tabindex全部保持-1listbox 容器自身保留一个 tab stoplistbox.ts。setActiveStyles中还会主动调用scrollIntoView因为 activedescendant 模式下浏览器不会自动将激活项滚动进视口listbox.ts。此外若用户在 activedescendant 模式下点击了某个选项焦点会被推回 listbox 容器以避免出现多余的 tab stoplistbox.ts。方向OrientationListbox默认假定垂直方向可通过cdkListboxOrientation输入定制。注意该输入只影响键盘导航上下左右方向键的语义视觉外观仍需你自行调整 CSS。示例见 cdk-listbox-horizontal-example.htmlul cdkListbox cdkListboxOrientationhorizontal aria-labelledbyexample-shirt-size-label classexample-listbox for (size of sizes; track size) { li [cdkOption]size classexample-option{{size}}/li } /ul源码中orientation的 setter 会将值归一化为horizontal | vertical其他值一律视为vertical并据此调用 key manager 的withHorizontalOrientation(dir)或withVerticalOrientation()listbox.ts水平方向时左右键生效垂直方向时上下键生效。选项 Typeahead键入搜索CDK listbox基于选项文本支持 typeahead快速键入字母跳转到对应选项由 key manager 的withTypeAhead()启用见 listbox.ts。如果某些选项的 typeahead 文本需要与显示文本不同例如要排除 emoji 表情符号可以在选项上设置cdkOptionTypeaheadLabel。示例见 cdk-listbox-custom-typeahead-example.html——选项显示 Great等带 emoji 的文本但 typeahead 文本分别为great/okay/badul cdkListbox aria-labelledbyexample-satisfaction-label classexample-listbox li [cdkOption]1 cdkOptionTypeaheadLabelgreat classexample-option Great/li li [cdkOption]0 cdkOptionTypeaheadLabelokay classexample-option Okay/li li [cdkOption]-1 cdkOptionTypeaheadLabelbad classexample-option Bad/li /ul源码中typeaheadLabel为null时getLabel()回退到宿主元素的textContent.trim()listbox.ts因此默认情况下 typeahead 直接使用渲染文本。键盘导航选项环绕wrap使用键盘在选项间导航时当尝试越过选项起点或终点默认会循环环绕到另一端。若需禁用环绕设置cdkListboxNavigationWrapDisabled。跳过禁用项键盘导航默认跳过被禁用的选项。若需改变此行为设置cdkListboxNavigatesDisabledOptions使导航也落入禁用选项。两者在 key manager 初始化与动态更新中均有体现withWrap(!this._navigationWrapDisabled)控制环绕listbox.tsskipPredicate根据开关在_skipDisabledPredicate跳过禁用项与_skipNonePredicate不跳过之间切换listbox.ts、listbox.ts。组合示例见 cdk-listbox-custom-navigation-example.htmlPumpkin Spice为禁用项同时开启不环绕与不跳过禁用项ul cdkListbox cdkListboxNavigatesDisabledOptions cdkListboxNavigationWrapDisabled aria-labelledbyexample-flavor-label classexample-listbox li cdkOptionchocolate classexample-optionChocolate/li li cdkOptionpumpkin-spice cdkOptionDisabled classexample-option Pumpkin Spice (seasonal) /li li cdkOptionstrawberry classexample-optionStrawberry/li li cdkOptionvanilla classexample-optionVanilla/li /ul完整的键盘交互矩阵源码级从_handleKeydownlistbox.ts可以还原出完整的按键行为按键行为Up/Down垂直/Left/Right水平移动激活项默认环绕、跳过禁用项Home/End跳到第一个 / 最后一个选项由withHomeAndEnd()启用Space/Enter触发激活选项单选模式下选中它并取消其他选中多选模式下切换其选中状态triggerOptionlistbox.tsShift 方向键 /Home/End移动激活项的同时选中经过的选项见_handleKeydown末尾的 shift 处理ShiftSpace/Enter多选从上次触发的选项到当前激活项的范围选择/取消triggerRangelistbox.tsCtrl/CmdShiftHome/End多选将选区扩展到列表首部 / 尾部Ctrl/CmdA多选若未全选则全选否则全部取消Shift 鼠标点击多选范围选择_handleOptionClickedlistbox.ts此外当 listbox 获得焦点时如果已有选中项焦点会自动落在第一个选中的选项上_setNextFocusToSelectedOptionlistbox.tslistbox 失去焦点时记录_onTouched供表单使用listbox.ts。CSS 类与样式定制指令施加的 CSS 类如下按指令归类指令CSS Class施加时机cdkOption.cdk-option始终cdkOption.cdk-option-active选项处于激活状态时cdkListbox.cdk-listbox始终除 CSS 类之外这些指令还会施加一些ARIA 属性同样可以作为 CSS 选择器来定向设置样式指令属性选择器施加时机cdkOption[aria-disabledtrue]选项被禁用cdkOption[aria-disabledfalse]选项未被禁用cdkOption[aria-selectedtrue]选项被选中cdkOption[aria-selectedfalse]选项未被选中cdkListbox[aria-disabledtrue]listbox 被禁用cdkListbox[aria-disabledfalse]listbox 未被禁用cdkListbox[aria-multiselectabletrue]listbox 为多选cdkListbox[aria-multiselectablefalse]listbox 为单选cdkListbox[aria-orientationhorizontal]listbox 为水平方向cdkListbox[aria-orientationvertical]listbox 为垂直方向这些属性与指令宿主绑定一一对应如[attr.aria-selected]: isSelected()、[attr.aria-disabled]: disabled、[attr.aria-multiselectable]: multiple、[attr.aria-orientation]: orientation见 listbox.ts 与 listbox.ts。注意.cdk-option-active是**激活当前键盘焦点所在**而非选中状态选中状态请使用[aria-selectedtrue]选择器。实际示例的完整样式可参考各示例目录下的 CSS 文件例如 cdk-listbox-overview-example.css展示了如何仅凭cdk-listbox/cdk-option/cdk-option-active与aria-selected等选择器实现选中高亮、激活边框等视觉效果完全不需要修改组件内部结构。小结angular/cdk/listbox以两个指令cdkListboxcdkOption为骨架将 WAI-ARIA Listbox Pattern 的完整交互规范封装为可组合的输入cdkListboxMultiple、cdkListboxValue、cdkListboxCompareWith、cdkListboxDisabled、cdkListboxUseActiveDescendant、cdkListboxOrientation、cdkListboxNavigationWrapDisabled、cdkListboxNavigatesDisabledOptions、cdkOptionDisabled、cdkOptionTypeaheadLabel与输出cdkListboxValueChange。它不做任何样式假设、自动管理 ARIA 角色与焦点、内置完整的键盘交互和表单接入能力让你可以专注业务展示层快速构建出符合无障碍最佳实践的自定义列表框。进一步深入核心实现可通读 listbox.ts 与配套测试 listbox.spec.ts模块声明见 listbox-module.ts全部可运行示例位于 src/components-examples/cdk/listbox 目录共 12 个示例覆盖本文介绍的所有特性。【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →