diff --git a/src/DayPicker.tsx b/src/DayPicker.tsx index 8a176bb14..5c5e6f9bb 100644 --- a/src/DayPicker.tsx +++ b/src/DayPicker.tsx @@ -11,7 +11,6 @@ import type { CalendarDay } from "./classes/CalendarDay.js"; import { getClassNamesForModifiers } from "./helpers/getClassNamesForModifiers.js"; import { getComponents } from "./helpers/getComponents.js"; import { getDataAttributes } from "./helpers/getDataAttributes.js"; -import { getDateLib } from "./helpers/getDateLib.js"; import { getDefaultClassNames } from "./helpers/getDefaultClassNames.js"; import { getFormatters } from "./helpers/getFormatters.js"; import { getMonthOptions } from "./helpers/getMonthOptions.js"; @@ -19,8 +18,8 @@ import { getStyleForModifiers } from "./helpers/getStyleForModifiers.js"; import { getWeekdays } from "./helpers/getWeekdays.js"; import { getYearOptions } from "./helpers/getYearOptions.js"; import * as defaultLabels from "./labels/index.js"; -import type { FormatOptions, LabelOptions } from "./lib/dateLib.js"; -import { enUS } from "./lib/locales.js"; +import { DateLib } from "./lib/dateLib.js"; +import { defaultLocale } from "./lib/locales.js"; import type { DayPickerProps, Modifiers, @@ -45,28 +44,43 @@ import { isDateRange } from "./utils/typeguards.js"; */ export function DayPicker(props: DayPickerProps) { const { components, formatters, labels, dateLib, locale, classNames } = - useMemo( - () => ({ - dateLib: getDateLib(props.dateLib), + useMemo(() => { + const locale = { ...defaultLocale, ...props.locale }; + + const dateLib = new DateLib( + { + locale, + weekStartsOn: props.weekStartsOn, + firstWeekContainsDate: props.firstWeekContainsDate, + useAdditionalWeekYearTokens: props.useAdditionalWeekYearTokens, + useAdditionalDayOfYearTokens: props.useAdditionalDayOfYearTokens + }, + props.dateLib + ); + + return { + dateLib, components: getComponents(props.components), formatters: getFormatters(props.formatters), labels: { ...defaultLabels, ...props.labels }, - locale: { ...enUS, ...props.locale }, + locale, classNames: { ...getDefaultClassNames(), ...props.classNames } - }), - [ - props.classNames, - props.components, - props.dateLib, - props.formatters, - props.labels, - props.locale - ] - ); + }; + }, [ + props.classNames, + props.components, + props.dateLib, + props.firstWeekContainsDate, + props.formatters, + props.labels, + props.locale, + props.useAdditionalDayOfYearTokens, + props.useAdditionalWeekYearTokens, + props.weekStartsOn + ]); const { captionLayout, - firstWeekContainsDate, mode, onDayBlur, onDayClick, @@ -77,22 +91,9 @@ export function DayPicker(props: DayPickerProps) { onNextClick, onPrevClick, showWeekNumber, - styles, - useAdditionalDayOfYearTokens, - useAdditionalWeekYearTokens, - weekStartsOn + styles } = props; - const formatOptions: FormatOptions = { - locale, - weekStartsOn, - firstWeekContainsDate, - useAdditionalWeekYearTokens, - useAdditionalDayOfYearTokens - }; - - const labelOptions: LabelOptions = formatOptions; - const { formatCaption, formatDay, @@ -144,15 +145,8 @@ export function DayPicker(props: DayPickerProps) { } = labels; const weekdays = useMemo( - () => - getWeekdays( - locale, - props.weekStartsOn, - props.ISOWeek, - props.timeZone, - dateLib - ), - [dateLib, locale, props.ISOWeek, props.timeZone, props.weekStartsOn] + () => getWeekdays(dateLib, props.ISOWeek, props.timeZone), + [dateLib, props.ISOWeek, props.timeZone] ); const isInteractive = mode !== undefined || onDayClick !== undefined; @@ -317,7 +311,6 @@ export function DayPicker(props: DayPickerProps) { navStart, navEnd, formatters, - locale, dateLib ); @@ -373,7 +366,7 @@ export function DayPicker(props: DayPickerProps) { captionLayout === "dropdown-years" ? ( {formatCaption( calendarMonth.date, - formatOptions, + dateLib.options, dateLib )} @@ -406,7 +399,7 @@ export function DayPicker(props: DayPickerProps) { role="grid" aria-multiselectable={mode === "multiple" || mode === "range"} aria-label={ - labelGrid(calendarMonth.date, labelOptions, dateLib) || + labelGrid(calendarMonth.date, dateLib.options, dateLib) || undefined } className={classNames[UI.MonthGrid]} @@ -419,7 +412,7 @@ export function DayPicker(props: DayPickerProps) { > {showWeekNumber && ( - {formatWeekdayName(weekday, formatOptions, dateLib)} + {formatWeekdayName(weekday, dateLib.options, dateLib)} ))} @@ -515,7 +508,7 @@ export function DayPicker(props: DayPickerProps) { ? labelGridcell( date, modifiers, - labelOptions, + dateLib.options, dateLib ) : undefined; @@ -555,7 +548,7 @@ export function DayPicker(props: DayPickerProps) { aria-label={labelDayButton( date, modifiers, - labelOptions, + dateLib.options, dateLib )} onClick={handleDayClick(day, modifiers)} @@ -571,10 +564,10 @@ export function DayPicker(props: DayPickerProps) { modifiers )} > - {formatDay(date, formatOptions, dateLib)} + {formatDay(date, dateLib.options, dateLib)} ) : ( - formatDay(day.date, formatOptions, dateLib) + formatDay(day.date, dateLib.options, dateLib) )} ); diff --git a/src/classes/CalendarDay.ts b/src/classes/CalendarDay.ts index 86eb54359..ff3455554 100644 --- a/src/classes/CalendarDay.ts +++ b/src/classes/CalendarDay.ts @@ -1,5 +1,4 @@ -import type { DateLib } from "../lib/index.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib } from "../lib/dateLib.js"; /** * Represent the day displayed in the calendar. @@ -13,7 +12,7 @@ export class CalendarDay { date: Date, displayMonth: Date, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = new DateLib() ) { this.date = date; this.displayMonth = displayMonth; diff --git a/src/formatters/formatCaption.test.ts b/src/formatters/formatCaption.test.ts index 2606958ac..5f2edc67d 100644 --- a/src/formatters/formatCaption.test.ts +++ b/src/formatters/formatCaption.test.ts @@ -1,18 +1,27 @@ import { es } from "date-fns/locale/es"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib"; +import { defaultLocale } from "../lib/locales.js"; import { formatCaption } from "./formatCaption"; const date = new Date(2022, 10, 21); test("should return the formatted caption", () => { - expect(formatCaption(date, {}, dateLib)).toEqual("November 2022"); + expect( + formatCaption(date, {}, new DateLib({ locale: defaultLocale })) + ).toEqual("November 2022"); }); describe("when a locale is passed in", () => { test("should format using the locale", () => { - expect(formatCaption(date, { locale: es }, dateLib)).toEqual( + expect(formatCaption(date, { locale: es })).toEqual("noviembre 2022"); + }); +}); + +describe("when a locale is passed in through date lib", () => { + test("should format using the locale", () => { + expect(formatCaption(date, {}, new DateLib({ locale: es }))).toEqual( "noviembre 2022" ); }); diff --git a/src/formatters/formatCaption.ts b/src/formatters/formatCaption.ts index edba56990..42e3a7c07 100644 --- a/src/formatters/formatCaption.ts +++ b/src/formatters/formatCaption.ts @@ -1,8 +1,4 @@ -import { - FormatOptions, - dateLib as defaultDateLib, - type DateLib -} from "../lib/index.js"; +import { DateLib, type FormatOptions } from "../lib/dateLib.js"; /** * Format the caption of the month. @@ -15,9 +11,9 @@ export function formatCaption( month: Date, options?: FormatOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - return dateLib.format(month, "LLLL y", options); + return dateLib.format(month, "LLLL y"); } /** diff --git a/src/formatters/formatDay.ts b/src/formatters/formatDay.ts index 858f14b2c..82072dd04 100644 --- a/src/formatters/formatDay.ts +++ b/src/formatters/formatDay.ts @@ -1,5 +1,4 @@ -import type { FormatOptions, DateLib } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, type FormatOptions } from "../lib/dateLib.js"; /** * Format the day date shown in the day cell. @@ -12,7 +11,7 @@ export function formatDay( date: Date, options?: FormatOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - return dateLib.format(date, "d", options); + return dateLib.format(date, "d"); } diff --git a/src/formatters/formatMonthDropdown.ts b/src/formatters/formatMonthDropdown.ts index b7773faca..02be49a9d 100644 --- a/src/formatters/formatMonthDropdown.ts +++ b/src/formatters/formatMonthDropdown.ts @@ -1,5 +1,5 @@ import type { DateFnsMonth } from "../lib/dateLib.js"; -import { enUS } from "../lib/locales.js"; +import { defaultLocale } from "../lib/locales.js"; /** * Format the month number for the dropdown option label. @@ -12,7 +12,7 @@ export function formatMonthDropdown( /** The month number to format. */ monthNumber: number, /** The locale to use for formatting. */ - locale = enUS + locale = defaultLocale ): string { return locale.localize?.month(monthNumber as DateFnsMonth); } diff --git a/src/formatters/formatWeekdayName.ts b/src/formatters/formatWeekdayName.ts index f29fc140d..6685800be 100644 --- a/src/formatters/formatWeekdayName.ts +++ b/src/formatters/formatWeekdayName.ts @@ -1,5 +1,4 @@ -import type { FormatOptions, DateLib } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, type FormatOptions } from "../lib/dateLib.js"; /** * Format the weekday name to be displayed in the weekdays header. @@ -12,7 +11,7 @@ export function formatWeekdayName( weekday: Date, options?: FormatOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - return dateLib.format(weekday, "cccccc", options); + return dateLib.format(weekday, "cccccc"); } diff --git a/src/helpers/getDateLib.ts b/src/helpers/getDateLib.ts deleted file mode 100644 index 2251d8d87..000000000 --- a/src/helpers/getDateLib.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { dateLib, type DateLib } from "../lib/dateLib.js"; -import type { DayPickerProps } from "../types/index.js"; - -export function getDateLib(customLib: DayPickerProps["dateLib"]): DateLib { - return { - ...dateLib, - ...customLib - }; -} diff --git a/src/helpers/getDates.test.ts b/src/helpers/getDates.test.ts index 40cd1a05b..05fdaabef 100644 --- a/src/helpers/getDates.test.ts +++ b/src/helpers/getDates.test.ts @@ -1,4 +1,4 @@ -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { getDates } from "./getDates"; @@ -13,7 +13,7 @@ describe("when the first month and the last month are the same", () => { { fixedWeeks: false }, - dateLib + DateLib.fromOptionsDefaultLocale({}) ); expect(dates).toHaveLength(42); expect(dates[0]).toEqual(new Date(2023, 10, 26)); @@ -28,7 +28,7 @@ describe("when the first month and the last month are the same", () => { { fixedWeeks: true }, - dateLib + DateLib.fromOptionsDefaultLocale({}) ); expect(dates).toHaveLength(42); expect(dates[0]).toEqual(new Date(2023, 10, 26)); @@ -46,7 +46,7 @@ describe("when the first month and the last month are the same", () => { { fixedWeeks: false }, - dateLib + DateLib.fromOptionsDefaultLocale({}) ); expect(dates).toHaveLength(35); expect(dates[0]).toEqual(new Date(2023, 3, 30)); @@ -61,7 +61,7 @@ describe("when the first month and the last month are the same", () => { { fixedWeeks: true }, - dateLib + DateLib.fromOptionsDefaultLocale({}) ); expect(dates).toHaveLength(42); expect(dates[0]).toEqual(new Date(2023, 3, 30)); @@ -75,10 +75,8 @@ describe("when the first month and the last month are the same", () => { const dates = getDates( [month], undefined, - { - weekStartsOn: 1 - }, - dateLib + {}, + DateLib.fromOptionsDefaultLocale({ weekStartsOn: 1 }) ); expect(dates[0]).toBeMonday(); expect(dates[0]).toEqual(new Date(2023, 4, 1)); @@ -90,7 +88,12 @@ describe("when the first month and the last month are the same", () => { const maxDate = new Date(2023, 4, 15); it("the last day should be the max date", () => { - const dates = getDates([month], maxDate, { weekStartsOn: 1 }, dateLib); + const dates = getDates( + [month], + maxDate, + {}, + DateLib.fromOptionsDefaultLocale({ weekStartsOn: 1 }) + ); expect(dates).toHaveLength(15); expect(dates[dates.length - 1]).toEqual(maxDate); }); @@ -98,7 +101,12 @@ describe("when the first month and the last month are the same", () => { describe("when using ISO weeks", () => { const month = new Date(2023, 4, 1); it("the first day should be Monday", () => { - const dates = getDates([month], undefined, { ISOWeek: true }, dateLib); + const dates = getDates( + [month], + undefined, + { ISOWeek: true }, + DateLib.fromOptionsDefaultLocale({}) + ); expect(dates[0]).toBeMonday(); expect(dates[0]).toEqual(new Date(2023, 4, 1)); expect(dates[dates.length - 1]).toEqual(new Date(2023, 5, 4)); @@ -114,10 +122,8 @@ describe("when the first month and the last month are different", () => { const dates = getDates( [firstMonth, lastMonth], undefined, - { - fixedWeeks: false - }, - dateLib + { fixedWeeks: false }, + DateLib.fromOptionsDefaultLocale({}) ); expect(dates).toHaveLength(252); expect(dates[0]).toEqual(new Date(2023, 3, 30)); @@ -133,10 +139,8 @@ describe("when the first month and the last month are different", () => { const dates = getDates( [firstMonth, lastMonth], maxDate, - { - weekStartsOn: 1 - }, - dateLib + {}, + DateLib.fromOptionsDefaultLocale({ weekStartsOn: 1 }) ); expect(dates).toHaveLength(46); expect(dates[dates.length - 1]).toEqual(maxDate); @@ -145,7 +149,12 @@ describe("when the first month and the last month are different", () => { describe("when using ISO weeks", () => { const month = new Date(2023, 4, 1); it("the first day should be Monday", () => { - const dates = getDates([month], undefined, { ISOWeek: true }, dateLib); + const dates = getDates( + [month], + undefined, + { ISOWeek: true }, + DateLib.fromOptionsDefaultLocale({}) + ); expect(dates[0]).toBeMonday(); expect(dates[0]).toEqual(new Date(2023, 4, 1)); expect(dates[dates.length - 1]).toEqual(new Date(2023, 5, 4)); diff --git a/src/helpers/getDates.ts b/src/helpers/getDates.ts index 2cf7e7209..533597c1e 100644 --- a/src/helpers/getDates.ts +++ b/src/helpers/getDates.ts @@ -7,16 +7,13 @@ const NrOfDaysWithFixedWeeks = 42; export function getDates( displayMonths: Date[], maxDate: Date | undefined, - props: Pick< - DayPickerProps, - "ISOWeek" | "fixedWeeks" | "locale" | "weekStartsOn" | "timeZone" - >, + props: Pick, dateLib: DateLib ): Date[] { const firstMonth = displayMonths[0]; const lastMonth = displayMonths[displayMonths.length - 1]; - const { ISOWeek, fixedWeeks, locale, weekStartsOn } = props ?? {}; + const { ISOWeek, fixedWeeks } = props ?? {}; const { startOfWeek, endOfWeek, @@ -31,17 +28,11 @@ export function getDates( const startWeekFirstDate = ISOWeek ? startOfISOWeek(firstMonth) - : startOfWeek(firstMonth, { - weekStartsOn, - locale - }); + : startOfWeek(firstMonth); const endWeekLastDate = ISOWeek ? endOfISOWeek(endOfMonth(lastMonth)) - : endOfWeek(endOfMonth(lastMonth), { - weekStartsOn, - locale - }); + : endOfWeek(endOfMonth(lastMonth)); const nOfDays = differenceInCalendarDays(endWeekLastDate, startWeekFirstDate); const nOfMonths = differenceInCalendarMonths(lastMonth, firstMonth) + 1; diff --git a/src/helpers/getDisplayMonths.test.ts b/src/helpers/getDisplayMonths.test.ts index 320851d3e..156722ba6 100644 --- a/src/helpers/getDisplayMonths.test.ts +++ b/src/helpers/getDisplayMonths.test.ts @@ -1,4 +1,4 @@ -import { dateLib } from "react-day-picker"; +import { DateLib } from "../lib/dateLib"; import { getDisplayMonths } from "./getDisplayMonths"; @@ -6,7 +6,7 @@ describe("getDisplayMonths", () => { it("should return the months to display in the calendar", () => { const firstMonth = new Date(2020, 0); const expectedResult = [new Date(2020, 0)]; - const result = getDisplayMonths(firstMonth, undefined, {}, dateLib); + const result = getDisplayMonths(firstMonth, undefined, {}, new DateLib()); expect(result).toEqual(expectedResult); }); @@ -23,7 +23,7 @@ describe("getDisplayMonths", () => { { numberOfMonths: 3 }, - dateLib + new DateLib() ); expect(result).toEqual(expectedResult); }); @@ -37,7 +37,7 @@ describe("getDisplayMonths", () => { { numberOfMonths: 3 }, - dateLib + new DateLib() ); expect(result).toEqual(expectedResult); }); diff --git a/src/helpers/getFocusableDate.ts b/src/helpers/getFocusableDate.ts index 1e1b03475..32389a3fa 100644 --- a/src/helpers/getFocusableDate.ts +++ b/src/helpers/getFocusableDate.ts @@ -12,10 +12,10 @@ export function getFocusableDate( refDate: Date, navStart: Date | undefined, navEnd: Date | undefined, - props: Pick, + props: Pick, dateLib: DateLib ): Date { - const { weekStartsOn, locale, ISOWeek } = props; + const { ISOWeek } = props; const { addDays, addMonths, @@ -34,11 +34,8 @@ export function getFocusableDate( month: addMonths, year: addYears, startOfWeek: (date: Date) => - ISOWeek - ? startOfISOWeek(date) - : startOfWeek(date, { locale, weekStartsOn }), - endOfWeek: (date: Date) => - ISOWeek ? endOfISOWeek(date) : endOfWeek(date, { locale, weekStartsOn }) + ISOWeek ? startOfISOWeek(date) : startOfWeek(date), + endOfWeek: (date: Date) => (ISOWeek ? endOfISOWeek(date) : endOfWeek(date)) }; let focusableDate = moveFns[moveBy](refDate, moveDir === "after" ? 1 : -1); diff --git a/src/helpers/getFormatters.test.ts b/src/helpers/getFormatters.test.ts index 7fb7baf8c..9f17bbbac 100644 --- a/src/helpers/getFormatters.test.ts +++ b/src/helpers/getFormatters.test.ts @@ -1,5 +1,5 @@ import * as defaultFormatters from "../formatters"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { getFormatters } from "./getFormatters"; @@ -20,8 +20,10 @@ test("merges custom formatters with default formatters", () => { test("assigns `formatMonthCaption` to `formatCaption` if `formatCaption` is not defined", () => { const result = getFormatters({ formatMonthCaption: () => "customMonth" }); - expect(result.formatCaption(new Date(), {}, dateLib)).toBe("customMonth"); - expect(result.formatMonthCaption(new Date(), {}, dateLib)).toBe( + expect(result.formatCaption(new Date(), {}, new DateLib())).toBe( + "customMonth" + ); + expect(result.formatMonthCaption(new Date(), {}, new DateLib())).toBe( "customMonth" ); }); @@ -31,8 +33,10 @@ test("does not overwrite `formatCaption` if already defined", () => { formatMonthCaption: () => "customMonth", formatCaption: () => "customCaption" }); - expect(result.formatCaption(new Date(), {}, dateLib)).toBe("customCaption"); - expect(result.formatMonthCaption(new Date(), {}, dateLib)).toBe( + expect(result.formatCaption(new Date(), {}, new DateLib())).toBe( + "customCaption" + ); + expect(result.formatMonthCaption(new Date(), {}, new DateLib())).toBe( "customMonth" ); }); diff --git a/src/helpers/getInitialMonth.test.ts b/src/helpers/getInitialMonth.test.ts index e208102ab..198607e12 100644 --- a/src/helpers/getInitialMonth.test.ts +++ b/src/helpers/getInitialMonth.test.ts @@ -1,5 +1,6 @@ import { addMonths, isSameMonth } from "date-fns"; -import { dateLib } from "react-day-picker"; + +import { DateLib } from "../lib/dateLib"; import { getInitialMonth } from "./getInitialMonth"; @@ -7,21 +8,21 @@ describe("when no endMonth is given", () => { describe("when month is in context", () => { const month = new Date(2010, 11, 12); it("return that month", () => { - const startMonth = getInitialMonth({ month }, dateLib); + const startMonth = getInitialMonth({ month }, new DateLib()); expect(isSameMonth(startMonth, month)).toBe(true); }); }); describe("when defaultMonth is in context", () => { const defaultMonth = new Date(2010, 11, 12); it("return that month", () => { - const startMonth = getInitialMonth({ defaultMonth }, dateLib); + const startMonth = getInitialMonth({ defaultMonth }, new DateLib()); expect(isSameMonth(startMonth, defaultMonth)).toBe(true); }); }); describe("when no month or defaultMonth", () => { const today = new Date(2010, 11, 12); it("return the today month", () => { - const startMonth = getInitialMonth({ today }, dateLib); + const startMonth = getInitialMonth({ today }, new DateLib()); expect(isSameMonth(startMonth, today)).toBe(true); }); }); @@ -32,7 +33,7 @@ describe("when endMonth is given", () => { const endMonth = addMonths(month, -2); describe("when the number of month is 1", () => { it("return the endMonth", () => { - const startMonth = getInitialMonth({ month, endMonth }, dateLib); + const startMonth = getInitialMonth({ month, endMonth }, new DateLib()); expect(isSameMonth(startMonth, endMonth)).toBe(true); }); }); @@ -40,7 +41,7 @@ describe("when endMonth is given", () => { it("return the endMonth plus the number of months", () => { const startMonth = getInitialMonth( { month, numberOfMonths: 3, endMonth }, - dateLib + new DateLib() ); const expectedMonth = addMonths(endMonth, -1 * (3 - 1)); expect(isSameMonth(startMonth, expectedMonth)).toBe(true); diff --git a/src/helpers/getMonthOptions.test.ts b/src/helpers/getMonthOptions.test.ts index a5ecc771a..6fb71862d 100644 --- a/src/helpers/getMonthOptions.test.ts +++ b/src/helpers/getMonthOptions.test.ts @@ -1,6 +1,7 @@ import { type Locale, format } from "date-fns"; import { enUS as locale } from "date-fns/locale"; -import { dateLib } from "react-day-picker"; + +import { DateLib } from "../lib/dateLib"; import { getFormatters } from "./getFormatters"; import { getMonthOptions } from "./getMonthOptions"; @@ -18,8 +19,7 @@ test("return correct dropdown options", () => { startMonth, endMonth, formatters, - locale, - dateLib + new DateLib({ locale }) ); expect(result).toEqual([ diff --git a/src/helpers/getMonthOptions.ts b/src/helpers/getMonthOptions.ts index 86b951692..4d890e12d 100644 --- a/src/helpers/getMonthOptions.ts +++ b/src/helpers/getMonthOptions.ts @@ -1,5 +1,5 @@ import { DropdownOption } from "../components/Dropdown.js"; -import type { Locale, DateLib } from "../lib/dateLib.js"; +import type { DateLib } from "../lib/dateLib.js"; import type { Formatters } from "../types/index.js"; /** Return the months to show in the dropdown. */ @@ -8,7 +8,6 @@ export function getMonthOptions( navStart: Date | undefined, navEnd: Date | undefined, formatters: Pick, - locale: Locale | undefined, dateLib: DateLib ): DropdownOption[] | undefined { if (!navStart) return undefined; @@ -27,7 +26,7 @@ export function getMonthOptions( return a - b; }); const options = sortedMonths.map((value) => { - const label = formatters.formatMonthDropdown(value, locale); + const label = formatters.formatMonthDropdown(value, dateLib.locale); const month = dateLib.Date ? new dateLib.Date(year, value) : new Date(year, value); diff --git a/src/helpers/getMonths.test.ts b/src/helpers/getMonths.test.ts index 482fbde29..e1208b4f6 100644 --- a/src/helpers/getMonths.test.ts +++ b/src/helpers/getMonths.test.ts @@ -1,7 +1,7 @@ import { DayPickerProps } from "react-day-picker"; import { CalendarMonth } from "../classes"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { getMonths } from "./getMonths"; @@ -17,20 +17,17 @@ const mockDates = [ const mockProps: Pick< DayPickerProps, - | "fixedWeeks" - | "ISOWeek" - | "locale" - | "weekStartsOn" - | "reverseMonths" - | "firstWeekContainsDate" + "fixedWeeks" | "ISOWeek" | "reverseMonths" > = { fixedWeeks: false, ISOWeek: false, - locale: undefined, + reverseMonths: false +}; + +const dateLib = DateLib.fromOptionsDefaultLocale({ weekStartsOn: 0, // Sunday - reverseMonths: false, firstWeekContainsDate: 1 -}; +}); it("should return the correct months without ISO weeks and reverse months", () => { const displayMonths = [new Date(2023, 5, 1)]; // June 2023 diff --git a/src/helpers/getMonths.ts b/src/helpers/getMonths.ts index fad0d29f8..147979529 100644 --- a/src/helpers/getMonths.ts +++ b/src/helpers/getMonths.ts @@ -9,16 +9,7 @@ export function getMonths( /** The dates to display in the calendar. */ dates: Date[], /** Options from the props context. */ - props: Pick< - DayPickerProps, - | "fixedWeeks" - | "ISOWeek" - | "locale" - | "weekStartsOn" - | "reverseMonths" - | "firstWeekContainsDate" - | "timeZone" - >, + props: Pick, dateLib: DateLib ): CalendarMonth[] { const { @@ -35,17 +26,11 @@ export function getMonths( (months, month) => { const firstDateOfFirstWeek = props.ISOWeek ? startOfISOWeek(month) - : startOfWeek(month, { - locale: props.locale, - weekStartsOn: props.weekStartsOn - }); + : startOfWeek(month); const lastDateOfLastWeek = props.ISOWeek ? endOfISOWeek(endOfMonth(month)) - : endOfWeek(endOfMonth(month), { - locale: props.locale, - weekStartsOn: props.weekStartsOn - }); + : endOfWeek(endOfMonth(month)); /** The dates to display in the month. */ const monthDates = dates.filter((date) => { @@ -63,13 +48,7 @@ export function getMonths( const weeks: CalendarWeek[] = monthDates.reduce( (weeks, date) => { - const weekNumber = props.ISOWeek - ? getISOWeek(date) - : getWeek(date, { - locale: props.locale, - weekStartsOn: props.weekStartsOn, - firstWeekContainsDate: props.firstWeekContainsDate - }); + const weekNumber = props.ISOWeek ? getISOWeek(date) : getWeek(date); const week = weeks.find((week) => week.weekNumber === weekNumber); const day = new CalendarDay(date, month, dateLib); diff --git a/src/helpers/getNavMonth.test.ts b/src/helpers/getNavMonth.test.ts index 850dcfa20..bd4c9f6da 100644 --- a/src/helpers/getNavMonth.test.ts +++ b/src/helpers/getNavMonth.test.ts @@ -1,10 +1,10 @@ -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { getNavMonths } from "./getNavMonth"; describe('when "startMonth" is not passed in', () => { test('"startMonth" should be undefined', () => { - const [navStartMonth] = getNavMonths({}, dateLib); + const [navStartMonth] = getNavMonths({}, new DateLib()); expect(navStartMonth).toBeUndefined(); }); }); @@ -13,7 +13,7 @@ describe('when "startMonth" is passed in', () => { { startMonth: new Date(2021, 4, 3) }, - dateLib + new DateLib() ); test('"startMonth" should be the start of that month', () => { expect(navStartMonth).toEqual(new Date(2021, 4, 1)); @@ -25,7 +25,7 @@ describe('when "startMonth" is passed in', () => { }); }); describe('when "fromYear" is passed in', () => { - const [navStartMonth] = getNavMonths({ fromYear: 2021 }, dateLib); + const [navStartMonth] = getNavMonths({ fromYear: 2021 }, new DateLib()); test('"startMonth" should be the start of that year', () => { expect(navStartMonth).toEqual(new Date(2021, 0, 1)); }); @@ -35,7 +35,7 @@ describe('when "endMonth" is passed in', () => { { endMonth: new Date(2021, 4, 3) }, - dateLib + new DateLib() ); test('"endMonth" should be the end of that month', () => { expect(navEndMonth).toEqual(new Date(2021, 4, 31)); @@ -50,7 +50,7 @@ describe('when "endMonth" is passed in', () => { describe('when "toYear" is passed in', () => { const toYear = 2021; const expectedendMonth = new Date(2021, 11, 31); - const [, navEndMonth] = getNavMonths({ toYear }, dateLib); + const [, navEndMonth] = getNavMonths({ toYear }, new DateLib()); test('"endMonth" should be the end of that year', () => { expect(navEndMonth).toEqual(expectedendMonth); }); @@ -63,7 +63,7 @@ describe('when "captionLayout" is dropdown', () => { captionLayout: "dropdown", today }, - dateLib + new DateLib() ); test('"startMonth" should be 100 years ago', () => { expect(navStartMonth).toEqual(new Date(1924, 0, 1)); @@ -80,7 +80,7 @@ describe('when "captionLayout" is dropdown', () => { fromYear, today }, - dateLib + new DateLib() ); test('"startMonth" should be equal to the "fromYear"', () => { expect(navStartMonth).toEqual(new Date(2022, 0, 1)); @@ -98,7 +98,7 @@ describe('when "captionLayout" is dropdown', () => { toYear, today }, - dateLib + new DateLib() ); test('"startMonth" should be 100 years ago', () => { expect(navStartMonth).toEqual(new Date(1921, 0, 1)); diff --git a/src/helpers/getNavMonth.ts b/src/helpers/getNavMonth.ts index 69db4e2e3..d8e221a6d 100644 --- a/src/helpers/getNavMonth.ts +++ b/src/helpers/getNavMonth.ts @@ -12,7 +12,6 @@ export function getNavMonths( | "startMonth" | "today" | "timeZone" - | "dateLib" // Deprecated: | "fromMonth" | "fromYear" diff --git a/src/helpers/getNextFocus.test.tsx b/src/helpers/getNextFocus.test.tsx index 364133a3e..48407c576 100644 --- a/src/helpers/getNextFocus.test.tsx +++ b/src/helpers/getNextFocus.test.tsx @@ -1,23 +1,22 @@ import { CalendarDay } from "../classes"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import type { DayPickerProps, MoveFocusBy, MoveFocusDir } from "../types"; import { getNextFocus } from "./getNextFocus"; const props: Pick< DayPickerProps, - "disabled" | "hidden" | "startMonth" | "endMonth" | "dateLib" + "disabled" | "hidden" | "startMonth" | "endMonth" > = { disabled: [], - hidden: [], - dateLib + hidden: [] }; it("should return `undefined` if `attempt` exceeds 365", () => { const focusedDay = new CalendarDay( new Date(2020, 0, 1), new Date(2020, 0, 1), - dateLib + new DateLib() ); const moveBy: MoveFocusBy = "day"; const moveDir: MoveFocusDir = "after"; @@ -28,7 +27,7 @@ it("should return `undefined` if `attempt` exceeds 365", () => { undefined, undefined, props, - dateLib, + new DateLib(), 366 ); expect(result).toBeUndefined(); @@ -38,7 +37,7 @@ it("should return the focus date if it is not disabled or hidden", () => { const focusedDay = new CalendarDay( new Date(2020, 0, 1), new Date(2020, 0, 1), - dateLib + new DateLib() ); const expectedDate = new Date(2020, 0, 2); const result = getNextFocus( @@ -48,7 +47,7 @@ it("should return the focus date if it is not disabled or hidden", () => { undefined, undefined, props, - dateLib + new DateLib() ); expect(result?.date).toEqual(expectedDate); }); @@ -57,7 +56,7 @@ it("should return the next focus date if it is disabled", () => { const focusedDay = new CalendarDay( new Date(2020, 0, 1), new Date(2020, 0, 1), - dateLib + new DateLib() ); const disabledDate = new Date(2020, 0, 2); const expectedDate = new Date(2020, 0, 3); @@ -71,7 +70,7 @@ it("should return the next focus date if it is disabled", () => { ...props, disabled: [disabledDate] }, - dateLib + new DateLib() ); expect(result?.date).toEqual(expectedDate); }); @@ -80,7 +79,7 @@ it("should return the next focus date if it is hidden", () => { const focusedDay = new CalendarDay( new Date(2020, 0, 1), new Date(2020, 0, 1), - dateLib + new DateLib() ); const hiddenDate = new Date(2020, 0, 2); const expectedDate = new Date(2020, 0, 3); @@ -94,7 +93,7 @@ it("should return the next focus date if it is hidden", () => { ...props, hidden: [hiddenDate] }, - dateLib + new DateLib() ); expect(result?.date).toEqual(expectedDate); }); diff --git a/src/helpers/getNextFocus.tsx b/src/helpers/getNextFocus.tsx index 06591e964..eef5d9ba1 100644 --- a/src/helpers/getNextFocus.tsx +++ b/src/helpers/getNextFocus.tsx @@ -18,13 +18,7 @@ export function getNextFocus( calendarEndMonth: Date | undefined, props: Pick< DayPickerProps, - | "disabled" - | "hidden" - | "modifiers" - | "locale" - | "ISOWeek" - | "weekStartsOn" - | "timeZone" + "disabled" | "hidden" | "modifiers" | "ISOWeek" | "timeZone" >, dateLib: DateLib, attempt: number = 0 diff --git a/src/helpers/getNextMonth.test.ts b/src/helpers/getNextMonth.test.ts index 616b1a3f1..78f7effe2 100644 --- a/src/helpers/getNextMonth.test.ts +++ b/src/helpers/getNextMonth.test.ts @@ -1,5 +1,6 @@ import { addMonths, isSameMonth } from "date-fns"; -import { dateLib } from "react-day-picker"; + +import { DateLib } from "../lib/dateLib"; import { getNextMonth } from "./getNextMonth"; @@ -14,7 +15,7 @@ describe("when number of months is 1", () => { { disableNavigation: true }, - dateLib + new DateLib() ); expect(result).toBe(undefined); }); @@ -22,7 +23,7 @@ describe("when number of months is 1", () => { describe("when in the navigable range", () => { const endMonth = addMonths(startingMonth, 3); it("the next month is not undefined", () => { - const result = getNextMonth(startingMonth, endMonth, {}, dateLib); + const result = getNextMonth(startingMonth, endMonth, {}, new DateLib()); const expectedNextMonth = addMonths(startingMonth, 1); expect(result && isSameMonth(result, expectedNextMonth)).toBeTruthy(); }); @@ -30,7 +31,7 @@ describe("when number of months is 1", () => { describe("when not in the navigable range", () => { const endMonth = startingMonth; it("the next month is undefined", () => { - const result = getNextMonth(startingMonth, endMonth, {}, dateLib); + const result = getNextMonth(startingMonth, endMonth, {}, new DateLib()); expect(result).toBe(undefined); }); }); @@ -47,7 +48,7 @@ describe("when displaying 3 months", () => { numberOfMonths, pagedNavigation }, - dateLib + new DateLib() ); const expectedNextMonth = addMonths(startingMonth, 3); expect(result && isSameMonth(result, expectedNextMonth)).toBeTruthy(); @@ -61,7 +62,7 @@ describe("when displaying 3 months", () => { numberOfMonths, pagedNavigation }, - dateLib + new DateLib() ); expect(result).toBe(undefined); }); @@ -77,7 +78,7 @@ describe("when displaying 3 months", () => { numberOfMonths, pagedNavigation }, - dateLib + new DateLib() ); const expectedNextMonth = addMonths(startingMonth, 1); expect(result && isSameMonth(result, expectedNextMonth)).toBeTruthy(); @@ -91,7 +92,7 @@ describe("when displaying 3 months", () => { numberOfMonths, pagedNavigation }, - dateLib + new DateLib() ); expect(result).toBe(undefined); }); diff --git a/src/helpers/getPossibleFocusDate.test.ts b/src/helpers/getPossibleFocusDate.test.ts index 9d00eb88e..b3f66fb83 100644 --- a/src/helpers/getPossibleFocusDate.test.ts +++ b/src/helpers/getPossibleFocusDate.test.ts @@ -11,17 +11,18 @@ import { endOfWeek } from "date-fns"; +import { DateLib } from "../lib/dateLib"; import type { DayPickerProps, MoveFocusBy, MoveFocusDir } from "../types"; -import { dateLib } from ".."; import { getFocusableDate } from "./getFocusableDate"; const focusedDate = new Date(2023, 0, 1); // Jan 1, 2023 -const options: Pick = { - locale: undefined, - ISOWeek: false, - weekStartsOn: 0 // Sunday +const options: Pick = { + ISOWeek: false }; +const dateLib = DateLib.fromOptionsDefaultLocale({ + weekStartsOn: 0 // Sunday +}); const calendarStartMonth = new Date(2022, 0, 1); // Jan 1, 2022 const calendarEndMonth = new Date(2024, 0, 1); // Jan 1, 2024 diff --git a/src/helpers/getPreviousMonth.test.ts b/src/helpers/getPreviousMonth.test.ts index eecca21f7..e6bf52541 100644 --- a/src/helpers/getPreviousMonth.test.ts +++ b/src/helpers/getPreviousMonth.test.ts @@ -1,4 +1,5 @@ -import { dateLib } from ".."; +import { DateLib } from "../lib/dateLib"; + import { getPreviousMonth } from "./getPreviousMonth"; it("should return undefined if navigation is disabled", () => { @@ -14,7 +15,7 @@ it("should return undefined if navigation is disabled", () => { firstDisplayedMonth, calendarStartMonth, props, - dateLib + new DateLib() ); expect(result).toBeUndefined(); @@ -32,7 +33,7 @@ it("should return the previous month if startMonth is not provided", () => { firstDisplayedMonth, undefined, props, - dateLib + new DateLib() ); expect(result).toEqual(new Date(2022, 0, 1)); // January 2022 @@ -50,7 +51,7 @@ it("should return undefined if the previous month is before the startMonth", () firstDisplayedMonth, calendarStartMonth, props, - dateLib + new DateLib() ); expect(result).toBeUndefined(); }); @@ -62,15 +63,14 @@ it("should return the correct previous month when pagedNavigation is true", () = disableNavigation: false, pagedNavigation: true, numberOfMonths: 2, - startMonth: new Date(2022, 0, 1), - dateLib + startMonth: new Date(2022, 0, 1) }; const result = getPreviousMonth( firstDisplayedMonth, calendarStartMonth, props, - dateLib + new DateLib() ); expect(result).toEqual(new Date(2022, 0, 1)); // January 2022 diff --git a/src/helpers/getWeekdays.test.ts b/src/helpers/getWeekdays.test.ts index 711da7c4c..0c3935460 100644 --- a/src/helpers/getWeekdays.test.ts +++ b/src/helpers/getWeekdays.test.ts @@ -1,6 +1,6 @@ import { es } from "date-fns/locale/es"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { getWeekdays } from "./getWeekdays"; @@ -8,7 +8,7 @@ let result: Date[]; describe("when rendered without a locale", () => { beforeEach(() => { - result = getWeekdays(); + result = getWeekdays(DateLib.fromOptionsDefaultLocale({})); }); test("should return 7 days", () => { expect(result).toHaveLength(7); @@ -22,7 +22,9 @@ describe.each<0 | 1 | 2 | 3 | 4 | 5 | 6>([0, 1, 2, 3, 4, 5, 6])( "when week start on %s", (weekStartsOn) => { beforeEach(() => { - result = getWeekdays(es, weekStartsOn); + result = getWeekdays( + DateLib.fromOptionsDefaultLocale({ locale: es, weekStartsOn }) + ); }); test("the first date should be weekStartsOn", () => { expect(result[0].getDay()).toBe(weekStartsOn); @@ -32,7 +34,11 @@ describe.each<0 | 1 | 2 | 3 | 4 | 5 | 6>([0, 1, 2, 3, 4, 5, 6])( describe("when using ISO week", () => { beforeEach(() => { - result = getWeekdays(es, 3, true, undefined, dateLib); + result = getWeekdays( + DateLib.fromOptionsDefaultLocale({ locale: es, weekStartsOn: 3 }), + true, + undefined + ); }); test("should return Monday as first day", () => { expect(result[0]).toBeMonday(); diff --git a/src/helpers/getWeekdays.ts b/src/helpers/getWeekdays.ts index 57552ebbf..5b1a7dc38 100644 --- a/src/helpers/getWeekdays.ts +++ b/src/helpers/getWeekdays.ts @@ -1,21 +1,17 @@ import { TZDate } from "@date-fns/tz"; -import type { Locale, DateLib } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib } from "../lib/dateLib.js"; /** * Generate a series of 7 days, starting from the week, to use for formatting * the weekday names (Monday, Tuesday, etc.). */ export function getWeekdays( - locale?: Locale | undefined, - /** The index of the first day of the week (0 - Sunday). */ - weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined, + /** The date library. */ + dateLib: DateLib, /** Use ISOWeek instead of locale/ */ ISOWeek?: boolean | undefined, - timeZone?: string | undefined, - /** @ignore */ - dateLib: DateLib = defaultDateLib + timeZone?: string | undefined ): Date[] { const date = timeZone ? TZDate.tz(timeZone) @@ -24,7 +20,7 @@ export function getWeekdays( : new Date(); const start = ISOWeek ? dateLib.startOfISOWeek(date) - : dateLib.startOfWeek(date, { locale, weekStartsOn }); + : dateLib.startOfWeek(date); const days = []; for (let i = 0; i < 7; i++) { diff --git a/src/helpers/getYearOptions.test.ts b/src/helpers/getYearOptions.test.ts index 79d2879a3..a2e3b2c38 100644 --- a/src/helpers/getYearOptions.test.ts +++ b/src/helpers/getYearOptions.test.ts @@ -1,4 +1,4 @@ -import { dateLib } from "react-day-picker"; +import { DateLib } from "../lib/dateLib"; import { getFormatters } from "./getFormatters"; import { getYearOptions } from "./getYearOptions"; @@ -13,14 +13,14 @@ test("return undefined if startMonth or endMonth is not provided", () => { undefined, new Date(2022, 11, 31), formatters, - dateLib + new DateLib() ); const result2 = getYearOptions( displayMonth, new Date(2022, 0, 1), undefined, formatters, - dateLib + new DateLib() ); expect(result1).toBeUndefined(); @@ -40,7 +40,7 @@ test("return correct dropdown options", () => { startMonth, endMonth, formatters, - dateLib + new DateLib() ); expect(result).toEqual([ diff --git a/src/index.ts b/src/index.ts index 35598d546..66b740755 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,4 +14,4 @@ export * from "./UI.js"; export * from "./useDayPicker.js"; -export { enUS as defaultLocale } from "./lib/locales.js"; +export { defaultLocale } from "./lib/locales.js"; diff --git a/src/labels/labelDayButton.ts b/src/labels/labelDayButton.ts index f772e6b7a..b7c4be1e5 100644 --- a/src/labels/labelDayButton.ts +++ b/src/labels/labelDayButton.ts @@ -1,5 +1,4 @@ -import type { DateLib, LabelOptions } from "../index.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, LabelOptions } from "../lib/dateLib.js"; import type { Modifiers } from "../types/index.js"; /** @@ -18,9 +17,9 @@ export function labelDayButton( modifiers: Modifiers, options?: LabelOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - let label = dateLib.format(date, "PPPP", options); + let label = dateLib.format(date, "PPPP"); if (modifiers.today) label = `Today, ${label}`; if (modifiers.selected) label = `${label}, selected`; return label; diff --git a/src/labels/labelGrid.ts b/src/labels/labelGrid.ts index be9149277..ec1f65f53 100644 --- a/src/labels/labelGrid.ts +++ b/src/labels/labelGrid.ts @@ -1,5 +1,4 @@ -import type { LabelOptions, DateLib } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, type LabelOptions } from "../lib/dateLib.js"; /** * Return an ARIA label for the month grid, that will be announced when entering @@ -13,9 +12,9 @@ export function labelGrid( date: Date, options?: LabelOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - return dateLib.format(date, "LLLL y", options); + return dateLib.format(date, "LLLL y"); } /** diff --git a/src/labels/labelGridcell.ts b/src/labels/labelGridcell.ts index 87409e33a..682b4d626 100644 --- a/src/labels/labelGridcell.ts +++ b/src/labels/labelGridcell.ts @@ -1,6 +1,4 @@ -import type { DateLib } from "../index.js"; -import type { LabelOptions } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, type LabelOptions } from "../lib/dateLib.js"; import type { Modifiers } from "../types/index.js"; /** @@ -15,9 +13,9 @@ export function labelGridcell( modifiers?: Modifiers, options?: LabelOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ) { - let label = dateLib.format(date, "PPPP", options); + let label = dateLib.format(date, "PPPP"); if (modifiers?.today) { label = `Today, ${label}`; } diff --git a/src/labels/labelWeekday.ts b/src/labels/labelWeekday.ts index 807c9a400..8afea40ab 100644 --- a/src/labels/labelWeekday.ts +++ b/src/labels/labelWeekday.ts @@ -1,5 +1,4 @@ -import type { LabelOptions, DateLib } from "../lib/dateLib.js"; -import { dateLib as defaultDateLib } from "../lib/index.js"; +import { DateLib, type LabelOptions } from "../lib/dateLib.js"; /** * The ARIA label for the Weekday column header. @@ -12,7 +11,7 @@ export function labelWeekday( date: Date, options?: LabelOptions, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = DateLib.fromOptionsDefaultLocale(options) ): string { - return dateLib.format(date, "cccc", options); + return dateLib.format(date, "cccc"); } diff --git a/src/lib/dateLib.ts b/src/lib/dateLib.ts index 9a6f044c8..5947714f9 100644 --- a/src/lib/dateLib.ts +++ b/src/lib/dateLib.ts @@ -1,6 +1,7 @@ import type { FormatOptions as DateFnsFormatOptions, - Locale as DateFnsLocale + Locale as DateFnsLocale, + DateArg } from "date-fns"; import { addDays } from "date-fns/addDays"; import { addMonths } from "date-fns/addMonths"; @@ -31,6 +32,8 @@ import { startOfMonth } from "date-fns/startOfMonth"; import { startOfWeek } from "date-fns/startOfWeek"; import { startOfYear } from "date-fns/startOfYear"; +import { defaultLocale } from "./locales.js"; + /** The options for the {@link Formatters}. */ export type FormatOptions = DateFnsFormatOptions; @@ -42,13 +45,15 @@ export type Locale = DateFnsLocale; export type { Month as DateFnsMonth } from "date-fns"; +type Options = Omit & { locale?: Locale }; + /** * The date library used by DayPicker. It's a subset of the date-fns functions * plus an optional Date constructor. * * Override the default date library with the `dateLib` prop. */ -export type DateLib = { +export type DateLibBase = { /** The constructor of the date object. */ Date?: DateConstructor | undefined; @@ -143,7 +148,7 @@ export type DateLib = { * @private * @internal */ -export const dateLib = { +const defaultDateLibBase: DateLibBase = { addDays, addMonths, addWeeks, @@ -173,3 +178,206 @@ export const dateLib = { startOfWeek, startOfYear }; + +/** + * The date library used by DayPicker. It's a subset of the date-fns functions + * with an explicit locale / formatting options. + * + * @private + * @internal + */ +export class DateLib { + /** + * Creates a new instance of the date library. + * + * @param options The formatting options for the date library. + * @param dateLibBaseOverrides Overrides for the date library functions. + */ + constructor( + readonly options: Options = {}, + dateLibBaseOverrides: Partial = {} + ) { + this.dateLibBase = { + ...defaultDateLibBase, + ...dateLibBaseOverrides + }; + } + + /** + * Creates a new instance of the date library with the default locale. + * + * @param options The formatting options for the date library. + */ + static fromOptionsDefaultLocale( + options: FormatOptions | Options | undefined + ) { + return new DateLib({ + ...options, + locale: { ...defaultLocale, ...options?.locale } + }); + } + + /** The options used by the date library. */ + private readonly dateLibBase: DateLibBase; + + // ===================================================================== + // DateLib functions which are just wrappers around date-fns functions + // They do not require locale overrides as they are not locale-dependent + // ===================================================================== + + /** Adds the specified number of days to the given date. */ + addDays: DateLibBase["addDays"] = (...args) => + this.dateLibBase.addDays(...args); + + /** Adds the specified number of months to the given date. */ + addMonths: DateLibBase["addMonths"] = (...args) => + this.dateLibBase.addMonths(...args); + + /** Adds the specified number of weeks to the given date. */ + addWeeks: DateLibBase["addWeeks"] = (...args) => + this.dateLibBase.addWeeks(...args); + + /** Adds the specified number of years to the given date. */ + addYears: DateLibBase["addYears"] = (...args) => + this.dateLibBase.addYears(...args); + + /** Returns the number of calendar days between the given dates. */ + differenceInCalendarDays: DateLibBase["differenceInCalendarDays"] = ( + ...args + ) => this.dateLibBase.differenceInCalendarDays(...args); + + /** Returns the number of calendar months between the given dates. */ + differenceInCalendarMonths: DateLibBase["differenceInCalendarMonths"] = ( + ...args + ) => this.dateLibBase.differenceInCalendarMonths(...args); + + /** Returns the end of an ISO week for the given date. */ + endOfISOWeek: DateLibBase["endOfISOWeek"] = (...args) => + this.dateLibBase.endOfISOWeek(...args); + + /** Returns the end of the month for the given date. */ + endOfMonth: DateLibBase["endOfMonth"] = (...args) => + this.dateLibBase.endOfMonth(...args); + + /** Returns the end of the year for the given date. */ + endOfYear: DateLibBase["endOfYear"] = (...args) => + this.dateLibBase.endOfYear(...args); + + /** Returns the ISO week number for the given date. */ + getISOWeek: DateLibBase["getISOWeek"] = (...args) => + this.dateLibBase.getISOWeek(...args); + + /** Checks if the first date is after the second date. */ + isAfter: DateLibBase["isAfter"] = (...args) => + this.dateLibBase.isAfter(...args); + + /** Checks if the first date is before the second date. */ + isBefore: DateLibBase["isBefore"] = (...args) => + this.dateLibBase.isBefore(...args); + + /** Checks if the given value is a date. */ + isDate: DateLibBase["isDate"] = (value): value is Date => + this.dateLibBase.isDate(value); + + /** Checks if the given dates are the same day. */ + isSameDay: DateLibBase["isSameDay"] = (...args) => + this.dateLibBase.isSameDay(...args); + + /** Checks if the given dates are in the same month. */ + isSameMonth: DateLibBase["isSameMonth"] = (...args) => + this.dateLibBase.isSameMonth(...args); + + /** Checks if the given dates are in the same year. */ + isSameYear: DateLibBase["isSameYear"] = (...args) => + this.dateLibBase.isSameYear(...args); + + /** Returns the maximum of the given dates. */ + max: DateLibBase["max"] = (...args) => this.dateLibBase.max(...args); + + /** Returns the minimum of the given dates. */ + min: DateLibBase["min"] = (...args) => this.dateLibBase.min(...args); + + /** Sets the month for the given date. */ + setMonth: DateLibBase["setMonth"] = (...args) => + this.dateLibBase.setMonth(...args); + + /** Sets the year for the given date. */ + setYear: DateLibBase["setYear"] = (...args) => + this.dateLibBase.setYear(...args); + + /** Returns the start of the day for the given date. */ + startOfDay: DateLibBase["startOfDay"] = (...args) => + this.dateLibBase.startOfDay(...args); + + /** Returns the start of an ISO week for the given date. */ + startOfISOWeek: DateLibBase["startOfISOWeek"] = (...args) => + this.dateLibBase.startOfISOWeek(...args); + + /** Returns the start of the month for the given date. */ + startOfMonth: DateLibBase["startOfMonth"] = (...args) => + this.dateLibBase.startOfMonth(...args); + + /** Returns the start of the year for the given date. */ + startOfYear: DateLibBase["startOfYear"] = (...args) => + this.dateLibBase.startOfYear(...args); + + // ================================================ + // DateLib functions which require locale overrides + // ================================================ + + /** Returns the end of the week for the given date. */ + endOfWeek = (date: DateArg) => { + if (!this.locale) { + throw new Error( + "DateLib needs to be initialized with locale to use the `endOfWeek` function." + ); + } + + return endOfWeek(date, this.options); + }; + + /** Formats the given date using the specified format string. */ + format = (date: DateArg & {}, formatStr: string) => { + if (!this.locale) { + throw new Error( + "DateLib needs to be initialized with locale to use the `format` function." + ); + } + + return format(date, formatStr, this.options); + }; + + /** Returns the week number for the given date. */ + getWeek = (date: DateArg & {}) => { + if (!this.locale) { + throw new Error( + "DateLib needs to be initialized with locale to use the `getWeek` function." + ); + } + + return getWeek(date, this.options); + }; + + /** Returns the start of the week for the given date. */ + startOfWeek = ( + date: DateArg + ) => { + if (!this.locale) { + throw new Error( + "DateLib needs to be initialized with locale to use the `startOfWeek` function." + ); + } + + return startOfWeek(date, this.options); + }; + + /** The locale used by the date library. */ + get locale(): Locale | undefined { + return this.options.locale; + } + + /** The constructor of the date object. */ + get Date(): DateConstructor | undefined { + return this.dateLibBase.Date; + } +} diff --git a/src/lib/locales.ts b/src/lib/locales.ts index 3c0f52435..5e154069d 100644 --- a/src/lib/locales.ts +++ b/src/lib/locales.ts @@ -1 +1 @@ -export { enUS } from "date-fns/locale/en-US"; +export { enUS as defaultLocale } from "date-fns/locale/en-US"; diff --git a/src/selection/useMulti.test.tsx b/src/selection/useMulti.test.tsx index 5c8fea569..f3255e7a8 100644 --- a/src/selection/useMulti.test.tsx +++ b/src/selection/useMulti.test.tsx @@ -1,7 +1,6 @@ -import { dateLib } from "react-day-picker/lib"; - import { act, renderHook } from "@/test/render"; +import { DateLib } from "../lib/dateLib"; import { DayPickerProps } from "../types"; import { useMulti } from "./useMulti"; @@ -16,7 +15,7 @@ describe("useMulti", () => { onSelect: mockOnSelect }; - const { result } = renderHook(() => useMulti(props, dateLib)); + const { result } = renderHook(() => useMulti(props, new DateLib())); expect(result.current.selected).toBe(selectedDates); }); @@ -28,7 +27,7 @@ describe("useMulti", () => { selected: initialSelectedDates }; - const { result } = renderHook(() => useMulti(props, dateLib)); + const { result } = renderHook(() => useMulti(props, new DateLib())); act(() => { result.current.select?.(new Date(2023, 9, 3), {}, {} as React.MouseEvent); diff --git a/src/selection/useRange.test.tsx b/src/selection/useRange.test.tsx index b53181333..1acf03247 100644 --- a/src/selection/useRange.test.tsx +++ b/src/selection/useRange.test.tsx @@ -3,7 +3,7 @@ import { DayPickerProps } from "react-day-picker/types"; import { act, renderHook } from "@/test/render"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { useRange } from "./useRange"; @@ -16,7 +16,7 @@ describe("useRange", () => { const { result } = renderHook(() => useRange( { mode: "range", selected: initiallySelected, required: false }, - dateLib + new DateLib() ) ); @@ -31,7 +31,7 @@ describe("useRange", () => { const { result } = renderHook(() => useRange( { mode: "range", selected: initiallySelected, required: false }, - dateLib + new DateLib() ) ); @@ -54,7 +54,7 @@ describe("useRange", () => { required: false, max: 5 }, - dateLib + new DateLib() ) ); @@ -73,7 +73,7 @@ describe("useRange", () => { const { result } = renderHook(() => useRange( { mode: "range", selected: undefined, required: false, min: 5 }, - dateLib + new DateLib() ) ); @@ -99,7 +99,7 @@ describe("useRange", () => { excludeDisabled: true, disabled }, - dateLib + new DateLib() ) ); @@ -126,7 +126,7 @@ describe("useRange", () => { onSelect: mockOnSelect }; - const { result } = renderHook(() => useRange(props, dateLib)); + const { result } = renderHook(() => useRange(props, new DateLib())); expect(result.current.selected).toBe(selectedRange); }); @@ -141,7 +141,7 @@ describe("useRange", () => { selected: initialSelectedRange }; - const { result } = renderHook(() => useRange(props, dateLib)); + const { result } = renderHook(() => useRange(props, new DateLib())); act(() => { result.current.select?.(new Date(2023, 9, 6), {}, {} as React.MouseEvent); diff --git a/src/selection/useSingle.test.tsx b/src/selection/useSingle.test.tsx index e1bebf050..4b280727b 100644 --- a/src/selection/useSingle.test.tsx +++ b/src/selection/useSingle.test.tsx @@ -1,7 +1,6 @@ -import { dateLib } from "react-day-picker/lib"; - import { act, renderHook } from "@/test/render"; +import { DateLib } from "../lib/dateLib"; import { DayPickerProps } from "../types"; import { useSingle } from "./useSingle"; @@ -16,7 +15,7 @@ describe("useSingle", () => { onSelect: mockOnSelect }; - const { result } = renderHook(() => useSingle(props, dateLib)); + const { result } = renderHook(() => useSingle(props, new DateLib())); expect(result.current.selected).toBe(selectedDate); }); @@ -28,7 +27,7 @@ describe("useSingle", () => { selected: initialSelectedDate }; - const { result } = renderHook(() => useSingle(props, dateLib)); + const { result } = renderHook(() => useSingle(props, new DateLib())); act(() => { result.current.select?.(new Date(2023, 9, 2), {}, {} as React.MouseEvent); diff --git a/src/types/props.ts b/src/types/props.ts index 980f62c54..853a65114 100644 --- a/src/types/props.ts +++ b/src/types/props.ts @@ -1,7 +1,7 @@ import React from "react"; import type { DeprecatedUI } from "../UI.js"; -import type { Locale, DateLib } from "../lib/dateLib.js"; +import type { Locale, DateLibBase } from "../lib/dateLib.js"; import type { ClassNames, @@ -441,7 +441,7 @@ export interface PropsBase { * @since 9.0.0 * @experimental */ - dateLib?: Partial | undefined; + dateLib?: Partial | undefined; /** * @private diff --git a/src/useCalendar.ts b/src/useCalendar.ts index 68992e351..8bb227196 100644 --- a/src/useCalendar.ts +++ b/src/useCalendar.ts @@ -75,7 +75,6 @@ export function useCalendar( | "today" | "fixedWeeks" | "ISOWeek" - | "weekStartsOn" | "numberOfMonths" | "disableNavigation" | "onMonthChange" diff --git a/src/utils/addToRange.ts b/src/utils/addToRange.ts index c46102db0..584527b39 100644 --- a/src/utils/addToRange.ts +++ b/src/utils/addToRange.ts @@ -1,4 +1,4 @@ -import { dateLib as defaultDateLib, type DateLib } from "../lib/index.js"; +import { DateLib } from "../lib/dateLib.js"; import type { DateRange } from "../types/index.js"; /** @@ -18,7 +18,7 @@ export function addToRange( max = 0, required = false, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = new DateLib() ): DateRange | undefined { const { from, to } = initialRange || {}; const { isSameDay, isAfter, isBefore } = dateLib; diff --git a/src/utils/dateMatchModifiers.test.ts b/src/utils/dateMatchModifiers.test.ts index d9a784c63..ea270041a 100644 --- a/src/utils/dateMatchModifiers.test.ts +++ b/src/utils/dateMatchModifiers.test.ts @@ -1,6 +1,6 @@ import { addDays, subDays } from "date-fns"; -import { dateLib } from "../lib"; +import { DateLib } from "../lib/dateLib"; import { DateAfter, DateBefore, @@ -15,14 +15,14 @@ const testDay = new Date(); describe("when the matcher is a boolean", () => { const matcher = true; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return the boolean", () => { expect(result).toBe(matcher); }); }); describe("when matching the same day", () => { const matcher = testDay; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -30,7 +30,7 @@ describe("when matching the same day", () => { describe("when matching an array of dates including the day", () => { const matcher = [addDays(testDay, -1), testDay, addDays(testDay, 1)]; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -41,7 +41,7 @@ describe("when matching date range", () => { from: testDay, to: addDays(testDay, 1) }; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -51,7 +51,7 @@ describe("when matching the day of week", () => { const matcher: DayOfWeek = { dayOfWeek: [testDay.getDay()] }; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -62,7 +62,7 @@ describe("when matching date interval (closed)", () => { before: addDays(testDay, 5), after: subDays(testDay, 3) }; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true for the included day", () => { expect(result).toBe(true); }); @@ -74,22 +74,30 @@ describe("when matching date interval (open)", () => { after: addDays(testDay, 5) }; test("should return false", () => { - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); expect(result).toBe(false); }); test("should return true for the days before", () => { - const result = dateMatchModifiers(subDays(testDay, 8), [matcher], dateLib); + const result = dateMatchModifiers( + subDays(testDay, 8), + [matcher], + new DateLib() + ); expect(result).toBe(true); }); test("should return true for the days after", () => { - const result = dateMatchModifiers(addDays(testDay, 8), [matcher], dateLib); + const result = dateMatchModifiers( + addDays(testDay, 8), + [matcher], + new DateLib() + ); expect(result).toBe(true); }); }); describe("when matching the date after", () => { const matcher: DateAfter = { after: addDays(testDay, -1) }; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -97,7 +105,7 @@ describe("when matching the date after", () => { describe("when matching the date before", () => { const matcher: DateBefore = { before: addDays(testDay, +1) }; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return true", () => { expect(result).toBe(true); }); @@ -105,7 +113,7 @@ describe("when matching the date before", () => { describe("when the matcher is a function", () => { const matcher = () => true; - const result = dateMatchModifiers(testDay, [matcher], dateLib); + const result = dateMatchModifiers(testDay, [matcher], new DateLib()); test("should return the result of the function", () => { expect(result).toBe(true); }); diff --git a/src/utils/dateMatchModifiers.ts b/src/utils/dateMatchModifiers.ts index a3caa4031..19cc198f3 100644 --- a/src/utils/dateMatchModifiers.ts +++ b/src/utils/dateMatchModifiers.ts @@ -1,4 +1,4 @@ -import { dateLib as defaultDateLib, type DateLib } from "../lib/dateLib.js"; +import { DateLib } from "../lib/dateLib.js"; import type { Matcher } from "../types/index.js"; import { rangeIncludesDate } from "./rangeIncludesDate.js"; @@ -33,7 +33,7 @@ import { export function dateMatchModifiers( date: Date, matchers: Matcher | Matcher[], - dateLib: DateLib = defaultDateLib + dateLib: DateLib = new DateLib() ): boolean { const matchersArr = !Array.isArray(matchers) ? [matchers] : matchers; const { isSameDay, differenceInCalendarDays, isAfter } = dateLib; diff --git a/src/utils/rangeIncludesDate.ts b/src/utils/rangeIncludesDate.ts index 04823f2a3..1162e3d63 100644 --- a/src/utils/rangeIncludesDate.ts +++ b/src/utils/rangeIncludesDate.ts @@ -1,4 +1,4 @@ -import { dateLib as defaultDateLib, type DateLib } from "../lib/index.js"; +import { DateLib } from "../lib/index.js"; import type { DateRange } from "../types/index.js"; /** @@ -13,7 +13,7 @@ export function rangeIncludesDate( /** If `true`, the ends of the range are excluded. */ excludeEnds = false, /** @ignore */ - dateLib: DateLib = defaultDateLib + dateLib: DateLib = new DateLib() ): boolean { let { from, to } = range; const { differenceInCalendarDays, isSameDay } = dateLib; @@ -41,4 +41,4 @@ export function rangeIncludesDate( * @deprecated Use {@link rangeIncludesDate} instead. */ export const isDateInRange = (range: DateRange, date: Date) => - rangeIncludesDate(range, date, false, defaultDateLib); + rangeIncludesDate(range, date, false, new DateLib()); diff --git a/src/utils/typeguards.test.ts b/src/utils/typeguards.test.ts index 63ec85d2d..2572ae093 100644 --- a/src/utils/typeguards.test.ts +++ b/src/utils/typeguards.test.ts @@ -1,4 +1,4 @@ -import { dateLib } from "../lib/dateLib.js"; +import { DateLib } from "../lib/dateLib.js"; import type { DateInterval, DateRange, @@ -73,11 +73,11 @@ test("isDayOfWeekType return false for invalid DayOfWeek", () => { test("isDatesArray return true for valid array of dates", () => { const validDatesArray: Date[] = [new Date(), new Date()]; - expect(isDatesArray(validDatesArray, dateLib)).toBe(true); + expect(isDatesArray(validDatesArray, new DateLib())).toBe(true); }); test("isDatesArray return false for invalid array of dates", () => { - expect(isDatesArray([{}, {}], dateLib)).toBe(false); - expect(isDatesArray(null, dateLib)).toBe(false); - expect(isDatesArray(undefined, dateLib)).toBe(false); + expect(isDatesArray([{}, {}], new DateLib())).toBe(false); + expect(isDatesArray(null, new DateLib())).toBe(false); + expect(isDatesArray(undefined, new DateLib())).toBe(false); });