diff --git a/.changeset/allow-undefined-classnames.md b/.changeset/allow-undefined-classnames.md new file mode 100644 index 0000000000..03990473f8 --- /dev/null +++ b/.changeset/allow-undefined-classnames.md @@ -0,0 +1,6 @@ +--- +"react-day-picker": patch +"@daypicker/react": patch +--- + +fix: allow `undefined` in `ClassNames` values so `classNames` type-checks under `exactOptionalPropertyTypes`. diff --git a/packages/react-day-picker/src/helpers/getClassNamesForModifiers.ts b/packages/react-day-picker/src/helpers/getClassNamesForModifiers.ts index 572f1b3dea..887a2fb5f1 100644 --- a/packages/react-day-picker/src/helpers/getClassNamesForModifiers.ts +++ b/packages/react-day-picker/src/helpers/getClassNamesForModifiers.ts @@ -24,10 +24,17 @@ export function getClassNamesForModifiers( (previousValue, [key]) => { if (modifiersClassNames[key]) { previousValue.push(modifiersClassNames[key as string]); - } else if (classNames[DayFlag[key as DayFlag]]) { - previousValue.push(classNames[DayFlag[key as DayFlag]]); - } else if (classNames[SelectionState[key as SelectionState]]) { - previousValue.push(classNames[SelectionState[key as SelectionState]]); + } else { + const dayFlagClassName = classNames[DayFlag[key as DayFlag]]; + if (dayFlagClassName) { + previousValue.push(dayFlagClassName); + } else { + const selectionClassName = + classNames[SelectionState[key as SelectionState]]; + if (selectionClassName) { + previousValue.push(selectionClassName); + } + } } return previousValue; }, diff --git a/packages/react-day-picker/src/types/props.test.tsx b/packages/react-day-picker/src/types/props.test.tsx index 51f171a840..79df4c518a 100644 --- a/packages/react-day-picker/src/types/props.test.tsx +++ b/packages/react-day-picker/src/types/props.test.tsx @@ -5,7 +5,7 @@ import { getDefaultClassNames } from "../helpers/getDefaultClassNames"; import type { useDayPicker } from "../useDayPicker"; import type { DayPickerProps } from "./props"; -import type { DateRange, Matcher } from "./shared"; +import type { ClassNames, DateRange, Matcher } from "./shared"; // Keep public class name outputs assignable to string for existing consumers. void (getDefaultClassNames().root satisfies string); @@ -27,6 +27,12 @@ type DateLike = { getDate(): number; }; +// ClassNames values must include `undefined` so `Partial` accepts +// `string | undefined` under `exactOptionalPropertyTypes` (issue #3015). +void (true satisfies undefined extends ClassNames[keyof ClassNames] + ? true + : false); + const date = new Date(2024, 0, 15); const month = new Date(2024, 0, 1); const endMonth = new Date(2024, 11, 1); diff --git a/packages/react-day-picker/src/types/shared.ts b/packages/react-day-picker/src/types/shared.ts index 0da93d6d8b..e289ad8993 100644 --- a/packages/react-day-picker/src/types/shared.ts +++ b/packages/react-day-picker/src/types/shared.ts @@ -251,7 +251,7 @@ export type MonthChangeEventHandler = (month: Date) => void; * }; */ export type ClassNames = { - [key in UI | SelectionState | DayFlag | Animation]: string; + [key in UI | SelectionState | DayFlag | Animation]: string | undefined; }; /** diff --git a/packages/react-day-picker/src/useAnimation.ts b/packages/react-day-picker/src/useAnimation.ts index 81a88a5079..cd9c76a7a0 100644 --- a/packages/react-day-picker/src/useAnimation.ts +++ b/packages/react-day-picker/src/useAnimation.ts @@ -115,12 +115,12 @@ export function useAnimation( // remove animation classes from the new month snapshots const captionEl = queryCaptionEl(currentMonthElSnapshot); - if (captionEl) { + if (captionEl && captionAnimationClass) { captionEl.classList.remove(captionAnimationClass); } const weeksEl = queryWeeksEl(currentMonthElSnapshot); - if (weeksEl) { + if (weeksEl && weeksAnimationClass) { weeksEl.classList.remove(weeksAnimationClass); } }); @@ -172,12 +172,12 @@ export function useAnimation( currentMonthEl.style.position = "relative"; currentMonthEl.style.overflow = "hidden"; const captionEl = queryCaptionEl(currentMonthEl); - if (captionEl) { + if (captionEl && captionAnimationClass) { captionEl.classList.add(captionAnimationClass); } const weeksEl = queryWeeksEl(currentMonthEl); - if (weeksEl) { + if (weeksEl && weeksAnimationClass) { weeksEl.classList.add(weeksAnimationClass); } // animate new displayed month end @@ -192,10 +192,10 @@ export function useAnimation( navEl.style.zIndex = ""; } - if (captionEl) { + if (captionEl && captionAnimationClass) { captionEl.classList.remove(captionAnimationClass); } - if (weeksEl) { + if (weeksEl && weeksAnimationClass) { weeksEl.classList.remove(weeksAnimationClass); } currentMonthEl.style.position = ""; @@ -220,21 +220,23 @@ export function useAnimation( const previousCaptionEl = queryCaptionEl(previousMonthEl); if (previousCaptionEl) { - previousCaptionEl.classList.add( - isAfterPreviousMonth - ? classNames[Animation.caption_before_exit] - : classNames[Animation.caption_after_exit], - ); + const captionExitClass = isAfterPreviousMonth + ? classNames[Animation.caption_before_exit] + : classNames[Animation.caption_after_exit]; + if (captionExitClass) { + previousCaptionEl.classList.add(captionExitClass); + } previousCaptionEl.addEventListener("animationend", cleanUp); } const previousWeeksEl = queryWeeksEl(previousMonthEl); if (previousWeeksEl) { - previousWeeksEl.classList.add( - isAfterPreviousMonth - ? classNames[Animation.weeks_before_exit] - : classNames[Animation.weeks_after_exit], - ); + const weeksExitClass = isAfterPreviousMonth + ? classNames[Animation.weeks_before_exit] + : classNames[Animation.weeks_after_exit]; + if (weeksExitClass) { + previousWeeksEl.classList.add(weeksExitClass); + } } currentMonthEl.insertBefore(previousMonthEl, currentMonthEl.firstChild);