
深度解析 radix-vue YearPickerGrid年份选择网格的渲染、组合与无障碍实现【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vueYearPickerGrid 是 radix-vue原 Radix VueYearPicker 组件族中负责承载年份选择网格的容器组件。本篇文章围绕该组件的 Props 定义、源码实现、网格数据生成机制与无障碍语义展开帮助你理解它在 YearPicker 结构中的定位并掌握用as/asChild将默认table渲染为任意元素或组件的组合技巧。读完本文你将能够独立搭建、定制并正确无障碍地使用基于年份网格的选择器界面。YearPickerGrid 在 YearPicker 结构中的定位YearPicker 是一个以日历视图形式选择年份的组件官方文档docs/content/docs/components/year-picker.md给出了完整的 Anatomy部件组装结构script setup import { YearPickerCell, YearPickerCellTrigger, YearPickerGrid, YearPickerGridBody, YearPickerGridRow, YearPickerHeader, YearPickerHeading, YearPickerNext, YearPickerPrev, YearPickerRoot, } from reka-ui /script template YearPickerRoot YearPickerHeader YearPickerPrev / YearPickerHeading / YearPickerNext / /YearPickerHeader YearPickerGrid YearPickerGridBody YearPickerGridRow YearPickerCell YearPickerCellTrigger / /YearPickerCell /YearPickerGridRow /YearPickerGridBody /YearPickerGrid /YearPickerRoot /template在这个结构中部件之间的层级关系与原生 HTML 表格一一对应部件默认渲染元素语义角色YearPickerGridtable网格容器YearPickerGridBodytbody网格主体容器YearPickerGridRowtr网格行容器YearPickerCelltd单元格容器YearPickerCellTriggerbutton可交互的年选择按钮YearPickerGrid位于Header包含 Prev / Heading / Next 导航按钮与单元格区域之间是整个网格部分的最外层容器。它的核心职责有两个包装网格内容、向上层传递只读与禁用状态的无障碍语义见下文源码分析。Props 详解as 与 asChild根据 docs/content/meta/YearPickerGrid.md 的定义YearPickerGrid暴露两个 Props均继承自 Primitive 组件体系NameDescriptionTypeRequiredDefaultasThe element or component this component should render as. Can be overwritten by asChild.AsTag \| ComponentNotableasChildChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details.booleanNo-两者含义与配套关系如下as指定组件最终渲染为哪个元素或组件。默认值是table即默认渲染为原生表格。你可以传入任意合法标签名如section、div或一个 Vue 组件如Transition、自定义布局组件。asChild当设为true时组件不再自己渲染元素而是将自身的 props 与行为合并到传入的单个子元素slot 根节点上由子元素承担最终渲染。此时as将被覆盖文档原文 Can be overwritten by asChild。这一设计在源码中有直接体现。YearPickerGrid.vue 中 Props 接口继承自PrimitiveProps并通过withDefaults设置默认渲染元素export interface YearPickerGridProps extends PrimitiveProps {} const props withDefaults(definePropsYearPickerGridProps(), { as: table })实际渲染时使用Primitive组件完成packages/core/src/YearPicker/YearPickerGrid.vue模板部分Primitive v-bindprops tabindex-1 roleapplication :aria-labelledbyrootContext.headingId :aria-readonlyreadonly :aria-disableddisabled :data-readonlyreadonly :data-disableddisabled slot / /PrimitivePrimitive位于 packages/core/src/Primitive负责解析as/asChild将属性和事件正确绑定到目标元素或组件上。这意味着无论你把网格渲染成table、section还是自定义组件无障碍属性aria-labelledby、aria-readonly、aria-disabled都会被一并透传。源码级实现网格容器的无障碍语义与状态透传YearPickerGrid看似只是一个包装容器但从源码可以看到它承担了重要的可访问性职责。它通过injectYearPickerRootContext()从YearPickerRoot注入共享上下文注入定义见 YearPickerRoot.vue 第 92-93 行的createContext进而读取根组件的禁用 / 只读状态const rootContext injectYearPickerRootContext() const disabled computed(() rootContext.disabled.value ? true : undefined) const readonly computed(() rootContext.readonly.value ? true : undefined)这些状态最终转化为以下输出packages/core/src/YearPicker/YearPickerGrid.vue属性值含义tabindex-1固定网格容器可被程序化聚焦供初始聚焦使用但不进入 Tab 键顺序roleapplication固定将容器声明为应用程序区域配合内部键盘导航逻辑使用aria-labelledby根上下文的headingId指向YearPickerHeading生成的标题元素为网格提供可访问名称aria-readonly只读时存在向辅助技术声明只读状态aria-disabled禁用时存在向辅助技术声明禁用状态data-readonly只读时存在供 CSS 选择器使用例如[data-readonly]样式定制data-disabled禁用时存在供 CSS 选择器使用例如[data-disabled]样式定制对应的官方数据属性表year-picker.md 中 Grid 一节确认了data-readonly与data-disabled两个属性在 Present when readonly / disabled 时出现。注意只读与禁用是不同的语义——禁用会完全阻止交互而只读仍然允许聚焦与浏览但不允许修改选择。网格数据从何而来createYearGrid 与 3×4 网格结构YearPickerGrid本身只负责渲染容器网格中的年份数据则由YearPickerRoot通过useYearPicker组合式函数生成。在 useYearPicker.ts 中网格以响应式ref保存const grid refGridDateValue(createYearGrid({ dateObj: props.placeholder.value, yearsPerPage: props.yearsPerPage.value, })) as RefGridDateValuecreateYearGrid定义于 packages/core/src/date/calendar.ts其行为如下/** * Creates a 3x4 grid of years (decade-aligned). * The grid starts from the decade that contains the given date. */ export function createYearGrid(props: CreateSelectProps { yearsPerPage?: number, decadeAligned?: boolean }): GridDateValue { const { dateObj, yearsPerPage 12, decadeAligned true } props let startYear: number if (decadeAligned) { startYear startOfDecade(dateObj).year } else { startYear dateObj.year } const years Array.from({ length: yearsPerPage }, (_, i) startOfYear(dateObj.set({ year: startYear i }))) const firstYear years[0] return { value: firstYear, cells: years, rows: chunk(years, 4) } }关键细节默认 3×4 网格默认yearsPerPage 12生成 12 个年份并通过chunk(years, 4)每行 4 个、共 3 行与文档导航按钮默认一次翻页 12 年的描述吻合。十年对齐decadeAligned默认为true网格从包含当前日期的那个十年起点开始startOfDecade。例如 placeholder 位于 2026 年时网格第一格是 2020 年展示 2020 - 2031 这样的标题区间。翻页重排nextPage/prevPage会基于当前网格首年加减yearsPerPage生成新网格并传入decadeAligned: false以保证翻页严格按页步进见 useYearPicker.ts 第 135-163 行。placeholder 驱动重绘useYearPicker内部watch(props.placeholder, ...)会检查新年份是否仍落在当前网格区间内若超出则重新生成网格同时监听locale与yearsPerPage变化以重建网格useYearPicker.ts 第 165-176 行。GridDateValue类型的数据结构为{ value, cells, rows }可通过YearPickerRoot的默认插槽解构获得见 YearPickerRoot.vue 中插槽签名{ date, grid, locale, modelValue }因此你完全可以在外层访问grid.cells/grid.rows来自定义网格排版。GridBody 与 GridRow层级化组合的分工与YearPickerGrid配套的还有两个同属网格体系的容器部件它们同样是纯容器 默认元素的 Primitive 组件YearPickerGridBody.vue默认渲染为tbodyProps 同样只有as默认tbody与asChild模板仅包含Primitive v-bindpropsslot //Primitive。YearPickerGridRow.vue默认渲染为tr并固定附加rolerowPrimitive v-bindprops rolerow slot / /Primitive从源码结构看这套层级刻意模拟了原生表格的 DOM 语义table tbody tr td。这样默认形态下可以免费获得原生表格的无障碍与样式基础而当你需要非表格布局例如用 CSS Grid 做 3×4 卡片网格时只需借助as/asChild把各层替换为div、section等元素语义层仍由组件自身补充role与 ARIA 属性。对应地YearPickerGridBody.md 与 YearPickerGridRow.md 中各自记录了两个标准 PropsNameDescriptionTypeRequiredDefaultGridBody / GridRowasThe element or component this component should render as. Can be overwritten by asChild.AsTag \| ComponentNotbody/trasChildChange the default rendered element for the one passed as a child, merging their props and behavior.booleanNo-无障碍与键盘交互YearPickerGrid及其兄弟部件共同构成了完整的键盘导航体验。官方文档year-picker.md 的 Accessibility 一节记录了如下键盘交互约定按键行为Tab焦点移入年份选择器时聚焦第一个导航按钮Space焦点在YearPickerNext/YearPickerPrev上时翻页否则选中年份Enter同上翻页或选中年份ArrowLeft / ArrowRight / ArrowUp / ArrowDown焦点在YearPickerCellTrigger上时在年份间移动必要时自动切换页面PageUp焦点在YearPickerCellTrigger上时跳到上一页年份PageDown焦点在YearPickerCellTrigger上时跳到下一页年份这些行为与YearPickerRoot在onMounted中调用handleCalendarInitialFocus(parentElement.value)当initialFocus为真时见 YearPickerRoot.vue 第 255-258 行相互配合网格容器上的tabindex-1允许程序化聚焦而方向键导航逻辑作用于内部的单元格触发器。实战从默认表格到完全自定义布局场景一默认表格形态最直接的用法就是采用默认形态配合YearPickerRoot的插槽数据自定义单元格样式template YearPickerRoot v-modelselected localezh-CN YearPickerHeader YearPickerPrev / YearPickerHeading / YearPickerNext / /YearPickerHeader YearPickerGrid YearPickerGridBody YearPickerGridRow v-for(row, i) in grid.rows :keyi YearPickerCell v-foryear in row :keyyear.toString() YearPickerCellTrigger / /YearPickerCell /YearPickerGridRow /YearPickerGridBody /YearPickerGrid /YearPickerRoot /template在YearPickerRoot的默认插槽中解构出grid即可按 3 行 × 4 列的数据结构自由驱动模板。场景二用 asChild 替换网格容器元素当需要把网格放进自定义组件例如过渡动画容器时将asChild设为true并把目标组件作为唯一子元素YearPickerGrid as-child TransitionGroup nameyears tagdiv classgrid !-- YearPickerGridBody / Row / Cell 内容 -- /TransitionGroup /YearPickerGridYearPickerGrid会把它携带的roleapplication、aria-labelledby、aria-readonly、aria-disabled以及data-*属性全部合并到子元素上保证自定义布局下无障碍语义不丢失。场景三配置每页年份数网格的页大小由YearPickerRoot的yearsPerPage属性控制默认 12这也是生成 3×4 网格的依据YearPickerRoot v-modelselected :years-per-page8 !-- 每页 8 年rows 按每行 4 个切分为 2 行 -- /YearPickerRoot修改后createYearGrid会生成对应数量的年份useYearPicker的watch([props.locale, props.yearsPerPage], ...)会自动重建网格useYearPicker.ts 第 173-176 行翻页逻辑也会同步按新页大小步进。小结YearPickerGrid是 YearPicker 网格体系的顶层容器默认渲染为table通过as/asChild支持任意元素或组件替换它从YearPickerRoot注入上下文负责输出roleapplication、aria-labelledby、aria-readonly、aria-disabled及data-readonly/data-disabled等无障碍与样式钩子属性网格数据由createYearGrid生成默认每页 12 年、十年对齐、每行 4 个3×4页大小可通过yearsPerPage调整与YearPickerGridBody默认tbody、YearPickerGridRow默认tr固定rolerow形成与原生表格一致的层级既开箱即用又可深度定制完整的键盘导航方向键、PageUp/PageDown、Space/Enter依赖这套网格结构实现替换渲染元素不会破坏交互语义。如需继续深入可以阅读 YearPickerRoot.vue上下文与状态管理、useYearPicker.ts翻页与网格生命周期以及 packages/core/src/date/calendar.ts网格生成算法并结合 docs/content/docs/components/year-picker.md 的完整 API 参考进行实践。【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考