Calendar 年份下拉范围改为按 fromDate/toDate 推导 (WEB-1312)
版本: 0.9.1 · 类型: 🐛 Bug 修复
问题
Calendar / DatePicker 的年份下拉始终列出「当前年 ± 50」,完全无视调用方传入的 fromDate / toDate。比如传 fromDate={今天} 想把可选范围限制在今天及以后,年份下拉里却仍然列着几十年的历史年份,看起来可以选——选中后日历确实会跳过去,但当月格子全部被禁用,用户体验上是明显的 bug。
DatePicker 的 disabledDate(透传成 Calendar 的 disabled matcher)存在同样的问题:design-site「Disabled Dates」示例用的是 disabledDate={{ before: new Date() }},年份下拉里仍然列出回溯到 1970 的历史年份,看起来可以选。
翻页箭头存在同样的盲区:此前完全没有边界感知,传 fromDate={今天}(或等价的 disabled={{ before: 今天 }})时,上一月箭头仍能一路点回 1969 年之前——比刚修完的年份下拉范围还要越界。
根因
fromDate / toDate 在 Calendar.tsx 里只被用来禁用单个日期格子。CalendarMonthCaption 的年份列表来自模块级常量(当前年 ± 50),完全不知道这两个边界的存在;月份下拉同样没有边界感知。
disabled 的 { before } / { after } matcher 形式在语义上与 fromDate / toDate 完全等价(见 Calendar.tsx 的 matchDate),但从未接入同一套推导逻辑,所以只要调用方用 disabledDate 而不是 fromDate/toDate,同样的年份范围异常就会重现。
翻页箭头的 goToPrevMonth / goToNextMonth 完全没有边界判断——不管 minDate / maxDate 是什么,永远无条件把 currentMonth 平移一个月。
改动文件
packages/design/src/components/Calendar/CalendarMonthCaption.tsxpackages/design/src/components/Calendar/Calendar.tsxpackages/design/src/components/Calendar/styles.tspackages/design/src/components/Calendar/__tests__/Calendar.test.tsx
改动内容
CalendarMonthCaption现在从fromDate推导年份下拉的下界(未传时回退到新增的DEFAULT_START_YEAR,即1970),从toDate推导上界(未传时回退到DEFAULT_END_YEAR,即「当前年 + 50」),按此裁剪列表而不是禁用条目。若算出的下界高于上界,会退化成只保留下界这一年,不抛错、也不会渲染出空列表。- 在边界年份,月份下拉会禁用早于
fromDate所在月(或晚于toDate所在月)的项,但仍保持 12 项全部渲染,不裁剪列表位置。 - 选中年份时,会先把目标月份 clamp 到该年份的合法范围内,再调用
goToMonth,确保切到边界年份不会落进一个全禁用的月份。 Calendar把fromDate/toDate透传给CalendarMonthCaption(此前仅用于日期格子禁用)。- 新增单测覆盖:默认 1970 下界、
fromDate/toDate推导出的范围边界、边界年份的月份禁用、切换年份时的月份 clamp。 Calendar新增resolveSelectableBounds辅助函数,把disabledmatcher 也归一进同一套推导:{ before }/{ after }(单个或数组)会被折算成传给CalendarMonthCaption的同一对minDate/maxDate。由于多个 matcher 之间是「任一命中即禁用」的并集,可选范围就是各约束的交集——下界取fromDate与所有{ before }值中的最大值,上界取toDate与所有{ after }值中的最小值。非单调 matcher(裸Date、{ from, to }、函数式)无法推导出可靠边界,有意忽略。- 新增单测覆盖
disabledmatcher 驱动的边界:单个{ before }/{ after }、数组同时收敛上下界、fromDate/toDate与disabled冲突时取更严的一方、非单调 matcher(函数、裸Date、{ from, to })不影响默认的 1970 下界。 - 翻页箭头新增边界感知:新增
toYearMonthIndex辅助函数按「年月粒度」比较日期,显示的月份范围到达归一后的minDate/maxDate时自动禁用——上一月箭头在第一个显示月已到达或早于minDate所在月时禁用;下一月箭头在最后一个显示月(addMonths(currentMonth, numberOfMonths - 1),兼容RangePicker双日历场景)已到达或晚于maxDate所在月时禁用。所有导航箭头(含navPerMonth模式下两个日历各自的箭头)共用同一对禁用状态,因为它们都作用于同一个currentMonth位移。 goToPrevMonth/goToNextMonth新增早退守卫,各自越界时直接返回,防止键盘或编程式调用绕过按钮的disabled属性。CALENDAR_NAV_BUTTON_CLASS新增disabled:cursor-not-allowed、disabled:text-(--Labels-Disabled),以及disabled:hover:bg-transparent disabled:hover:text-(--Labels-Disabled)压掉禁用态下仍会漏出的 hover 样式(做法与 Button 组件的禁用态一致)。- 新增单测覆盖翻页箭头越界禁用:无约束时的基线表现、
fromDate/toDate落在当前显示月、disabled={{ before }}matcher 与fromDate表现一致、点击已禁用箭头不触发onMonthChange、numberOfMonths={2}时禁用判据基于最后一个显示月而非第一个、未越界时箭头保持可点击。
影响
- 传了
fromDate/toDate的调用方,年份下拉现在会与实际可选范围一致。 - 未传
fromDate/toDate的调用方,上界仍是「当前年 + 50」不变;下界从跟随当前年浮动的「当前年 - 50」改为固定的1970。 - 使用
disabledDate(Calendar的disabledprop)传入{ before }/{ after }matcher 的调用方,年份/月份下拉现在与fromDate/toDate一样能正确收敛,包括 design-site 的「Disabled Dates」示例。 - 翻页箭头现在无法点击超出
minDate/maxDate的范围,无论该边界来自fromDate/toDate还是disabled={{ before }}/{ after }matcher。