@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.
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +6 -4
- package/dist/Calendar/hooks/useCalendarDays.d.ts +19 -20
- package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarDays.js +24 -40
- package/dist/Calendar/hooks/useCalendarNavigation.d.ts +28 -3
- package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarNavigation.js +33 -22
- package/dist/i18n/useCalendarSystem.d.ts +6 -0
- package/dist/i18n/useCalendarSystem.d.ts.map +1 -1
- package/dist/i18n/useCalendarSystem.js +15 -6
- package/dist/utils/calendarSystem.d.ts +20 -5
- package/dist/utils/calendarSystem.d.ts.map +1 -1
- package/dist/utils/calendarSystem.js +58 -5
- package/package.json +4 -4
- package/src/Calendar/Calendar.doc.mjs +50 -0
- package/src/Calendar/Calendar.tsx +9 -3
- package/src/Calendar/hooks/useCalendarDays.ts +39 -57
- package/src/Calendar/hooks/useCalendarNavigation.ts +68 -26
- package/src/i18n/useCalendarSystem.ts +24 -9
- package/src/utils/calendarSystem.ts +61 -6
|
@@ -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,
|
|
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 {
|
|
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
|
-
} =
|
|
531
|
-
|
|
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
|
|
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
|
-
/**
|
|
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,
|
|
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
|
|
10
|
-
* (
|
|
11
|
-
* @position Calendar-specific
|
|
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,
|
|
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
|
|
58
|
-
|
|
62
|
+
const calendar = resolveShownCalendar(calendarOption, 'useCalendarDays');
|
|
63
|
+
const days = useMemo(() => {
|
|
64
|
+
const calendarMonth = calendarMonthFromYearMonth(calendar, {
|
|
59
65
|
year,
|
|
60
|
-
month
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
30
|
+
/** The Gregorian first day of the focus month */
|
|
21
31
|
baseMonth: PlainDate;
|
|
22
|
-
/**
|
|
32
|
+
/** The Gregorian first day of each visible month */
|
|
23
33
|
visibleMonths: PlainDate[];
|
|
24
|
-
/**
|
|
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":"
|
|
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
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
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,
|
|
16
|
-
import {
|
|
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
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
//
|
|
96
|
-
|
|
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 =
|
|
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
|
-
}, [
|
|
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":"
|
|
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
|
|
9
|
-
*
|
|
10
|
-
* @position Internal i18n hook; read by InternationalizationProvider
|
|
11
|
-
* components. Not re-exported from the i18n
|
|
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
|
-
|
|
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,
|
|
5
|
-
* steps, dates from calendar fields,
|
|
6
|
-
* calendar labels and digits
|
|
7
|
-
* @position Shared date utility; read by the i18n calendar resolution
|
|
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,
|
|
7
|
-
* steps, dates from calendar fields,
|
|
8
|
-
* calendar labels and digits
|
|
9
|
-
* @position Shared date utility; read by the i18n calendar resolution
|
|
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.
|
|
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.
|
|
879
|
-
"@astryxdesign/cli": "0.7.9-canary.
|
|
880
|
-
"@astryxdesign/interaction-harness": "0.7.9-canary.
|
|
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 {
|
|
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
|
|
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
|
|
10
|
-
* (
|
|
11
|
-
* @position Calendar-specific
|
|
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
|
-
/**
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
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 {
|
|
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
|
|
63
|
+
/** The Gregorian first day of the focus month */
|
|
48
64
|
baseMonth: PlainDate;
|
|
49
|
-
/**
|
|
65
|
+
/** The Gregorian first day of each visible month */
|
|
50
66
|
visibleMonths: PlainDate[];
|
|
51
|
-
/**
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
134
|
-
}, [baseMonth, numberOfMonths]);
|
|
173
|
+
const visibleCalendarMonths = useMemo(
|
|
174
|
+
() => shownMonths.map(month => calendarYearMonthOf(calendar, month)),
|
|
175
|
+
[calendar, shownMonths],
|
|
176
|
+
);
|
|
135
177
|
|
|
136
|
-
//
|
|
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
|
-
|
|
141
|
-
.map(
|
|
181
|
+
shownMonths
|
|
182
|
+
.map(month => formatCalendarMonthLabel(calendar, month, locale))
|
|
142
183
|
.join(' – '),
|
|
143
|
-
[
|
|
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 =
|
|
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
|
-
[
|
|
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
|
|
9
|
-
*
|
|
10
|
-
* @position Internal i18n hook; read by InternationalizationProvider
|
|
11
|
-
* components. Not re-exported from the i18n
|
|
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
|
-
|
|
76
|
-
calendar
|
|
77
|
-
|
|
78
|
-
|
|
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,
|
|
7
|
-
* steps, dates from calendar fields,
|
|
8
|
-
* calendar labels and digits
|
|
9
|
-
* @position Shared date utility; read by the i18n calendar resolution
|
|
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,
|