@astryxdesign/core 0.7.9-canary.f8a309d → 0.7.9-canary.ffa495b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA6B5C,OAAO,EAEL,KAAK,cAAc,EAQpB,MAAM,yBAAyB,CAAC;AAoBjC,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAiB5B,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IAEzC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEzB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AA4BrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAogBtD;yBApgBe,QAAQ"}
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA6B5C,OAAO,EAEL,KAAK,cAAc,EASpB,MAAM,yBAAyB,CAAC;AAoBjC,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAiB5B,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IAEzC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEzB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AA4BrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAogBtD;yBApgBe,QAAQ"}
@@ -23,10 +23,10 @@ import { Button } from "../Button/index.js";
23
23
  import { Icon } from "../Icon/index.js";
24
24
  import { useAnnounce, useGridFocus } from "../hooks/index.js";
25
25
  import { useCalendarConstraints } from "./hooks/index.js";
26
- import { useCalendarMonthDays } from "./hooks/useCalendarDays.js";
26
+ import { useCalendarDays } from "./hooks/useCalendarDays.js";
27
27
  import { calendarStyles, monthGridStyles, dayCellStyles, dayCellTheme } from "./styles.js";
28
28
  import { plainDateFromISO, plainDateToISO, plainDateToDate, plainDateToday, plainDateAddDays, plainDateDiffDays, plainDateIsAfter, plainDateIsBefore, plainDateIsEqual, plainDateGetWeekNumber, plainDateWeekFieldName, plainDateFormatDayNumber, DATE_FORMAT_WITH_WEEKDAY } from "../utils/plainDate.js";
29
- import { calendarAddMonths, calendarMonthOf, calendarShiftMonths, calendarShiftYears, formatCalendarDate, formatCalendarMonthLabel, isInCalendarMonth } from "../utils/calendarSystem.js";
29
+ import { calendarAddMonths, calendarMonthOf, calendarShiftMonths, calendarShiftYears, calendarYearMonthOf, formatCalendarDate, formatCalendarMonthLabel, isInCalendarMonth } from "../utils/calendarSystem.js";
30
30
  import { mergeProps, composeEventHandlers, rtlStyles } from "../utils/index.js";
31
31
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
32
32
  import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
@@ -524,11 +524,13 @@ function MonthGrid({
524
524
  const locale = useLocale();
525
525
 
526
526
  // Use hooks for days generation and constraints
527
+ const yearMonth = useMemo(() => calendarYearMonthOf(calendar, month), [calendar, month]);
527
528
  const {
528
529
  days,
529
530
  weeks
530
- } = useCalendarMonthDays({
531
- month,
531
+ } = useCalendarDays({
532
+ year: yearMonth.year,
533
+ month: yearMonth.month,
532
534
  calendar,
533
535
  weekStartsOn,
534
536
  hasVariableRowCount
@@ -1,6 +1,6 @@
1
1
  import type { DayOfWeek, ISODateString } from '../../utils/dateTypes.js';
2
2
  import { type PlainDate } from '../../utils/plainDate.js';
3
- import { type CalendarMonth, type CalendarSystem } from '../../utils/calendarSystem.js';
3
+ import { type CalendarSystem } from '../../utils/calendarSystem.js';
4
4
  /**
5
5
  * Represents a single day in the calendar grid.
6
6
  */
@@ -18,14 +18,27 @@ export interface CalendarDay {
18
18
  * Configuration for calendar days generation
19
19
  */
20
20
  export interface UseCalendarDaysOptions {
21
- /** The year to generate days for */
21
+ /** The year to generate days for, in `calendar`'s numbering */
22
22
  year: number;
23
- /** The month (1-based: 1 = January, 12 = December) */
23
+ /**
24
+ * The month's position in its year (1-based). In the Gregorian calendar,
25
+ * 1 is January and 12 is December. In other calendars it is numbered as
26
+ * `Temporal.PlainDate` numbers it: 1 through 13 in a Hebrew leap year.
27
+ */
24
28
  month: number;
25
29
  /** First day of week (0=Sunday through 6=Saturday) */
26
30
  weekStartsOn?: DayOfWeek;
27
31
  /** Use variable rows per month vs. fixed 6-row grid */
28
32
  hasVariableRowCount?: boolean;
33
+ /**
34
+ * The calendar system that numbers `year`, `month`, and each `dayNumber`.
35
+ * Each day keeps its Gregorian `date` and `iso`. The hook never reads the
36
+ * InternationalizationProvider calendar. Calendars other than gregory,
37
+ * iso8601, buddhist, roc, and japanese need Temporal; without it, the hook
38
+ * reads Gregorian numbers and warns once in development.
39
+ * @default 'gregory'
40
+ */
41
+ calendar?: CalendarSystem;
29
42
  }
30
43
  /**
31
44
  * Return type for useCalendarDays hook
@@ -54,24 +67,10 @@ export interface UseCalendarDaysReturn {
54
67
  * weekStartsOn: 0, // Sunday
55
68
  * });
56
69
  * // days[i].date is a PlainDate { year, month (1-based), day }
70
+ *
71
+ * // Aban 1405 in the Persian calendar; dates stay Gregorian
72
+ * useCalendarDays({year: 1405, month: 8, calendar: 'persian'});
57
73
  * ```
58
74
  */
59
75
  export declare function useCalendarDays(options: UseCalendarDaysOptions): UseCalendarDaysReturn;
60
- /** Configuration for the day grid of one month of a calendar system. */
61
- export interface UseCalendarMonthDaysOptions {
62
- /** The month to lay out, located by its Gregorian days. */
63
- month: CalendarMonth;
64
- /** The calendar system the month belongs to; it numbers the days. */
65
- calendar: CalendarSystem;
66
- /** First day of week (0=Sunday through 6=Saturday) */
67
- weekStartsOn?: DayOfWeek;
68
- /** Use variable rows per month vs. fixed 6-row grid */
69
- hasVariableRowCount?: boolean;
70
- }
71
- /**
72
- * The day grid of one month of any calendar system. Each day keeps its
73
- * Gregorian date; `dayNumber` is its day of the calendar month. Internal to
74
- * Calendar.
75
- */
76
- export declare function useCalendarMonthDays(options: UseCalendarMonthDaysOptions): UseCalendarDaysReturn;
77
76
  //# sourceMappingURL=useCalendarDays.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCalendarDays.d.ts","sourceRoot":"","sources":["../../../src/Calendar/hooks/useCalendarDays.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEpB,MAAM,4BAA4B,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,mCAAmC;IACnC,GAAG,EAAE,aAAa,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,8BAA8B;IAC9B,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;IACvB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAcvB;AAED,wEAAwE;AACxE,MAAM,WAAW,2BAA2B;IAC1C,2DAA2D;IAC3D,KAAK,EAAE,aAAa,CAAC;IACrB,qEAAqE;IACrE,QAAQ,EAAE,cAAc,CAAC;IACzB,sDAAsD;IACtD,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,qBAAqB,CAsBvB"}
1
+ {"version":3,"file":"useCalendarDays.d.ts","sourceRoot":"","sources":["../../../src/Calendar/hooks/useCalendarDays.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,4BAA4B,CAAC;AAGpC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,mCAAmC;IACnC,GAAG,EAAE,aAAa,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,8BAA8B;IAC9B,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;IACvB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAuBvB"}
@@ -6,18 +6,19 @@
6
6
  * @file useCalendarDays.ts
7
7
  * @input Uses React useMemo, useLocale, PlainDate and calendar-system
8
8
  * utilities, generated CLDR weekday data
9
- * @output Exports useCalendarDays (a Gregorian month) and useCalendarMonthDays
10
- * (a month of any calendar system) for generating calendar day grids
11
- * @position Calendar-specific hooks; useCalendarMonthDays is used by Calendar
9
+ * @output Exports useCalendarDays, the day grid of one month of any calendar
10
+ * system (Gregorian by default)
11
+ * @position Calendar-specific hook; used by Calendar and MonthScroller
12
12
  *
13
13
  * SYNC: When modified, update:
14
14
  * - /packages/core/src/Calendar/hooks/index.ts
15
15
  */
16
16
  import { useMemo } from 'react';
17
- import { plainDateToISO, getDaysInMonth, plainDateDayOfWeek, plainDateAddDays } from "../../utils/plainDate.js";
17
+ import { plainDateToISO, plainDateDayOfWeek, plainDateAddDays } from "../../utils/plainDate.js";
18
18
  import { useLocale } from "../../i18n/index.js";
19
19
  import { getWeekdayHeaderNames } from "../getWeekdayHeaderNames.js";
20
- import { calendarDayOfMonth } from "../../utils/calendarSystem.js";
20
+ import { calendarDayOfMonth, calendarMonthFromYearMonth } from "../../utils/calendarSystem.js";
21
+ import { resolveShownCalendar } from "../../i18n/useCalendarSystem.js";
21
22
 
22
23
  /**
23
24
  * Represents a single day in the calendar grid.
@@ -45,6 +46,9 @@ import { calendarDayOfMonth } from "../../utils/calendarSystem.js";
45
46
  * weekStartsOn: 0, // Sunday
46
47
  * });
47
48
  * // days[i].date is a PlainDate { year, month (1-based), day }
49
+ *
50
+ * // Aban 1405 in the Persian calendar; dates stay Gregorian
51
+ * useCalendarDays({year: 1405, month: 8, calendar: 'persian'});
48
52
  * ```
49
53
  */
50
54
  export function useCalendarDays(options) {
@@ -52,43 +56,23 @@ export function useCalendarDays(options) {
52
56
  year,
53
57
  month,
54
58
  weekStartsOn = 0,
55
- hasVariableRowCount = false
59
+ hasVariableRowCount = false,
60
+ calendar: calendarOption = 'gregory'
56
61
  } = options;
57
- const days = useMemo(() => layOutMonthDays({
58
- first: {
62
+ const calendar = resolveShownCalendar(calendarOption, 'useCalendarDays');
63
+ const days = useMemo(() => {
64
+ const calendarMonth = calendarMonthFromYearMonth(calendar, {
59
65
  year,
60
- month,
61
- day: 1
62
- },
63
- length: getDaysInMonth(year, month),
64
- weekStartsOn,
65
- hasVariableRowCount,
66
- dayNumberOf: pd => pd.day
67
- }), [year, month, weekStartsOn, hasVariableRowCount]);
68
- return useMonthGrid(days, weekStartsOn);
69
- }
70
-
71
- /** Configuration for the day grid of one month of a calendar system. */
72
-
73
- /**
74
- * The day grid of one month of any calendar system. Each day keeps its
75
- * Gregorian date; `dayNumber` is its day of the calendar month. Internal to
76
- * Calendar.
77
- */
78
- export function useCalendarMonthDays(options) {
79
- const {
80
- month,
81
- calendar,
82
- weekStartsOn = 0,
83
- hasVariableRowCount = false
84
- } = options;
85
- const days = useMemo(() => layOutMonthDays({
86
- first: month.start,
87
- length: month.length,
88
- weekStartsOn,
89
- hasVariableRowCount,
90
- dayNumberOf: (pd, offset) => offset >= 0 && offset < month.length ? offset + 1 : calendarDayOfMonth(calendar, pd)
91
- }), [month, calendar, weekStartsOn, hasVariableRowCount]);
66
+ month
67
+ });
68
+ return layOutMonthDays({
69
+ first: calendarMonth.start,
70
+ length: calendarMonth.length,
71
+ weekStartsOn,
72
+ hasVariableRowCount,
73
+ dayNumberOf: (pd, offset) => offset >= 0 && offset < calendarMonth.length ? offset + 1 : calendarDayOfMonth(calendar, pd)
74
+ });
75
+ }, [year, month, calendar, weekStartsOn, hasVariableRowCount]);
92
76
  return useMonthGrid(days, weekStartsOn);
93
77
  }
94
78
  function layOutMonthDays(layout) {
@@ -1,5 +1,6 @@
1
1
  import type { ISODateString } from '../../utils/dateTypes.js';
2
2
  import { type PlainDate } from '../../utils/plainDate.js';
3
+ import { type CalendarSystem } from '../../utils/calendarSystem.js';
3
4
  /**
4
5
  * Configuration for calendar navigation
5
6
  */
@@ -12,16 +13,33 @@ export interface UseCalendarNavigationOptions {
12
13
  onFocusDateChange?: (focusDate: ISODateString) => void;
13
14
  /** Number of months to display */
14
15
  numberOfMonths?: 1 | 2;
16
+ /**
17
+ * The calendar system whose months the hook steps and labels. Dates stay
18
+ * Gregorian ISO. The hook never reads the InternationalizationProvider
19
+ * calendar. Calendars other than gregory, iso8601, buddhist, roc, and
20
+ * japanese need Temporal; without it, the hook steps Gregorian months and
21
+ * warns once in development.
22
+ * @default 'gregory'
23
+ */
24
+ calendar?: CalendarSystem;
15
25
  }
16
26
  /**
17
27
  * Return type for useCalendarNavigation hook
18
28
  */
19
29
  export interface UseCalendarNavigationReturn {
20
- /** The base month (first day of the focus month) as a PlainDate */
30
+ /** The Gregorian first day of the focus month */
21
31
  baseMonth: PlainDate;
22
- /** Array of visible months to render as PlainDates */
32
+ /** The Gregorian first day of each visible month */
23
33
  visibleMonths: PlainDate[];
24
- /** Formatted label for the month header */
34
+ /**
35
+ * The year and month numbers of each visible month in `calendar`, as
36
+ * `useCalendarDays` reads them with the same `calendar`
37
+ */
38
+ visibleCalendarMonths: {
39
+ year: number;
40
+ month: number;
41
+ }[];
42
+ /** Formatted label for the month header, in `calendar` */
25
43
  monthYearLabel: string;
26
44
  /** Navigate to previous/next month */
27
45
  navigateMonth: (delta: number, focusedDate?: ISODateString, offset?: number) => void;
@@ -55,6 +73,13 @@ export interface UseCalendarNavigationReturn {
55
73
  * // Navigate from arrow key (with focus tracking)
56
74
  * navigateMonth(1, focusedDate, 1); // horizontal offset
57
75
  * navigateMonth(1, focusedDate, 7); // vertical offset
76
+ *
77
+ * // Persian months, laid out by useCalendarDays
78
+ * const nav = useCalendarNavigation({initialValue, calendar: 'persian'});
79
+ * const {weeks} = useCalendarDays({
80
+ * ...nav.visibleCalendarMonths[0],
81
+ * calendar: 'persian',
82
+ * });
58
83
  * ```
59
84
  */
60
85
  export declare function useCalendarNavigation(options: UseCalendarNavigationOptions): UseCalendarNavigationReturn;
@@ -1 +1 @@
1
- {"version":3,"file":"useCalendarNavigation.d.ts","sourceRoot":"","sources":["../../../src/Calendar/hooks/useCalendarNavigation.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,KAAK,SAAS,EAOf,MAAM,uBAAuB,CAAC;AAI/B;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,gDAAgD;IAChD,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,qDAAqD;IACrD,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IACvD,kCAAkC;IAClC,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,mEAAmE;IACnE,SAAS,EAAE,SAAS,CAAC;IACrB,sDAAsD;IACtD,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,aAAa,EAAE,CACb,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,aAAa,EAC3B,MAAM,CAAC,EAAE,MAAM,KACZ,IAAI,CAAC;IACV,4CAA4C;IAC5C,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAyF7B"}
1
+ {"version":3,"file":"useCalendarNavigation.d.ts","sourceRoot":"","sources":["../../../src/Calendar/hooks/useCalendarNavigation.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,cAAc,EAKpB,MAAM,4BAA4B,CAAC;AAIpC;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,gDAAgD;IAChD,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,qDAAqD;IACrD,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IACvD,kCAAkC;IAClC,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,iDAAiD;IACjD,SAAS,EAAE,SAAS,CAAC;IACrB,oDAAoD;IACpD,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACvD,0DAA0D;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,aAAa,EAAE,CACb,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,aAAa,EAC3B,MAAM,CAAC,EAAE,MAAM,KACZ,IAAI,CAAC;IACV,4CAA4C;IAC5C,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAuG7B"}
@@ -4,17 +4,20 @@
4
4
 
5
5
  /**
6
6
  * @file useCalendarNavigation.ts
7
- * @input Uses React useState, useMemo, useCallback, useLocale, PlainDate utilities
8
- * @output Exports useCalendarNavigation hook for month navigation
9
- * @position Calendar-specific hook; used by Calendar
7
+ * @input Uses React useState, useMemo, useCallback, useLocale, PlainDate and
8
+ * calendar-system utilities
9
+ * @output Exports useCalendarNavigation hook for month navigation in any
10
+ * calendar system (Gregorian by default)
11
+ * @position Calendar-specific public hook
10
12
  *
11
13
  * SYNC: When modified, update:
12
14
  * - /packages/core/src/Calendar/hooks/index.ts
13
15
  */
14
16
  import { useState, useMemo, useCallback } from 'react';
15
- import { plainDateFromISO, plainDateToISO, plainDateToday, plainDateSetFirstOfMonth, plainDateAddMonths, plainDateAddDays } from "../../utils/plainDate.js";
16
- import { plainDateFormatStandaloneMonthYear } from "../../utils/plainDate.js";
17
+ import { plainDateFromISO, plainDateToISO, plainDateToday, plainDateAddDays } from "../../utils/plainDate.js";
18
+ import { calendarAddMonths, calendarMonthOf, calendarYearMonthOf, formatCalendarMonthLabel } from "../../utils/calendarSystem.js";
17
19
  import { useLocale } from "../../i18n/index.js";
20
+ import { resolveShownCalendar } from "../../i18n/useCalendarSystem.js";
18
21
 
19
22
  /**
20
23
  * Configuration for calendar navigation
@@ -49,6 +52,13 @@ import { useLocale } from "../../i18n/index.js";
49
52
  * // Navigate from arrow key (with focus tracking)
50
53
  * navigateMonth(1, focusedDate, 1); // horizontal offset
51
54
  * navigateMonth(1, focusedDate, 7); // vertical offset
55
+ *
56
+ * // Persian months, laid out by useCalendarDays
57
+ * const nav = useCalendarNavigation({initialValue, calendar: 'persian'});
58
+ * const {weeks} = useCalendarDays({
59
+ * ...nav.visibleCalendarMonths[0],
60
+ * calendar: 'persian',
61
+ * });
52
62
  * ```
53
63
  */
54
64
  export function useCalendarNavigation(options) {
@@ -56,9 +66,11 @@ export function useCalendarNavigation(options) {
56
66
  initialValue,
57
67
  focusDate: focusDateProp,
58
68
  onFocusDateChange,
59
- numberOfMonths = 1
69
+ numberOfMonths = 1,
70
+ calendar: calendarOption = 'gregory'
60
71
  } = options;
61
72
  const locale = useLocale();
73
+ const calendar = resolveShownCalendar(calendarOption, 'useCalendarNavigation');
62
74
 
63
75
  // Pending focus target after month navigation
64
76
  const [pendingFocus, setPendingFocus] = useState(null);
@@ -78,27 +90,25 @@ export function useCalendarNavigation(options) {
78
90
  const isControlledFocus = focusDateProp !== undefined && onFocusDateChange !== undefined;
79
91
  const focusDate = isControlledFocus ? plainDateFromISO(focusDateProp) : internalFocusDate;
80
92
 
81
- // Base month (first day of focus month)
82
- const baseMonth = useMemo(() => {
83
- return plainDateSetFirstOfMonth(focusDate);
84
- }, [focusDate]);
85
-
86
- // Generate visible months
87
- const visibleMonths = useMemo(() => {
93
+ // A controlled focus date is a new object on each render. Key the memo on
94
+ // its ISO string.
95
+ const focusIso = plainDateToISO(focusDate);
96
+ const shownMonths = useMemo(() => {
97
+ const first = calendarMonthOf(calendar, plainDateFromISO(focusIso));
88
98
  return Array.from({
89
99
  length: numberOfMonths
90
- }, (_, i) => {
91
- return plainDateAddMonths(baseMonth, i);
92
- });
93
- }, [baseMonth, numberOfMonths]);
100
+ }, (_, i) => calendarAddMonths(calendar, first, i));
101
+ }, [calendar, focusIso, numberOfMonths]);
102
+ const visibleMonths = useMemo(() => shownMonths.map(month => month.start), [shownMonths]);
103
+ const baseMonth = visibleMonths[0];
104
+ const visibleCalendarMonths = useMemo(() => shownMonths.map(month => calendarYearMonthOf(calendar, month)), [calendar, shownMonths]);
94
105
 
95
- // Format month header
96
- // Each month is its own stand-alone label (Calendar FR1).
97
- const monthYearLabel = useMemo(() => visibleMonths.map(m => plainDateFormatStandaloneMonthYear(m, locale)).join(' – '), [visibleMonths, locale]);
106
+ // Each month is its own stand-alone label.
107
+ const monthYearLabel = useMemo(() => shownMonths.map(month => formatCalendarMonthLabel(calendar, month, locale)).join(' – '), [calendar, shownMonths, locale]);
98
108
 
99
109
  // Navigate to previous/next month
100
110
  const navigateMonth = useCallback((delta, focusedDate, offset) => {
101
- const newMonth = plainDateAddMonths(baseMonth, delta);
111
+ const newMonth = calendarAddMonths(calendar, shownMonths[0], delta).start;
102
112
  const newISO = plainDateToISO(newMonth);
103
113
 
104
114
  // Calculate target focus date if provided
@@ -114,7 +124,7 @@ export function useCalendarNavigation(options) {
114
124
  } else {
115
125
  setInternalFocusDate(newMonth);
116
126
  }
117
- }, [baseMonth, onFocusDateChange]);
127
+ }, [calendar, shownMonths, onFocusDateChange]);
118
128
 
119
129
  // Clear pending focus
120
130
  const clearPendingFocus = useCallback(() => {
@@ -123,6 +133,7 @@ export function useCalendarNavigation(options) {
123
133
  return {
124
134
  baseMonth,
125
135
  visibleMonths,
136
+ visibleCalendarMonths,
126
137
  monthYearLabel,
127
138
  navigateMonth,
128
139
  pendingFocus,
@@ -4,6 +4,12 @@ import { type CalendarSystem } from '../utils/calendarSystem.js';
4
4
  * resolves to `gregory` and warns once in development.
5
5
  */
6
6
  export declare function resolveCalendarSystem(id: string, source: string): CalendarSystem;
7
+ /**
8
+ * The calendar that a caller names, without the provider: an unknown
9
+ * identifier, or one this runtime cannot show, becomes `gregory` and warns
10
+ * once in development.
11
+ */
12
+ export declare function resolveShownCalendar(calendar: string, source: string): CalendarSystem;
7
13
  /**
8
14
  * The calendar a date component shows: its own `calendar`, else the
9
15
  * provider's resolved calendar, else `gregory`. It resolves during render, so
@@ -1 +1 @@
1
- {"version":3,"file":"useCalendarSystem.d.ts","sourceRoot":"","sources":["../../src/i18n/useCalendarSystem.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,yBAAyB,CAAC;AAGjC;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,GACb,cAAc,CAUhB;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,MAAM,EAAE,MAAM,GACb,cAAc,CAOhB"}
1
+ {"version":3,"file":"useCalendarSystem.d.ts","sourceRoot":"","sources":["../../src/i18n/useCalendarSystem.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,yBAAyB,CAAC;AAGjC;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,GACb,cAAc,CAUhB;AAyBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,cAAc,CAKhB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,MAAM,EAAE,MAAM,GACb,cAAc,CAMhB"}
@@ -5,10 +5,11 @@
5
5
  /**
6
6
  * @file useCalendarSystem.ts
7
7
  * @input InternationalizationContext, an optional component `calendar`
8
- * @output The calendar system a date component shows, and the helper that
9
- * normalizes a calendar identifier
10
- * @position Internal i18n hook; read by InternationalizationProvider and date
11
- * components. Not re-exported from the i18n barrel.
8
+ * @output The calendar system a date component shows, and the helpers that
9
+ * normalize a calendar identifier
10
+ * @position Internal i18n hook; read by InternationalizationProvider, date
11
+ * components, and the public calendar hooks. Not re-exported from the i18n
12
+ * barrel.
12
13
  */
13
14
  import { use } from 'react';
14
15
  import { InternationalizationContext } from "./InternationalizationContext.js";
@@ -40,6 +41,15 @@ function availableCalendarOrGregorian(calendar, source) {
40
41
  return 'gregory';
41
42
  }
42
43
 
44
+ /**
45
+ * The calendar that a caller names, without the provider: an unknown
46
+ * identifier, or one this runtime cannot show, becomes `gregory` and warns
47
+ * once in development.
48
+ */
49
+ export function resolveShownCalendar(calendar, source) {
50
+ return availableCalendarOrGregorian(resolveCalendarSystem(calendar, source), source);
51
+ }
52
+
43
53
  /**
44
54
  * The calendar a date component shows: its own `calendar`, else the
45
55
  * provider's resolved calendar, else `gregory`. It resolves during render, so
@@ -47,6 +57,5 @@ function availableCalendarOrGregorian(calendar, source) {
47
57
  */
48
58
  export function useCalendarSystem(calendar, source) {
49
59
  const context = use(InternationalizationContext);
50
- const requested = calendar != null ? resolveCalendarSystem(calendar, source) : context.calendar ?? 'gregory';
51
- return availableCalendarOrGregorian(requested, source);
60
+ return resolveShownCalendar(calendar ?? context.calendar ?? 'gregory', source);
52
61
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * @file calendarSystem.ts
3
3
  * @input A calendar identifier and Gregorian PlainDates
4
- * @output Calendar months located by their Gregorian days, month and year
5
- * steps, dates from calendar fields, availability of each calendar, and
6
- * calendar labels and digits
7
- * @position Shared date utility; read by the i18n calendar resolution and by
8
- * Calendar. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
4
+ * @output Calendar months located by their Gregorian days, their year and
5
+ * month numbers, month and year steps, dates from calendar fields,
6
+ * availability of each calendar, and calendar labels and digits
7
+ * @position Shared date utility; read by the i18n calendar resolution, by
8
+ * Calendar, and by its public hooks. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
9
9
  * every calendar whose months are not Gregorian.
10
10
  */
11
11
  import type { Locale } from '../i18n/types.js';
@@ -36,6 +36,21 @@ export interface CalendarMonth {
36
36
  export declare function calendarDayOfMonth(calendar: CalendarSystem, pd: PlainDate): number;
37
37
  /** The calendar month that contains a Gregorian date. */
38
38
  export declare function calendarMonthOf(calendar: CalendarSystem, pd: PlainDate): CalendarMonth;
39
+ /**
40
+ * A calendar month by its year and its position in that year, as
41
+ * `Temporal.PlainDate` numbers them: month 13 exists in a Hebrew leap year.
42
+ */
43
+ export interface CalendarYearMonth {
44
+ readonly year: number;
45
+ readonly month: number;
46
+ }
47
+ /** The year and month numbers of a calendar month. */
48
+ export declare function calendarYearMonthOf(calendar: CalendarSystem, month: CalendarMonth): CalendarYearMonth;
49
+ /**
50
+ * The calendar month with these year and month numbers. A month past the end
51
+ * of its year becomes the last month of that year, as Temporal constrains it.
52
+ */
53
+ export declare function calendarMonthFromYearMonth(calendar: CalendarSystem, { year, month }: CalendarYearMonth): CalendarMonth;
39
54
  /** The calendar month `n` months after `month`, or before it when negative. */
40
55
  export declare function calendarAddMonths(calendar: CalendarSystem, month: CalendarMonth, n: number): CalendarMonth;
41
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"calendarSystem.d.ts","sourceRoot":"","sources":["../../src/utils/calendarSystem.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"calendarSystem.d.ts","sourceRoot":"","sources":["../../src/utils/calendarSystem.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,11 @@
3
3
  /**
4
4
  * @file calendarSystem.ts
5
5
  * @input A calendar identifier and Gregorian PlainDates
6
- * @output Calendar months located by their Gregorian days, month and year
7
- * steps, dates from calendar fields, availability of each calendar, and
8
- * calendar labels and digits
9
- * @position Shared date utility; read by the i18n calendar resolution and by
10
- * Calendar. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
6
+ * @output Calendar months located by their Gregorian days, their year and
7
+ * month numbers, month and year steps, dates from calendar fields,
8
+ * availability of each calendar, and calendar labels and digits
9
+ * @position Shared date utility; read by the i18n calendar resolution, by
10
+ * Calendar, and by its public hooks. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
11
11
  * every calendar whose months are not Gregorian.
12
12
  */
13
13
 
@@ -142,6 +142,59 @@ export function calendarMonthOf(calendar, pd) {
142
142
  }));
143
143
  }
144
144
 
145
+ /**
146
+ * A calendar month by its year and its position in that year, as
147
+ * `Temporal.PlainDate` numbers them: month 13 exists in a Hebrew leap year.
148
+ */
149
+
150
+ // Temporal counts the years of these Gregorian-aligned calendars from another
151
+ // epoch: 2026 is 2569 in `buddhist` and 115 in `roc`.
152
+ const GREGORIAN_YEAR_OFFSETS = {
153
+ buddhist: 543,
154
+ roc: -1911
155
+ };
156
+
157
+ /** The year and month numbers of a calendar month. */
158
+ export function calendarYearMonthOf(calendar, month) {
159
+ if (isGregorianAligned(calendar)) {
160
+ return {
161
+ year: month.start.year + (GREGORIAN_YEAR_OFFSETS[calendar] ?? 0),
162
+ month: month.start.month
163
+ };
164
+ }
165
+ const {
166
+ year,
167
+ month: monthOfYear
168
+ } = inCalendar(calendar, month.start);
169
+ return {
170
+ year,
171
+ month: monthOfYear
172
+ };
173
+ }
174
+
175
+ /**
176
+ * The calendar month with these year and month numbers. A month past the end
177
+ * of its year becomes the last month of that year, as Temporal constrains it.
178
+ */
179
+ export function calendarMonthFromYearMonth(calendar, {
180
+ year,
181
+ month
182
+ }) {
183
+ if (isGregorianAligned(calendar)) {
184
+ return gregorianMonth(year - (GREGORIAN_YEAR_OFFSETS[calendar] ?? 0), month);
185
+ }
186
+ // `with` replaces the year and month of any date in the calendar.
187
+ return temporalMonth(inCalendar(calendar, {
188
+ year: 2000,
189
+ month: 1,
190
+ day: 1
191
+ }).with({
192
+ year,
193
+ month,
194
+ day: 1
195
+ }));
196
+ }
197
+
145
198
  /** The calendar month `n` months after `month`, or before it when negative. */
146
199
  export function calendarAddMonths(calendar, month, n) {
147
200
  if (n === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.9-canary.f8a309d",
3
+ "version": "0.7.9-canary.ffa495b",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -875,9 +875,9 @@
875
875
  "react-dom": ">=19.0.0"
876
876
  },
877
877
  "devDependencies": {
878
- "@astryxdesign/a11y-spec": "0.7.9-canary.f8a309d",
879
- "@astryxdesign/cli": "0.7.9-canary.f8a309d",
880
- "@astryxdesign/interaction-harness": "0.7.9-canary.f8a309d",
878
+ "@astryxdesign/a11y-spec": "0.7.9-canary.ffa495b",
879
+ "@astryxdesign/cli": "0.7.9-canary.ffa495b",
880
+ "@astryxdesign/interaction-harness": "0.7.9-canary.ffa495b",
881
881
  "@babel/cli": "^7.29.7",
882
882
  "@babel/core": "^7.29.7",
883
883
  "@babel/preset-react": "^7.29.7",
@@ -39,6 +39,11 @@ export const docs = {
39
39
  description:
40
40
  'Show two months side by side when the user frequently selects dates that span a month boundary.',
41
41
  },
42
+ {
43
+ guidance: true,
44
+ description:
45
+ 'For a custom month view, use useCalendarNavigation and useCalendarDays from @astryxdesign/core/Calendar. To show another calendar system, pass the same calendar to both and feed visibleCalendarMonths into useCalendarDays. The hooks default to gregory and never read the InternationalizationProvider calendar.',
46
+ },
42
47
  {
43
48
  guidance: false,
44
49
  description:
@@ -195,6 +200,41 @@ export const docs = {
195
200
  default: 'the InternationalizationProvider calendar, else gregory',
196
201
  },
197
202
  ],
203
+ examples: [
204
+ {
205
+ label: 'A custom month view in the Persian calendar',
206
+ code: `import {Button} from '@astryxdesign/core/Button';
207
+ import {
208
+ useCalendarDays,
209
+ useCalendarNavigation,
210
+ } from '@astryxdesign/core/Calendar';
211
+
212
+ function PersianMonth() {
213
+ const {monthYearLabel, visibleCalendarMonths, navigateMonth} =
214
+ useCalendarNavigation({initialValue: '2026-10-30', calendar: 'persian'});
215
+ // visibleCalendarMonths[0] is {year: 1405, month: 8}: Aban 1405.
216
+ const {weeks} = useCalendarDays({
217
+ ...visibleCalendarMonths[0],
218
+ calendar: 'persian',
219
+ });
220
+ return (
221
+ <section aria-label={monthYearLabel}>
222
+ <Button label="Previous month" onClick={() => navigateMonth(-1)} />
223
+ <Button label="Next month" onClick={() => navigateMonth(1)} />
224
+ {weeks.map(week => (
225
+ <div key={week[0].iso}>
226
+ {week.map(day => (
227
+ <span key={day.iso} data-date={day.iso}>
228
+ {day.isOutside ? '' : day.dayNumber}
229
+ </span>
230
+ ))}
231
+ </div>
232
+ ))}
233
+ </section>
234
+ );
235
+ }`,
236
+ },
237
+ ],
198
238
  theming: {
199
239
  targets: [
200
240
  {className: 'astryx-calendar', visualProps: ['mode']},
@@ -239,6 +279,11 @@ export const docsZh = {
239
279
  description:
240
280
  'Show two months side by side when the user frequently selects dates that span a month boundary.',
241
281
  },
282
+ {
283
+ guidance: true,
284
+ description:
285
+ 'For a custom month view, use useCalendarNavigation and useCalendarDays from @astryxdesign/core/Calendar. To show another calendar system, pass the same calendar to both and feed visibleCalendarMonths into useCalendarDays. The hooks default to gregory and never read the InternationalizationProvider calendar.',
286
+ },
242
287
  {
243
288
  guidance: false,
244
289
  description:
@@ -391,6 +436,11 @@ export const docsDense = {
391
436
  description:
392
437
  'Show two months side by side when user frequently selects dates spanning a month boundary.',
393
438
  },
439
+ {
440
+ guidance: true,
441
+ description:
442
+ 'Custom month view: useCalendarNavigation + useCalendarDays; pass the same calendar to both, feed visibleCalendarMonths into useCalendarDays. Default gregory; never read the provider calendar.',
443
+ },
394
444
  {
395
445
  guidance: false,
396
446
  description:
@@ -32,7 +32,7 @@ import {Button} from '../Button';
32
32
  import {Icon} from '../Icon';
33
33
  import {useAnnounce, useGridFocus} from '../hooks';
34
34
  import {useCalendarConstraints, type CalendarDay} from './hooks';
35
- import {useCalendarMonthDays} from './hooks/useCalendarDays';
35
+ import {useCalendarDays} from './hooks/useCalendarDays';
36
36
  import {
37
37
  calendarStyles,
38
38
  monthGridStyles,
@@ -62,6 +62,7 @@ import {
62
62
  calendarMonthOf,
63
63
  calendarShiftMonths,
64
64
  calendarShiftYears,
65
+ calendarYearMonthOf,
65
66
  formatCalendarDate,
66
67
  formatCalendarMonthLabel,
67
68
  isInCalendarMonth,
@@ -864,8 +865,13 @@ function MonthGrid({
864
865
  const locale = useLocale();
865
866
 
866
867
  // Use hooks for days generation and constraints
867
- const {days, weeks} = useCalendarMonthDays({
868
- month,
868
+ const yearMonth = useMemo(
869
+ () => calendarYearMonthOf(calendar, month),
870
+ [calendar, month],
871
+ );
872
+ const {days, weeks} = useCalendarDays({
873
+ year: yearMonth.year,
874
+ month: yearMonth.month,
869
875
  calendar,
870
876
  weekStartsOn,
871
877
  hasVariableRowCount,
@@ -6,9 +6,9 @@
6
6
  * @file useCalendarDays.ts
7
7
  * @input Uses React useMemo, useLocale, PlainDate and calendar-system
8
8
  * utilities, generated CLDR weekday data
9
- * @output Exports useCalendarDays (a Gregorian month) and useCalendarMonthDays
10
- * (a month of any calendar system) for generating calendar day grids
11
- * @position Calendar-specific hooks; useCalendarMonthDays is used by Calendar
9
+ * @output Exports useCalendarDays, the day grid of one month of any calendar
10
+ * system (Gregorian by default)
11
+ * @position Calendar-specific hook; used by Calendar and MonthScroller
12
12
  *
13
13
  * SYNC: When modified, update:
14
14
  * - /packages/core/src/Calendar/hooks/index.ts
@@ -19,17 +19,17 @@ import type {DayOfWeek, ISODateString} from '../../utils/dateTypes';
19
19
  import {
20
20
  type PlainDate,
21
21
  plainDateToISO,
22
- getDaysInMonth,
23
22
  plainDateDayOfWeek,
24
23
  plainDateAddDays,
25
24
  } from '../../utils/plainDate';
26
25
  import {useLocale} from '../../i18n';
27
26
  import {getWeekdayHeaderNames} from '../getWeekdayHeaderNames';
28
27
  import {
29
- type CalendarMonth,
30
28
  type CalendarSystem,
31
29
  calendarDayOfMonth,
30
+ calendarMonthFromYearMonth,
32
31
  } from '../../utils/calendarSystem';
32
+ import {resolveShownCalendar} from '../../i18n/useCalendarSystem';
33
33
 
34
34
  /**
35
35
  * Represents a single day in the calendar grid.
@@ -49,14 +49,27 @@ export interface CalendarDay {
49
49
  * Configuration for calendar days generation
50
50
  */
51
51
  export interface UseCalendarDaysOptions {
52
- /** The year to generate days for */
52
+ /** The year to generate days for, in `calendar`'s numbering */
53
53
  year: number;
54
- /** The month (1-based: 1 = January, 12 = December) */
54
+ /**
55
+ * The month's position in its year (1-based). In the Gregorian calendar,
56
+ * 1 is January and 12 is December. In other calendars it is numbered as
57
+ * `Temporal.PlainDate` numbers it: 1 through 13 in a Hebrew leap year.
58
+ */
55
59
  month: number;
56
60
  /** First day of week (0=Sunday through 6=Saturday) */
57
61
  weekStartsOn?: DayOfWeek;
58
62
  /** Use variable rows per month vs. fixed 6-row grid */
59
63
  hasVariableRowCount?: boolean;
64
+ /**
65
+ * The calendar system that numbers `year`, `month`, and each `dayNumber`.
66
+ * Each day keeps its Gregorian `date` and `iso`. The hook never reads the
67
+ * InternationalizationProvider calendar. Calendars other than gregory,
68
+ * iso8601, buddhist, roc, and japanese need Temporal; without it, the hook
69
+ * reads Gregorian numbers and warns once in development.
70
+ * @default 'gregory'
71
+ */
72
+ calendar?: CalendarSystem;
60
73
  }
61
74
 
62
75
  /**
@@ -87,66 +100,35 @@ export interface UseCalendarDaysReturn {
87
100
  * weekStartsOn: 0, // Sunday
88
101
  * });
89
102
  * // days[i].date is a PlainDate { year, month (1-based), day }
103
+ *
104
+ * // Aban 1405 in the Persian calendar; dates stay Gregorian
105
+ * useCalendarDays({year: 1405, month: 8, calendar: 'persian'});
90
106
  * ```
91
107
  */
92
108
  export function useCalendarDays(
93
109
  options: UseCalendarDaysOptions,
94
- ): UseCalendarDaysReturn {
95
- const {year, month, weekStartsOn = 0, hasVariableRowCount = false} = options;
96
- const days = useMemo(
97
- () =>
98
- layOutMonthDays({
99
- first: {year, month, day: 1},
100
- length: getDaysInMonth(year, month),
101
- weekStartsOn,
102
- hasVariableRowCount,
103
- dayNumberOf: pd => pd.day,
104
- }),
105
- [year, month, weekStartsOn, hasVariableRowCount],
106
- );
107
- return useMonthGrid(days, weekStartsOn);
108
- }
109
-
110
- /** Configuration for the day grid of one month of a calendar system. */
111
- export interface UseCalendarMonthDaysOptions {
112
- /** The month to lay out, located by its Gregorian days. */
113
- month: CalendarMonth;
114
- /** The calendar system the month belongs to; it numbers the days. */
115
- calendar: CalendarSystem;
116
- /** First day of week (0=Sunday through 6=Saturday) */
117
- weekStartsOn?: DayOfWeek;
118
- /** Use variable rows per month vs. fixed 6-row grid */
119
- hasVariableRowCount?: boolean;
120
- }
121
-
122
- /**
123
- * The day grid of one month of any calendar system. Each day keeps its
124
- * Gregorian date; `dayNumber` is its day of the calendar month. Internal to
125
- * Calendar.
126
- */
127
- export function useCalendarMonthDays(
128
- options: UseCalendarMonthDaysOptions,
129
110
  ): UseCalendarDaysReturn {
130
111
  const {
112
+ year,
131
113
  month,
132
- calendar,
133
114
  weekStartsOn = 0,
134
115
  hasVariableRowCount = false,
116
+ calendar: calendarOption = 'gregory',
135
117
  } = options;
136
- const days = useMemo(
137
- () =>
138
- layOutMonthDays({
139
- first: month.start,
140
- length: month.length,
141
- weekStartsOn,
142
- hasVariableRowCount,
143
- dayNumberOf: (pd, offset) =>
144
- offset >= 0 && offset < month.length
145
- ? offset + 1
146
- : calendarDayOfMonth(calendar, pd),
147
- }),
148
- [month, calendar, weekStartsOn, hasVariableRowCount],
149
- );
118
+ const calendar = resolveShownCalendar(calendarOption, 'useCalendarDays');
119
+ const days = useMemo(() => {
120
+ const calendarMonth = calendarMonthFromYearMonth(calendar, {year, month});
121
+ return layOutMonthDays({
122
+ first: calendarMonth.start,
123
+ length: calendarMonth.length,
124
+ weekStartsOn,
125
+ hasVariableRowCount,
126
+ dayNumberOf: (pd, offset) =>
127
+ offset >= 0 && offset < calendarMonth.length
128
+ ? offset + 1
129
+ : calendarDayOfMonth(calendar, pd),
130
+ });
131
+ }, [year, month, calendar, weekStartsOn, hasVariableRowCount]);
150
132
  return useMonthGrid(days, weekStartsOn);
151
133
  }
152
134
 
@@ -4,9 +4,11 @@
4
4
 
5
5
  /**
6
6
  * @file useCalendarNavigation.ts
7
- * @input Uses React useState, useMemo, useCallback, useLocale, PlainDate utilities
8
- * @output Exports useCalendarNavigation hook for month navigation
9
- * @position Calendar-specific hook; used by Calendar
7
+ * @input Uses React useState, useMemo, useCallback, useLocale, PlainDate and
8
+ * calendar-system utilities
9
+ * @output Exports useCalendarNavigation hook for month navigation in any
10
+ * calendar system (Gregorian by default)
11
+ * @position Calendar-specific public hook
10
12
  *
11
13
  * SYNC: When modified, update:
12
14
  * - /packages/core/src/Calendar/hooks/index.ts
@@ -19,12 +21,17 @@ import {
19
21
  plainDateFromISO,
20
22
  plainDateToISO,
21
23
  plainDateToday,
22
- plainDateSetFirstOfMonth,
23
- plainDateAddMonths,
24
24
  plainDateAddDays,
25
25
  } from '../../utils/plainDate';
26
- import {plainDateFormatStandaloneMonthYear} from '../../utils/plainDate';
26
+ import {
27
+ type CalendarSystem,
28
+ calendarAddMonths,
29
+ calendarMonthOf,
30
+ calendarYearMonthOf,
31
+ formatCalendarMonthLabel,
32
+ } from '../../utils/calendarSystem';
27
33
  import {useLocale} from '../../i18n';
34
+ import {resolveShownCalendar} from '../../i18n/useCalendarSystem';
28
35
 
29
36
  /**
30
37
  * Configuration for calendar navigation
@@ -38,17 +45,31 @@ export interface UseCalendarNavigationOptions {
38
45
  onFocusDateChange?: (focusDate: ISODateString) => void;
39
46
  /** Number of months to display */
40
47
  numberOfMonths?: 1 | 2;
48
+ /**
49
+ * The calendar system whose months the hook steps and labels. Dates stay
50
+ * Gregorian ISO. The hook never reads the InternationalizationProvider
51
+ * calendar. Calendars other than gregory, iso8601, buddhist, roc, and
52
+ * japanese need Temporal; without it, the hook steps Gregorian months and
53
+ * warns once in development.
54
+ * @default 'gregory'
55
+ */
56
+ calendar?: CalendarSystem;
41
57
  }
42
58
 
43
59
  /**
44
60
  * Return type for useCalendarNavigation hook
45
61
  */
46
62
  export interface UseCalendarNavigationReturn {
47
- /** The base month (first day of the focus month) as a PlainDate */
63
+ /** The Gregorian first day of the focus month */
48
64
  baseMonth: PlainDate;
49
- /** Array of visible months to render as PlainDates */
65
+ /** The Gregorian first day of each visible month */
50
66
  visibleMonths: PlainDate[];
51
- /** Formatted label for the month header */
67
+ /**
68
+ * The year and month numbers of each visible month in `calendar`, as
69
+ * `useCalendarDays` reads them with the same `calendar`
70
+ */
71
+ visibleCalendarMonths: {year: number; month: number}[];
72
+ /** Formatted label for the month header, in `calendar` */
52
73
  monthYearLabel: string;
53
74
  /** Navigate to previous/next month */
54
75
  navigateMonth: (
@@ -87,6 +108,13 @@ export interface UseCalendarNavigationReturn {
87
108
  * // Navigate from arrow key (with focus tracking)
88
109
  * navigateMonth(1, focusedDate, 1); // horizontal offset
89
110
  * navigateMonth(1, focusedDate, 7); // vertical offset
111
+ *
112
+ * // Persian months, laid out by useCalendarDays
113
+ * const nav = useCalendarNavigation({initialValue, calendar: 'persian'});
114
+ * const {weeks} = useCalendarDays({
115
+ * ...nav.visibleCalendarMonths[0],
116
+ * calendar: 'persian',
117
+ * });
90
118
  * ```
91
119
  */
92
120
  export function useCalendarNavigation(
@@ -97,8 +125,13 @@ export function useCalendarNavigation(
97
125
  focusDate: focusDateProp,
98
126
  onFocusDateChange,
99
127
  numberOfMonths = 1,
128
+ calendar: calendarOption = 'gregory',
100
129
  } = options;
101
130
  const locale = useLocale();
131
+ const calendar = resolveShownCalendar(
132
+ calendarOption,
133
+ 'useCalendarNavigation',
134
+ );
102
135
 
103
136
  // Pending focus target after month navigation
104
137
  const [pendingFocus, setPendingFocus] = useState<ISODateString | null>(null);
@@ -121,32 +154,40 @@ export function useCalendarNavigation(
121
154
  ? plainDateFromISO(focusDateProp)
122
155
  : internalFocusDate;
123
156
 
124
- // Base month (first day of focus month)
125
- const baseMonth = useMemo(() => {
126
- return plainDateSetFirstOfMonth(focusDate);
127
- }, [focusDate]);
157
+ // A controlled focus date is a new object on each render. Key the memo on
158
+ // its ISO string.
159
+ const focusIso = plainDateToISO(focusDate);
160
+ const shownMonths = useMemo(() => {
161
+ const first = calendarMonthOf(calendar, plainDateFromISO(focusIso));
162
+ return Array.from({length: numberOfMonths}, (_, i) =>
163
+ calendarAddMonths(calendar, first, i),
164
+ );
165
+ }, [calendar, focusIso, numberOfMonths]);
166
+
167
+ const visibleMonths = useMemo(
168
+ () => shownMonths.map(month => month.start),
169
+ [shownMonths],
170
+ );
171
+ const baseMonth = visibleMonths[0];
128
172
 
129
- // Generate visible months
130
- const visibleMonths = useMemo(() => {
131
- return Array.from({length: numberOfMonths}, (_, i) => {
132
- return plainDateAddMonths(baseMonth, i);
133
- });
134
- }, [baseMonth, numberOfMonths]);
173
+ const visibleCalendarMonths = useMemo(
174
+ () => shownMonths.map(month => calendarYearMonthOf(calendar, month)),
175
+ [calendar, shownMonths],
176
+ );
135
177
 
136
- // Format month header
137
- // Each month is its own stand-alone label (Calendar FR1).
178
+ // Each month is its own stand-alone label.
138
179
  const monthYearLabel = useMemo(
139
180
  () =>
140
- visibleMonths
141
- .map(m => plainDateFormatStandaloneMonthYear(m, locale))
181
+ shownMonths
182
+ .map(month => formatCalendarMonthLabel(calendar, month, locale))
142
183
  .join(' – '),
143
- [visibleMonths, locale],
184
+ [calendar, shownMonths, locale],
144
185
  );
145
186
 
146
187
  // Navigate to previous/next month
147
188
  const navigateMonth = useCallback(
148
189
  (delta: number, focusedDate?: ISODateString, offset?: number) => {
149
- const newMonth = plainDateAddMonths(baseMonth, delta);
190
+ const newMonth = calendarAddMonths(calendar, shownMonths[0], delta).start;
150
191
  const newISO = plainDateToISO(newMonth);
151
192
 
152
193
  // Calculate target focus date if provided
@@ -164,7 +205,7 @@ export function useCalendarNavigation(
164
205
  setInternalFocusDate(newMonth);
165
206
  }
166
207
  },
167
- [baseMonth, onFocusDateChange],
208
+ [calendar, shownMonths, onFocusDateChange],
168
209
  );
169
210
 
170
211
  // Clear pending focus
@@ -175,6 +216,7 @@ export function useCalendarNavigation(
175
216
  return {
176
217
  baseMonth,
177
218
  visibleMonths,
219
+ visibleCalendarMonths,
178
220
  monthYearLabel,
179
221
  navigateMonth,
180
222
  pendingFocus,
@@ -5,10 +5,11 @@
5
5
  /**
6
6
  * @file useCalendarSystem.ts
7
7
  * @input InternationalizationContext, an optional component `calendar`
8
- * @output The calendar system a date component shows, and the helper that
9
- * normalizes a calendar identifier
10
- * @position Internal i18n hook; read by InternationalizationProvider and date
11
- * components. Not re-exported from the i18n barrel.
8
+ * @output The calendar system a date component shows, and the helpers that
9
+ * normalize a calendar identifier
10
+ * @position Internal i18n hook; read by InternationalizationProvider, date
11
+ * components, and the public calendar hooks. Not re-exported from the i18n
12
+ * barrel.
12
13
  */
13
14
 
14
15
  import {use} from 'react';
@@ -62,6 +63,21 @@ function availableCalendarOrGregorian(
62
63
  return 'gregory';
63
64
  }
64
65
 
66
+ /**
67
+ * The calendar that a caller names, without the provider: an unknown
68
+ * identifier, or one this runtime cannot show, becomes `gregory` and warns
69
+ * once in development.
70
+ */
71
+ export function resolveShownCalendar(
72
+ calendar: string,
73
+ source: string,
74
+ ): CalendarSystem {
75
+ return availableCalendarOrGregorian(
76
+ resolveCalendarSystem(calendar, source),
77
+ source,
78
+ );
79
+ }
80
+
65
81
  /**
66
82
  * The calendar a date component shows: its own `calendar`, else the
67
83
  * provider's resolved calendar, else `gregory`. It resolves during render, so
@@ -72,9 +88,8 @@ export function useCalendarSystem(
72
88
  source: string,
73
89
  ): CalendarSystem {
74
90
  const context = use(InternationalizationContext);
75
- const requested =
76
- calendar != null
77
- ? resolveCalendarSystem(calendar, source)
78
- : (context.calendar ?? 'gregory');
79
- return availableCalendarOrGregorian(requested, source);
91
+ return resolveShownCalendar(
92
+ calendar ?? context.calendar ?? 'gregory',
93
+ source,
94
+ );
80
95
  }
@@ -3,11 +3,11 @@
3
3
  /**
4
4
  * @file calendarSystem.ts
5
5
  * @input A calendar identifier and Gregorian PlainDates
6
- * @output Calendar months located by their Gregorian days, month and year
7
- * steps, dates from calendar fields, availability of each calendar, and
8
- * calendar labels and digits
9
- * @position Shared date utility; read by the i18n calendar resolution and by
10
- * Calendar. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
6
+ * @output Calendar months located by their Gregorian days, their year and
7
+ * month numbers, month and year steps, dates from calendar fields,
8
+ * availability of each calendar, and calendar labels and digits
9
+ * @position Shared date utility; read by the i18n calendar resolution, by
10
+ * Calendar, and by its public hooks. Values stay Gregorian PlainDates. Temporal owns the arithmetic of
11
11
  * every calendar whose months are not Gregorian.
12
12
  */
13
13
 
@@ -97,7 +97,7 @@ interface TemporalPlainDate {
97
97
  readonly day: number;
98
98
  readonly daysInMonth: number;
99
99
  readonly monthsInYear: number;
100
- with(fields: {day: number}): TemporalPlainDate;
100
+ with(fields: {year?: number; month?: number; day: number}): TemporalPlainDate;
101
101
  add(duration: {months?: number; years?: number}): TemporalPlainDate;
102
102
  withCalendar(calendar: 'iso8601'): TemporalPlainDate;
103
103
  }
@@ -216,6 +216,61 @@ export function calendarMonthOf(
216
216
  return temporalMonth(inCalendar(calendar, pd).with({day: 1}));
217
217
  }
218
218
 
219
+ /**
220
+ * A calendar month by its year and its position in that year, as
221
+ * `Temporal.PlainDate` numbers them: month 13 exists in a Hebrew leap year.
222
+ */
223
+ export interface CalendarYearMonth {
224
+ readonly year: number;
225
+ readonly month: number;
226
+ }
227
+
228
+ // Temporal counts the years of these Gregorian-aligned calendars from another
229
+ // epoch: 2026 is 2569 in `buddhist` and 115 in `roc`.
230
+ const GREGORIAN_YEAR_OFFSETS: Partial<Record<CalendarSystem, number>> = {
231
+ buddhist: 543,
232
+ roc: -1911,
233
+ };
234
+
235
+ /** The year and month numbers of a calendar month. */
236
+ export function calendarYearMonthOf(
237
+ calendar: CalendarSystem,
238
+ month: CalendarMonth,
239
+ ): CalendarYearMonth {
240
+ if (isGregorianAligned(calendar)) {
241
+ return {
242
+ year: month.start.year + (GREGORIAN_YEAR_OFFSETS[calendar] ?? 0),
243
+ month: month.start.month,
244
+ };
245
+ }
246
+ const {year, month: monthOfYear} = inCalendar(calendar, month.start);
247
+ return {year, month: monthOfYear};
248
+ }
249
+
250
+ /**
251
+ * The calendar month with these year and month numbers. A month past the end
252
+ * of its year becomes the last month of that year, as Temporal constrains it.
253
+ */
254
+ export function calendarMonthFromYearMonth(
255
+ calendar: CalendarSystem,
256
+ {year, month}: CalendarYearMonth,
257
+ ): CalendarMonth {
258
+ if (isGregorianAligned(calendar)) {
259
+ return gregorianMonth(
260
+ year - (GREGORIAN_YEAR_OFFSETS[calendar] ?? 0),
261
+ month,
262
+ );
263
+ }
264
+ // `with` replaces the year and month of any date in the calendar.
265
+ return temporalMonth(
266
+ inCalendar(calendar, {year: 2000, month: 1, day: 1}).with({
267
+ year,
268
+ month,
269
+ day: 1,
270
+ }),
271
+ );
272
+ }
273
+
219
274
  /** The calendar month `n` months after `month`, or before it when negative. */
220
275
  export function calendarAddMonths(
221
276
  calendar: CalendarSystem,