news 2026/9/17 19:33:17

radix-vue(Reka UI)YearRangePickerRoot 组件完全指南:年份区间选择器的 Props、事件与插槽深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
radix-vue(Reka UI)YearRangePickerRoot 组件完全指南:年份区间选择器的 Props、事件与插槽深度解析

radix-vue(Reka UI)YearRangePickerRoot 组件完全指南:年份区间选择器的 Props、事件与插槽深度解析

【免费下载链接】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

YearRangePickerRoot 是 radix-vue 组件库(现更名为 Reka UI)中用于构建"年份区间选择器"的根组件,适合需要按年份批量选择连续区间的场景,例如筛选 2018-2023 年度数据、生成多年期的统计报表等。读完本文,你将掌握该根组件的全部 23 个 Props、3 个自定义事件与 4 个作用域插槽的准确语义,并能结合子组件组合出一个可无障碍键盘操作的年份范围选择器。

组件定位与整体架构

YearRangePickerRoot 是整个 YearRangePicker(年份范围选择器)的唯一状态中枢。它本身不渲染任何可见的年份网格,而是通过provide/inject上下文机制向YearRangePickerGridYearRangePickerCellYearRangePickerHeaderYearRangePickerNextYearRangePickerPrev等子组件下发共享状态与回调,这与仓库内RangeCalendarMonthRangePickerYearPicker等日期组件共享同一套设计模式。

从源码看,Root 组件内部组合了两个核心状态逻辑:

  • useYearPicker(由YearRangePickerRoot.vue第 216 行引入):负责年份网格生成(grid)、翻页(nextPage/prevPage)、标题文本(headingValue)与格式化器(formatter);
  • useRangeYearPickerState:负责区间合法性校验(isInvalid)、选中态判定(isSelected)、区间高亮(highlightedRange)以及maximumYears/fixedDate的边界约束。

组件通过 index.ts 统一导出 9 个子组件,所有组件均以YearRangePicker前缀命名,便于在 IDE 中自动补全与全局注册。

基础用法示例

首先引入组件与日期工具类:

import { CalendarDate } from '@internationalized/date' import { YearRangePickerCell, YearRangePickerCellTrigger, YearRangePickerGrid, YearRangePickerGridBody, YearRangePickerGridRow, YearRangePickerHeader, YearRangePickerHeading, YearRangePickerNext, YearRangePickerPrev, YearRangePickerRoot, } from 'reka-ui'

默认选中一个 2020 至 2024 年的区间(默认每页显示 12 年,按 4 列 × 3 行布局):

<script setup lang="ts"> const defaultValue = { start: new CalendarDate(2020, 1, 1), end: new CalendarDate(2024, 1, 1), } </script> <template> <YearRangePickerRoot v-slot="{ grid }" :default-value="defaultValue"> <YearRangePickerHeader> <YearRangePickerPrev>‹</YearRangePickerPrev> <YearRangePickerHeading /> <YearRangePickerNext>›</YearRangePickerNext> </YearRangePickerHeader> <YearRangePickerGrid> <YearRangePickerGridBody> <YearRangePickerGridRow v-for="(years, index) in grid.rows" :key="`year-${index}`"> <YearRangePickerCell v-for="year in years" :key="year.toString()" :date="year"> <YearRangePickerCellTrigger :year="year" /> </YearRangePickerCell> </YearRangePickerGridRow> </YearRangePickerGridBody> </YearRangePickerGrid> </YearRangePickerRoot> </template>

上述结构在官方文档示例中均有完整可运行的实现,分别位于 tailwind 版示例 与 css 版示例,可直接参考其样式写法。

Props 全量解析

YearRangePickerRoot 共暴露 23 个 Props。下表为完整清单:

Name说明类型必填默认值
allowNonContiguousRangesisYearUnavailable配合,决定是否允许选择不连续区间booleanfalse
as指定组件渲染为的元素或组件,可被asChild覆盖AsTag \| Component"div"
asChild将默认渲染元素替换为传入的子元素,并合并 props 与行为boolean-
calendarLabel日历的可访问标签(无障碍用途)string-
defaultPlaceholder默认占位日期DateValue-
defaultValue日历的默认值DateRange{ start: undefined, end: undefined }
dir日历的阅读方向"ltr" \| "rtl"-
disabled是否禁用整个日历booleanfalse
fixedDate固定区间的哪一端"start" \| "end"-
initialFocus为 true 时,挂载时聚焦到已选中的年份booleanfalse
isYearDisabled判断某一年是否被禁用的函数Matcher-
isYearUnavailable判断某一年是否不可用的函数Matcher-
locale用于日期格式化的语言环境string-
maximumYears区间最多可选多少个年份number-
maxValue可选择的最大日期DateValue-
minValue可选择的最小日期DateValue-
modelValue受控的已选年份区间,可绑定v-modelDateRange \| null-
nextPage返回下一页(下一个年份页)的函数(placeholder: DateValue) => DateValue-
placeholder占位日期,用于在未选择时决定显示哪一页DateValue-
preventDeselect是否阻止用户在未选择新日期前取消选择booleanfalse
prevPage返回上一页(上一个年份页)的函数(placeholder: DateValue) => DateValue-
readonly日历是否只读booleanfalse
yearsPerPage每页显示的年份数量number12

受控与非受控:modelValue、defaultValue 与 placeholder

  • modelValue是受控值,通过v-model双向绑定;当存在modelValue时,defaultValue不再生效。Root 内部使用useVModel同步,并在外部值变化时通过watch自动同步startValue/endValue(见 YearRangePickerRoot.vue)。
  • defaultValue为非受控初始值,类型为DateRange,其结构为{ start: DateValue | undefined, end: DateValue | undefined }
  • placeholder决定"当前展示的是哪一页"——当用户尚未选择任何年份时,以占位日期所在年份对应的页作为展示页;选中起始年后,placeholder会自动跟随起始年(源码中watch(startValue)会同步更新 placeholder)。

区间约束:minValue / maxValue 与 maximumYears / fixedDate

  • minValue/maxValue是绝对日期边界:任何超出该范围的年份既不可选中,也不会被聚焦(键盘导航时shiftFocus会先判断候选年份是否越界,见 YearRangePickerCellTrigger.vue)。
  • maximumYears限制区间跨度的最大年份数。在 useRangeYearPicker.ts 中可以看到,当startend都确定后,超出start ± (maximumYears - 1)范围的年份会被判为禁用;当只选中start时,focusedValue的预览高亮也会被anchor.add({ years: maximumYears - 1 })封顶,避免预览出超长区间。
  • fixedDatemaximumYears配合:固定起始年后,end只能在start + (maximumYears - 1)范围内向后延伸;固定结束年则相反。交互上,选中完整区间后再次点击时,fixedDate决定"重开哪个端点"(详见 CellTrigger 中changeYear的分支逻辑)。

禁用与不可用:isYearDisabled / isYearUnavailable

  • isYearDisabled表示"硬禁用":禁用年份不可点击、不可聚焦、键盘导航会跳过。
  • isYearUnavailable表示"软不可用":通常用于表达业务上暂时不可选的年份(如尚未发生的未来年度)。
  • 二者对区间有效性的影响不同:若选中的起点或终点命中isYearDisabled,区间会被判为isInvalid(Root 会渲染data-invalid属性);而isYearUnavailable只影响区间中段——当区间内任意年份不可用时,highlightedRange返回null(即不可提交该区间),除非开启allowNonContiguousRanges。判断逻辑见 useRangeYearPicker.ts:areAllYearsBetweenValid(start, end, allowNonContiguousRanges ? () => false : isYearUnavailable, rangeIsYearDisabled)

交互行为:preventDeselect、allowNonContiguousRanges、readonly、disabled

  • preventDeselecttrue时,点击已选中的起点年份不会将其取消,必须先选新年份(见changeYear!rootContext.preventDeselect.value的守卫条件)。
  • allowNonContiguousRanges开启后,即使区间中间存在isYearUnavailable的年份,也允许该区间通过预览高亮与选中(前提是端点本身有效)。
  • readonly只读模式下所有选择操作被拦截(changeYear第一步即检查readonly),但仍可展示数据;disabled则禁用整个交互且视觉上呈现禁用态。

渲染控制:as / asChild 与 yearsPerPage / nextPage / prevPage

  • as默认渲染为<div>,可通过as覆盖或asChild将根节点替换为任意子元素,用于与组件库组合(Composition 模式)。
  • yearsPerPage控制每页年份数量(默认 12),网格行数由子组件按 4 列排布得出;nextPage/prevPage允许自定义翻页算法,例如跳过某些年份段,返回值为新的DateValue

无障碍与国际化:calendarLabel、locale、dir

  • calendarLabel作为日历整体可访问标签,Root 会在渲染时把它写入aria-label,同时内部还维护一个视觉隐藏的role="heading"标题用于屏幕阅读器朗读当前页(见模板中fullCalendarLabel的用法)。
  • locale决定年份文本的本地化格式(如中文"二〇二四年"、英文"2024");未指定时跟随全局ConfigProvider或浏览器默认。
  • dir支持ltr/rtl,影响键盘方向键导航的语义(RTL 下左右箭头方向翻转,见handleArrowKeysign的计算)。

事件(Emits)

事件名说明回调参数
update:modelValue每当模型值(选中区间)变化时触发[date: DateRange]
update:placeholder每当占位日期变化时触发[date: DateValue]
update:startValue每当起始值变化时触发[date: DateValue]
  • update:modelValuev-model自动监听,通常无需手动处理;在源码中,该事件由内部watch([startValue, endValue])驱动:当start/end都确定时,会按年份先后自动规整为{ start: 较小年, end: 较大年 },保证区间始终正向有序。
  • update:placeholder用于受控占位日期;结合:placeholder.sync可在组件外部控制当前页码。
  • update:startValue是一个便捷事件,方便在仅关心区间起点(例如级联联动到其他组件)时避免解构整个DateRange
  • 交互中途按 ESC 会回滚到上一次合法的modelValue(Root 的keydown监听基于isEditing状态实现),避免半成品区间污染受控值。

作用域插槽(Slots)

插槽名说明插槽 props
date当前占位日期DateValue
grid年份网格Grid<DateValue>
locale日历语言环境string
modelValue当前日期区间DateRange

在官方示例中最常用的是grid插槽:通过v-slot="{ grid }"解构出grid.rows,外层用v-for渲染YearRangePickerGridRow,内层再遍历每行的年份数组渲染YearRangePickerCellgrid.rows的数据结构为二维数组,每行包含若干个DateValue,其分页与每页行数由yearsPerPage决定。

数据属性(Data Attributes)与键盘交互

尽管 Root 自身只渲染data-readonlydata-disableddata-invalid三个状态属性,但其子组件YearRangePickerCellTrigger会依据 Root 上下文输出完整的状态属性,用于样式定制(见 YearRangePickerCellTrigger.vue):

  • data-selected/data-selection-start/data-selection-end:选中态与区间端点;
  • data-highlighted/data-highlighted-start/data-highlighted-end:悬停预览高亮态(配合focusedValue);
  • data-disabled/data-unavailable:禁用与不可用态;
  • data-today:当前年份(年份选择器中指"今年");
  • data-focused:当前占位年份,同时对应tabindex="0"的可聚焦项。

键盘交互同样由 CellTrigger 完成,包括方向键逐格移动、PageUp/PageDown整页翻动、Enter/Space选中、越界自动翻页与跳过禁用项等,整套行为与无障碍规范保持一致。Root 提供headingIdfullCalendarLabel等上下文,保证屏幕阅读器可朗读当前页标题。

完整可运行示例:带约束的年份区间筛选器

综合以上能力,实现一个"近十年可选、最长 5 年、禁止未来年度"的筛选器:

<script setup lang="ts"> import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date' const currentYear = today(getLocalTimeZone()).year // 禁用未来年份:isYearUnavailable 命中未来年度 const isYearUnavailable = (date: CalendarDate) => date.year > currentYear // 硬性边界:最早 2010 年 const minValue = new CalendarDate(2010, 1, 1) </script> <template> <YearRangePickerRoot v-slot="{ grid }" :min-value="minValue" :is-year-unavailable="isYearUnavailable" :maximum-years="5" years-per-page="16" locale="zh-CN" > <YearRangePickerHeader> <YearRangePickerPrev>‹</YearRangePickerPrev> <YearRangePickerHeading /> <YearRangePickerNext>›</YearRangePickerNext> </YearRangePickerHeader> <YearRangePickerGrid> <YearRangePickerGridBody> <YearRangePickerGridRow v-for="(years, index) in grid.rows" :key="`year-${index}`"> <YearRangePickerCell v-for="year in years" :key="year.toString()" :date="year"> <YearRangePickerCellTrigger :year="year" /> </YearRangePickerCell> </YearRangePickerGridRow> </YearRangePickerGridBody> </YearRangePickerGrid> </YearRangePickerRoot> </template>

该示例中,maximum-years="5"会阻止用户选择跨度超过 5 年的区间,is-year-unavailable会让未来年份显示删除线且不可点选,minValue则把最早可选年份限制在 2010 年。

小结

YearRangePickerRoot 是 radix-vue / Reka UI 年份区间选择能力的入口与状态核心:通过modelValue/defaultValue管理受控与非受控值,通过isYearDisabled/isYearUnavailable/maximumYears/fixedDate/minValue/maxValue组合出精细的可选区间约束,再借助grid作用域插槽与 9 个配套子组件完成完整的网格渲染与无障碍键盘交互。掌握其 Props、事件与插槽语义后,即可在任意 Vue 3 项目中快速构建专业、可访问的年份范围选择界面。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 19:32:41

第一次用FluidVoice听写?这份保姆级上手清单帮你避坑

第一次用FluidVoice听写&#xff1f;这份保姆级上手清单帮你避坑 【免费下载链接】FluidVoice Fastest and only macOS Dictation app with on-device STT and custom trained AI enhancement model. Windows pre-build available! A local Wispr Flow alternative. DM us on X…

作者头像 李华
网站建设 2026/9/17 19:28:34

在 Linux 上像原生一样跑 Windows 应用:WinApps 5 分钟上手

在 Linux 上像原生一样跑 Windows 应用&#xff1a;WinApps 5 分钟上手 【免费下载链接】winapps Run Windows apps such as Microsoft Office/Adobe in Linux (Ubuntu/Fedora) and GNOME/KDE as if they were a part of the native OS, including Nautilus integration. Hard…

作者头像 李华