资讯动态

radix-vue MonthPickerRoot 深入解析:月份选择器根组件的 Props、事件、插槽 API 与源码级实现

发布时间:2026/9/17 16:22:58 来源:尧图企业网站定制
radix-vue MonthPickerRoot 深入解析月份选择器根组件的 Props、事件、插槽 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本篇以 radix-vue原 Radix Vue包名reka-uiMonthPicker 组件族的根组件MonthPickerRoot为核心完整解读其全部 Props、Events、Slots API并结合 MonthPickerRoot.vue 与 useMonthPicker.ts 的源码讲清 placeholder 驱动年份展示、单/多选与取消选择、min/max 范围裁剪、自定义翻页以及无障碍键盘导航的底层机制帮助你在项目中正确接入并深度定制月份选择器。MonthPickerRoot 的定位与依赖前提MonthPicker 用于呈现一个“以月份为粒度”的日历视图如选择“2025 年 3 月”而非某一天组件族整体目前标记为Alpha状态见 month-picker.md 中的 Alpha 徽标。MonthPickerRoot是整个组件树的容器与状态中枢它持有选中值modelValue、占位日期placeholder、禁用/只读/多选等状态并通过 Vue 的 provide/inject 上下文向MonthPickerHeader、MonthPickerPrev、MonthPickerNext、MonthPickerHeading、MonthPickerGrid、MonthPickerCellTrigger等子部件下发能力。从源码结构看所有子部件统一通过 index.ts 导出的injectMonthPickerRootContext()访问这些状态。MonthPicker 依赖internationalized/date包处理日期运算与格式化文档强烈建议先了解该包的使用方式并在项目中安装它组件本体从reka-ui安装以当前仓库文档给出的安装指引为准可用 npm/pnpm/yarn 等任意包管理器执行install reka-ui internationalized/date。所有日期类型DateValue、Matcher等均来自该依赖。组件组装方式与 Root 默认插槽MonthPicker 采用“组装式composable”用法MonthPickerRoot本身只渲染一个外层元素默认div全部可视结构由你在插槽内拼装。官方文档给出的标准骨架如下script setup import { MonthPickerCell, MonthPickerCellTrigger, MonthPickerGrid, MonthPickerGridBody, MonthPickerGridRow, MonthPickerHeader, MonthPickerHeading, MonthPickerNext, MonthPickerPrev, MonthPickerRoot, } from reka-ui /script template MonthPickerRoot MonthPickerHeader MonthPickerPrev / MonthPickerHeading / MonthPickerNext / /MonthPickerHeader MonthPickerGrid MonthPickerGridBody MonthPickerGridRow MonthPickerCell MonthPickerCellTrigger / /MonthPickerCell /MonthPickerGridRow /MonthPickerGridBody /MonthPickerGrid /MonthPickerRoot /template对应源码在 MonthPickerRoot.vue 的模板部分。Root 提供默认插槽暴露四个响应式变量用于自定义渲染例如按grid自己循环 12 个月份单元格插槽属性类型说明dateDateValue当前 placeholder 日期即当前展示的参考日期gridGridDateValue由 placeholder 年份生成的月份网格数据localestring当前解析后的本地化语言modelValueDateValue \| DateValue[] \| undefined当前选中值值得注意的是Root 模板在插槽之外还额外渲染了一个视觉隐藏clip 裁剪为 1px的roleheading元素内容为fullCalendarLabel用于屏幕阅读器播报见 MonthPickerRoot.vue。Props 完整参考继承自官方 API 文档以下为 MonthPickerRoot.md 中定义的MonthPickerRoot全部 PropsNameDescriptionTypeRequiredDefaultas渲染为指定元素或组件可被asChild覆盖AsTag \| ComponentNodivasChild将默认渲染元素替换为传入的子元素并合并其 props 与行为组合式写法booleanNo-calendarLabel月份选择器的可访问性标签stringNo-defaultPlaceholder默认 placeholder 日期DateValueNo-defaultValue月份选择器的默认选中值非受控模式DateValueNo-dir阅读方向省略时继承ConfigProvider全局设置或按 LTR 处理ltr \| rtlNo-disabled是否禁用月份选择器booleanNofalseinitialFocus为true时挂载后聚焦到已选月份、当月或该年第一个月booleanNofalseisMonthDisabled返回某月份是否禁用的函数MatcherNo-isMonthUnavailable返回某月份是否“不可用”如无库存的函数MatcherNo-locale用于日期格式化的 localestringNo-maxValue可选中的最大日期DateValueNo-minValue可选中的最小日期DateValueNo-modelValue受控的选中月份值支持v-model绑定DateValue \| DateValue[] \| nullNo-multiple是否允许多选月份booleanNofalsenextPage返回“下一页”的函数参数为当前 placeholder(placeholder: DateValue) DateValueNo-placeholderplaceholder 日期用于决定未选中时展示哪一年DateValueNo-preventDeselect是否禁止“不先选另一个就取消已选日期”booleanNofalseprevPage返回“上一页”的函数参数为当前 placeholder(placeholder: DateValue) DateValueNo-readonly是否只读booleanNo-这些默认值与 MonthPickerRoot.vue 中withDefaults的定义一一对应as: div、preventDeselect: false、multiple: false、disabled: false、readonly: false、initialFocus: false等。受控与非受控模式modelValue 与 defaultValueMonthPickerRoot同时支持受控controlled与非受控uncontrolled两种用法。在 MonthPickerRoot.vue 中可以看到其实现基于vueuse/core的useVModelconst modelValue useVModel(props, modelValue, emits, { defaultValue: defaultValue.value, passive: (props.modelValue undefined) as false, })非受控不传modelValue只传defaultValue。此时passive为true组件内部自维护选中状态外部通过update:modelValue事件监听变化。受控传入modelValue可用v-model绑定组件状态完全由外部驱动。支持multiple多选时modelValue类型为DateValue[]。Events 与 placeholder 的双向绑定MonthPickerRoot声明了两个事件定义见 MonthPickerRoot.vueNameDescriptionTypeupdate:modelValue选中值变化时触发[date: DateValue \| DateValue[]]update:placeholderplaceholder 变化时触发[date: DateValue]placeholder同样支持v-model:placeholder绑定。其默认值解析链路为defaultPlaceholder ?? getDefaultDate(...)即若未显式传入则以defaultValue/modelValue或今天作为占位日期见 MonthPickerRoot.vue。placeholder 是 MonthPicker 的核心概念它决定当前网格展示哪一年。源码中有两处自动推进 placeholder 的机制外部改选中值时MonthPickerRoot.vue 中watch(modelValue, ...)会发现选中新值与 placeholder 不在同一年isSameYearMonth判断失败时自动把 placeholder 同步到新值从而翻到对应年份。多选时取数组最后一个值_modelValue.at(-1)。翻页时nextPage/prevPage在重建网格的同时保留 placeholder 的“月与日”只替换年份见 useMonthPicker.ts。另外useMonthPicker.ts 中还有一个watch(props.placeholder)当 placeholder 的年份与当前网格年份不一致时重新生成月份网格watch(props.locale)则会在语言切换时重建网格使月份名称按新 locale 渲染。选择行为单月选择、多选与 preventDeselect点击/键盘选择某个月份最终都汇入onMonthChangeMonthPickerRoot.vue其完整逻辑如下function onMonthChange(value: DateValue) { if (!multiple.value) { if (!modelValue.value) { modelValue.value resolveMonthValue(value, placeholder.value) return } if (!preventDeselect.value isSameYearMonth(modelValue.value as DateValue, value)) { placeholder.value resolveMonthValue(value, modelValue.value as DateValue) modelValue.value undefined } else { modelValue.value resolveMonthValue(value, modelValue.value as DateValue) } } else if (!modelValue.value) { modelValue.value [resolveMonthValue(value, placeholder.value)] } else { const modelValueArray Array.isArray(modelValue.value) ? modelValue.value : [modelValue.value] const index modelValueArray.findIndex(date isSameYearMonth(date, value)) if (index -1) { modelValue.value [...modelValueArray, resolveMonthValue(value, placeholder.value)] } else if (!preventDeselect.value) { const next modelValueArray.filter(date !isSameYearMonth(date, value)) if (!next.length) { placeholder.value resolveMonthValue(value, modelValueArray[index]) modelValue.value undefined return } modelValue.value next.map(date date.copy()) } } }要点拆解单选取反单选模式下再次点击已选月份会将其清空modelValue undefined但若设置preventDeselect: true则保留选择——这正是preventDeselect的语义“不先选另一个日期就不得取消当前选择”。多选去重多选模式下点击已选月份会从数组中移除该月若移除后数组为空且未设置preventDeselect整体选中值同样被清空。resolveMonthValue的日保留策略MonthPickerRoot.vue月份选择器最终仍输出DateValue因此新选月份的“日”会继承参考值placeholder 或原选中值的day保证v-model的值可直接用于日期型表单。取消选择时的 placeholder 回写注意源码在取消选择时把 placeholder 设为被取消月份的原始值避免网格年份发生跳变。选择范围限制minValue / maxValue 的月份级语义minValue/maxValue虽然接收“日”粒度的DateValue但在 MonthPicker 中按月份边界裁剪。核心在 useMonthPicker.tsfunction isMonthDisabled(dateObj: DateValue) { if (resolveMatcher(props.isMonthDisabled)?.(dateObj) || props.disabled.value) return true if (props.maxValue.value isAfter(dateObj.set({ day: 1 }), props.maxValue.value)) return true if (props.minValue.value isBefore(endOfMonth(dateObj), props.minValue.value)) return true return false }也就是说某月 1 号晚于maxValue或该月最后一天早于minValue整月不可选props.disabled为true时全部月份禁用。同样的边界判断也作用于键盘导航MonthPickerCellTrigger的shiftFocus中用endOfMonth/set({ day: 1 })比较并驱动前后翻页按钮的禁用状态const isNextButtonDisabled (nextPageFunc?: (date: DateValue) DateValue) { if (!props.maxValue.value) return false if (props.disabled.value) return true const currentDate grid.value.value if (nextPageFunc || props.nextPage.value) { const nextDate (nextPageFunc || props.nextPage.value)!(currentDate) return isAfter(nextDate.set({ month: 1, day: 1 }), props.maxValue.value) } const nextYear currentDate.add({ years: 1 }).set({ month: 1, day: 1 }) return isAfter(nextYear, props.maxValue.value) }useMonthPicker.ts。isPrevButtonDisabled为对称实现。从源码结构看若设置了minValue/maxValue翻页按钮会在“下一页上一年整体越界”时自动禁用形成天然的年份翻页边界。此外useMonthPickerState会根据isMonthDisabled/isMonthUnavailable计算isInvalid当外部以受控方式传入的选中值落在被禁用/不可用月份上时Root 会渲染data-invalid属性便于样式层给出警示外观实现见 useMonthPicker.ts。自定义翻页nextPage / prevPage默认翻页步长是±1 年currentDate.add({ years: 1 })/subtract({ years: 1 })见 useMonthPicker.ts。当你的业务需要跨季度、跨固定年份窗口或非线性跳页时可传入nextPage/prevPage函数template MonthPickerRoot v-modelselected :next-page(p) p.add({ years: 3 }) :prev-page(p) p.subtract({ years: 3 }) !-- ... -- /MonthPickerRoot /template函数接收当前 placeholder返回新的DateValue组件据此重建网格并同步 placeholder 年份。更细粒度的覆盖发生在按钮层面MonthPickerPrev/MonthPickerNext各自暴露prevPage/nextPageprop会在点击时优先于 Root 上的同名函数生效见 MonthPickerPrev.vue 中rootContext.prevPage(props.prevPage)的调用方式以及disabled计算里传入局部prevPage的逻辑。无障碍与键盘交互MonthPicker 的无障碍实现贯穿 Root 与网格各层Root渲染:aria-labelfullCalendarLabel其值为${calendarLabel ?? Month Picker}, ${headingValue}年份标题由formatter.fullYear生成见 useMonthPicker.ts因此calendarLabelprop 直接影响读屏播报。Gridtable元素roleapplication、tabindex-1并通过aria-labelledby指向 Heading 的headingIdMonthPickerGrid.vue。CellTriggerrolebutton、aria-label为“长月份名 数字年份”如March 2025采用 roving tabindex——仅聚焦的月份data-focused持有tabindex0其余为-1。官方文档month-picker.md定义的键盘交互如下按键行为Tab焦点移入月份选择器时聚焦第一个导航按钮Space焦点在MonthPickerNext/MonthPickerPrev上时翻页否则选中当前月份Enter同上ArrowLeft/ArrowRight/ArrowUp/ArrowDown在MonthPickerCellTrigger上移动月份焦点左右 ±1 个月上下 ±4 个月必要时跨年翻页PageUp焦点在同月的上一年PageDown焦点在同月的下一年这套导航的底层实现在 MonthPickerCellTrigger.vue 的handleArrowKey方向键通过shiftFocus计算候选月份并querySelector([data-value...])定位新焦点找不到说明跨年到下一张网格时调用rootContext.nextPage()/prevPage()翻页后在nextTick中递归重试递归深度上限 48 次防止死循环候选格带data-disabled时会继续向同方向寻找可用月份。PageUp/PageDown则由shiftFocusYear实现。RTL 语言下左右箭头方向会翻转sign dir rtl ? -1 : 1对应dirprop 的作用。initialFocus为true时挂载后调用handleCalendarInitialFocus(parentElement)完成首次聚焦MonthPickerRoot.vue聚焦优先级为已选月份 当月 该年第一个月。样式钩子data-* 属性清单配合v-bind或选择器定制样式时可依赖以下数据属性来源month-picker.md 的 DataAttributesTable 与各组件模板MonthPickerRoot渲染于根元素属性出现条件[data-readonly]只读时[data-disabled]禁用时[data-invalid]选中值无效时见前文isInvalidMonthPickerGrid[data-readonly]、[data-disabled]。MonthPickerPrev / Next / Heading[data-disabled]按钮不可点时。MonthPickerCell[data-disabled]并附带rolegridcell、aria-selected、aria-disabled。MonthPickerCellTrigger最丰富的一组见 MonthPickerCellTrigger.vue属性出现条件[data-selected]该月被选中[data-value]该月日期的 ISO 字符串[data-disabled]被禁用[data-unavailable]不可用如库存不足[data-today]为当前月份[data-focused]持有焦点MonthPickerCellTrigger的默认插槽还会输出monthValue短月份名、disabled、selected、today、unavailable五个变量可用于自定义单元格内容。实现细节与测试验证从源码结构看MonthPickerRoot的职责边界非常清晰状态与上下文createContext(MonthPickerRoot)创建 provide/inject 对MonthPickerRoot.vue子部件零通信成本日期域逻辑网格构建createMonthGrid、禁用判定、翻页、标题格式化全部收敛在 useMonthPicker.ts与 Vue 模板解耦便于单测行为验证仓库提供了 MonthPicker.test.ts 作为组件行为的自动化验证入口可结合阅读确认翻页、选择、键盘导航的实际表现。适用前提与限制小结MonthPicker 目前为 Alpha 组件API 可能随版本演进日期值统一使用internationalized/date的DateValue与原生 JSDate不直接互换minValue/maxValue、isMonthDisabled等约束作用于“整月”跨月日期如只允许 3 月 15 日之后会被整月级判断近似处理。综上MonthPickerRoot以一份完整的 Props/Events/Slots API 覆盖受控与自管理两种模式并通过 placeholder 机制、onMonthChange选择状态机和 contenteditable="false">【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价