@lazerlen/legend-calendar 1.4.1 → 1.6.0
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/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +91 -91
- package/dist/index.d.mts +58 -13
- package/dist/index.d.ts +58 -13
- package/dist/index.js +592 -317
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +588 -314
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/Calendar.tsx +14 -16
- package/src/components/CalendarItemDay.tsx +6 -8
- package/src/components/CalendarItemEmpty.tsx +2 -3
- package/src/components/CalendarItemWeekName.tsx +2 -3
- package/src/components/CalendarList.tsx +279 -92
- package/src/components/CalendarListConfigContext.tsx +29 -0
- package/src/components/CalendarRowMonth.tsx +2 -3
- package/src/components/CalendarRowWeek.tsx +2 -3
- package/src/components/index.ts +4 -0
- package/src/developer/decorators.tsx +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazerlen/legend-calendar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A better calendar for React Native.",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@babel/preset-typescript": "^7.28.5",
|
|
29
29
|
"@lazerlen/eslint-config": "*",
|
|
30
30
|
"@lazerlen/tsconfig": "*",
|
|
31
|
-
"@legendapp/list": "^3.0.0
|
|
31
|
+
"@legendapp/list": "^3.0.0",
|
|
32
32
|
"@testing-library/react-hooks": "^8.0.1",
|
|
33
33
|
"@types/bun": "^1.3.2",
|
|
34
34
|
"@types/react": "~19.1.10",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript": "~5.9.2"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@legendapp/list": ">=3.0.0
|
|
45
|
+
"@legendapp/list": ">=3.0.0",
|
|
46
46
|
"react": "*",
|
|
47
47
|
"react-native": "*"
|
|
48
48
|
}
|
|
49
|
-
}
|
|
49
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from "react";
|
|
2
2
|
import type { ColorSchemeName, PressableProps } from "react-native";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
@@ -13,6 +13,7 @@ import type { CalendarItemEmptyProps } from "@/components/CalendarItemEmpty";
|
|
|
13
13
|
import { CalendarItemEmpty } from "@/components/CalendarItemEmpty";
|
|
14
14
|
import type { CalendarItemWeekNameProps } from "@/components/CalendarItemWeekName";
|
|
15
15
|
import { CalendarItemWeekName } from "@/components/CalendarItemWeekName";
|
|
16
|
+
import { useCalendarListConfig } from "@/components/CalendarListConfigContext";
|
|
16
17
|
import type { CalendarRowMonthProps } from "@/components/CalendarRowMonth";
|
|
17
18
|
import { CalendarRowMonth } from "@/components/CalendarRowMonth";
|
|
18
19
|
import type { CalendarRowWeekProps } from "@/components/CalendarRowWeek";
|
|
@@ -105,7 +106,7 @@ export interface CalendarProps extends UseCalendarParams {
|
|
|
105
106
|
CalendarPressableComponent?: PressableLike;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
const BaseCalendar =
|
|
109
|
+
const BaseCalendar = function BaseCalendar(props: CalendarProps) {
|
|
109
110
|
const {
|
|
110
111
|
calendarInstanceId,
|
|
111
112
|
calendarRowVerticalSpacing = 8,
|
|
@@ -185,16 +186,23 @@ const BaseCalendar = memo(function BaseCalendar(props: CalendarProps) {
|
|
|
185
186
|
))}
|
|
186
187
|
</VStack>
|
|
187
188
|
);
|
|
188
|
-
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const Calendar = function Calendar(props: CalendarProps) {
|
|
192
|
+
const listConfig = useCalendarListConfig();
|
|
193
|
+
|
|
194
|
+
// When inside CalendarList, merge context values as defaults.
|
|
195
|
+
// Direct props always take precedence over context.
|
|
196
|
+
const resolvedProps = listConfig ? { ...listConfig, ...props } : props;
|
|
189
197
|
|
|
190
|
-
export const Calendar = memo(function Calendar(props: CalendarProps) {
|
|
191
198
|
const {
|
|
192
199
|
calendarInstanceId,
|
|
193
200
|
calendarActiveDateRanges,
|
|
194
201
|
calendarMonthId,
|
|
195
202
|
calendarColorScheme,
|
|
196
203
|
...otherProps
|
|
197
|
-
} =
|
|
204
|
+
} = resolvedProps;
|
|
205
|
+
|
|
198
206
|
useEffect(() => {
|
|
199
207
|
// When used inside CalendarList, calendarActiveDateRanges is undefined
|
|
200
208
|
// because CalendarList writes directly to the store. Skip to avoid
|
|
@@ -204,16 +212,6 @@ export const Calendar = memo(function Calendar(props: CalendarProps) {
|
|
|
204
212
|
calendarInstanceId ?? "legend-calendar-default-instance",
|
|
205
213
|
calendarActiveDateRanges
|
|
206
214
|
);
|
|
207
|
-
/**
|
|
208
|
-
* While `calendarMonthId` is not used by the effect, we still need it in
|
|
209
|
-
* the dependency array since [LegendList uses recycling
|
|
210
|
-
* internally](https://legendapp.com/open-source/list/).
|
|
211
|
-
*
|
|
212
|
-
* This means `Calendar` can re-render with different props instead of
|
|
213
|
-
* getting re-mounted. Without it, we would see staled/invalid data, as
|
|
214
|
-
* reported by
|
|
215
|
-
* [#11](https://github.com/MarceloPrado/flash-calendar/issues/11).
|
|
216
|
-
*/
|
|
217
215
|
}, [calendarActiveDateRanges, calendarInstanceId, calendarMonthId]);
|
|
218
216
|
|
|
219
217
|
return (
|
|
@@ -225,4 +223,4 @@ export const Calendar = memo(function Calendar(props: CalendarProps) {
|
|
|
225
223
|
/>
|
|
226
224
|
</CalendarThemeProvider>
|
|
227
225
|
);
|
|
228
|
-
}
|
|
226
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { memo } from "react";
|
|
3
2
|
import type { TextStyle, ViewStyle } from "react-native";
|
|
4
3
|
import { Pressable, StyleSheet, View } from "react-native";
|
|
5
4
|
|
|
@@ -161,7 +160,7 @@ export interface CalendarItemDayProps {
|
|
|
161
160
|
* `CalendarItemDayWithContainer`, since it also includes the spacing between
|
|
162
161
|
* each day.
|
|
163
162
|
*/
|
|
164
|
-
export const CalendarItemDay =
|
|
163
|
+
export const CalendarItemDay = function CalendarItemDay({
|
|
165
164
|
onPress,
|
|
166
165
|
children,
|
|
167
166
|
theme,
|
|
@@ -295,7 +294,7 @@ export const CalendarItemDay = memo(function CalendarItemDay({
|
|
|
295
294
|
}}
|
|
296
295
|
</CalendarPressableComponent>
|
|
297
296
|
);
|
|
298
|
-
}
|
|
297
|
+
};
|
|
299
298
|
|
|
300
299
|
interface CalendarItemDayContainerTheme {
|
|
301
300
|
/** An empty view that acts as a spacer between each day. The spacing is
|
|
@@ -331,7 +330,7 @@ export interface CalendarItemDayContainerProps {
|
|
|
331
330
|
metadata?: CalendarDayMetadata;
|
|
332
331
|
}
|
|
333
332
|
|
|
334
|
-
export const CalendarItemDayContainer =
|
|
333
|
+
export const CalendarItemDayContainer = function CalendarItemDayContainer({
|
|
335
334
|
children,
|
|
336
335
|
isStartOfWeek,
|
|
337
336
|
shouldShowActiveDayFiller,
|
|
@@ -389,7 +388,7 @@ export const CalendarItemDayContainer = memo(function CalendarItemDayContainer({
|
|
|
389
388
|
{children}
|
|
390
389
|
</View>
|
|
391
390
|
);
|
|
392
|
-
}
|
|
391
|
+
};
|
|
393
392
|
|
|
394
393
|
export interface CalendarItemDayWithContainerProps
|
|
395
394
|
extends Omit<CalendarItemDayProps, "height">,
|
|
@@ -409,7 +408,7 @@ export interface CalendarItemDayWithContainerProps
|
|
|
409
408
|
calendarInstanceId?: string;
|
|
410
409
|
}
|
|
411
410
|
|
|
412
|
-
export const CalendarItemDayWithContainer =
|
|
411
|
+
export const CalendarItemDayWithContainer =
|
|
413
412
|
function CalendarItemDayWithContainer({
|
|
414
413
|
children,
|
|
415
414
|
metadata: baseMetadata,
|
|
@@ -447,5 +446,4 @@ export const CalendarItemDayWithContainer = memo(
|
|
|
447
446
|
</CalendarItemDay>
|
|
448
447
|
</CalendarItemDayContainer>
|
|
449
448
|
);
|
|
450
|
-
}
|
|
451
|
-
);
|
|
449
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { memo } from "react";
|
|
2
1
|
import type { ViewStyle } from "react-native";
|
|
3
2
|
import { StyleSheet, View } from "react-native";
|
|
4
3
|
|
|
@@ -18,11 +17,11 @@ export interface CalendarItemEmptyProps {
|
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export const CalendarItemEmpty =
|
|
20
|
+
export const CalendarItemEmpty = function CalendarItemEmpty(
|
|
22
21
|
props: CalendarItemEmptyProps
|
|
23
22
|
) {
|
|
24
23
|
const { height, theme } = props;
|
|
25
24
|
const containerStyles = [{ ...styles.container, height }, theme?.container];
|
|
26
25
|
|
|
27
26
|
return <View style={containerStyles} />;
|
|
28
|
-
}
|
|
27
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { memo } from "react";
|
|
3
2
|
import type { TextStyle, ViewStyle } from "react-native";
|
|
4
3
|
import { StyleSheet, View } from "react-native";
|
|
5
4
|
|
|
@@ -35,7 +34,7 @@ export interface CalendarItemWeekNameProps {
|
|
|
35
34
|
textProps?: Omit<CalendarTextProps, "children">;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
export const CalendarItemWeekName =
|
|
37
|
+
export const CalendarItemWeekName = function CalendarItemWeekName({
|
|
39
38
|
children,
|
|
40
39
|
height,
|
|
41
40
|
theme,
|
|
@@ -57,4 +56,4 @@ export const CalendarItemWeekName = memo(function CalendarItemWeekName({
|
|
|
57
56
|
</Text>
|
|
58
57
|
</View>
|
|
59
58
|
);
|
|
60
|
-
}
|
|
59
|
+
};
|
|
@@ -3,11 +3,18 @@ import {
|
|
|
3
3
|
type LegendListProps,
|
|
4
4
|
type LegendListRef,
|
|
5
5
|
} from "@legendapp/list/react-native";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useEffect,
|
|
8
|
+
useImperativeHandle,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
7
13
|
import { StyleSheet, View } from "react-native";
|
|
8
14
|
|
|
9
15
|
import type { CalendarProps } from "@/components/Calendar";
|
|
10
16
|
import { Calendar } from "@/components/Calendar";
|
|
17
|
+
import { CalendarListConfigProvider } from "@/components/CalendarListConfigContext";
|
|
11
18
|
import {
|
|
12
19
|
fromDateId,
|
|
13
20
|
getWeekOfMonth,
|
|
@@ -27,11 +34,78 @@ const LegendList = LegendListBase as <T>(
|
|
|
27
34
|
* `Calendar` props to simplify building custom `Calendar` components.
|
|
28
35
|
*/
|
|
29
36
|
export type CalendarMonthEnhanced = CalendarMonth & {
|
|
37
|
+
/**
|
|
38
|
+
* The calendar configuration props for this item. Available when using a
|
|
39
|
+
* custom `renderItem` for backwards compatibility.
|
|
40
|
+
*
|
|
41
|
+
* @deprecated Prefer reading calendar config from context via
|
|
42
|
+
* `useCalendarListConfig()` instead of spreading `item.calendarProps`.
|
|
43
|
+
* This avoids creating a new data array each render and lets LegendList
|
|
44
|
+
* skip unnecessary item re-renders.
|
|
45
|
+
*
|
|
46
|
+
* **Before (slower):**
|
|
47
|
+
* ```tsx
|
|
48
|
+
* renderItem={({ item }) => (
|
|
49
|
+
* <MyCalendar calendarMonthId={item.id} {...item.calendarProps} />
|
|
50
|
+
* )}
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* **After (faster):**
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // Inside your custom calendar component:
|
|
56
|
+
* const listConfig = useCalendarListConfig();
|
|
57
|
+
* // Merge: { ...listConfig, ...props }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
30
60
|
calendarProps: Omit<CalendarProps, "calendarMonthId">;
|
|
31
61
|
};
|
|
32
62
|
|
|
33
63
|
const keyExtractor = (month: CalendarMonth) => month.id;
|
|
34
64
|
|
|
65
|
+
function buildCalendarConfig(
|
|
66
|
+
calendarColorScheme: CalendarMonthEnhanced["calendarProps"]["calendarColorScheme"],
|
|
67
|
+
calendarDayHeight: number,
|
|
68
|
+
calendarDisabledDateIds: CalendarMonthEnhanced["calendarProps"]["calendarDisabledDateIds"],
|
|
69
|
+
calendarFirstDayOfWeek: CalendarMonthEnhanced["calendarProps"]["calendarFirstDayOfWeek"],
|
|
70
|
+
calendarFormatLocale: CalendarMonthEnhanced["calendarProps"]["calendarFormatLocale"],
|
|
71
|
+
calendarInstanceId: CalendarMonthEnhanced["calendarProps"]["calendarInstanceId"],
|
|
72
|
+
calendarMaxDateId: CalendarMonthEnhanced["calendarProps"]["calendarMaxDateId"],
|
|
73
|
+
calendarMinDateId: CalendarMonthEnhanced["calendarProps"]["calendarMinDateId"],
|
|
74
|
+
calendarMonthHeaderHeight: number,
|
|
75
|
+
calendarRowHorizontalSpacing: CalendarMonthEnhanced["calendarProps"]["calendarRowHorizontalSpacing"],
|
|
76
|
+
calendarRowVerticalSpacing: number,
|
|
77
|
+
calendarWeekHeaderHeightProp: CalendarMonthEnhanced["calendarProps"]["calendarWeekHeaderHeight"],
|
|
78
|
+
getCalendarDayFormat: CalendarMonthEnhanced["calendarProps"]["getCalendarDayFormat"],
|
|
79
|
+
getCalendarMonthFormat: CalendarMonthEnhanced["calendarProps"]["getCalendarMonthFormat"],
|
|
80
|
+
getCalendarWeekDayFormat: CalendarMonthEnhanced["calendarProps"]["getCalendarWeekDayFormat"],
|
|
81
|
+
onCalendarDayPress: CalendarMonthEnhanced["calendarProps"]["onCalendarDayPress"],
|
|
82
|
+
theme: CalendarMonthEnhanced["calendarProps"]["theme"],
|
|
83
|
+
CalendarPressableComponent: CalendarMonthEnhanced["calendarProps"]["CalendarPressableComponent"]
|
|
84
|
+
): CalendarMonthEnhanced["calendarProps"] {
|
|
85
|
+
const calendarWeekHeaderHeight =
|
|
86
|
+
calendarWeekHeaderHeightProp ?? calendarDayHeight;
|
|
87
|
+
return {
|
|
88
|
+
calendarColorScheme,
|
|
89
|
+
calendarDayHeight,
|
|
90
|
+
calendarDisabledDateIds,
|
|
91
|
+
calendarFirstDayOfWeek,
|
|
92
|
+
calendarFormatLocale,
|
|
93
|
+
calendarInstanceId,
|
|
94
|
+
calendarMaxDateId,
|
|
95
|
+
calendarMinDateId,
|
|
96
|
+
calendarMonthHeaderHeight,
|
|
97
|
+
calendarRowHorizontalSpacing,
|
|
98
|
+
calendarRowVerticalSpacing,
|
|
99
|
+
calendarWeekHeaderHeight,
|
|
100
|
+
getCalendarDayFormat,
|
|
101
|
+
getCalendarMonthFormat,
|
|
102
|
+
getCalendarWeekDayFormat,
|
|
103
|
+
onCalendarDayPress,
|
|
104
|
+
theme,
|
|
105
|
+
CalendarPressableComponent,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
35
109
|
export interface CalendarListProps
|
|
36
110
|
extends Omit<CalendarProps, "calendarMonthId">,
|
|
37
111
|
Omit<
|
|
@@ -100,6 +174,12 @@ export interface CalendarListProps
|
|
|
100
174
|
* - calendarAdditionalHeight
|
|
101
175
|
* - calendarRowVerticalSpacing
|
|
102
176
|
* - calendarSpacing
|
|
177
|
+
*
|
|
178
|
+
* **Performance tip**: Using `item.calendarProps` is provided for
|
|
179
|
+
* backwards compatibility but creates a new data array each render.
|
|
180
|
+
* For better performance, have your custom component call
|
|
181
|
+
* `useCalendarListConfig()` to read the shared config from context
|
|
182
|
+
* and only use `item.id` as `calendarMonthId`.
|
|
103
183
|
*/
|
|
104
184
|
renderItem?: LegendListProps<CalendarMonthEnhanced>["renderItem"];
|
|
105
185
|
}
|
|
@@ -125,39 +205,37 @@ export interface CalendarListRef {
|
|
|
125
205
|
scrollToOffset: (offset: number, animated: boolean) => void;
|
|
126
206
|
}
|
|
127
207
|
|
|
128
|
-
|
|
129
|
-
ref
|
|
130
|
-
|
|
131
|
-
|
|
208
|
+
type CalendarListInnerProps = CalendarListProps & {
|
|
209
|
+
ref?: React.Ref<CalendarListRef>;
|
|
210
|
+
} & {
|
|
211
|
+
flatListProps: Omit<
|
|
212
|
+
LegendListProps<CalendarMonthEnhanced>,
|
|
213
|
+
"renderItem" | "data" | "children"
|
|
214
|
+
>;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export function CalendarList(
|
|
218
|
+
props: CalendarListProps & { ref?: React.Ref<CalendarListRef> }
|
|
219
|
+
) {
|
|
132
220
|
const {
|
|
133
|
-
|
|
221
|
+
ref,
|
|
134
222
|
calendarInitialMonthId,
|
|
135
|
-
calendarInitialScrollToActiveRange
|
|
136
|
-
calendarPastScrollRangeInMonths
|
|
137
|
-
calendarFutureScrollRangeInMonths
|
|
138
|
-
calendarFirstDayOfWeek
|
|
223
|
+
calendarInitialScrollToActiveRange,
|
|
224
|
+
calendarPastScrollRangeInMonths,
|
|
225
|
+
calendarFutureScrollRangeInMonths,
|
|
226
|
+
calendarFirstDayOfWeek,
|
|
139
227
|
calendarFormatLocale,
|
|
140
|
-
|
|
141
|
-
// Spacings
|
|
142
|
-
calendarSpacing = 20,
|
|
228
|
+
calendarSpacing,
|
|
143
229
|
calendarRowHorizontalSpacing,
|
|
144
|
-
calendarRowVerticalSpacing
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
calendarWeekHeaderHeight = calendarDayHeight,
|
|
150
|
-
calendarAdditionalHeight = 0,
|
|
151
|
-
|
|
152
|
-
// Other props
|
|
230
|
+
calendarRowVerticalSpacing,
|
|
231
|
+
calendarMonthHeaderHeight,
|
|
232
|
+
calendarDayHeight,
|
|
233
|
+
calendarWeekHeaderHeight,
|
|
234
|
+
calendarAdditionalHeight,
|
|
153
235
|
calendarColorScheme,
|
|
154
236
|
theme,
|
|
155
237
|
onEndReached,
|
|
156
238
|
onStartReached,
|
|
157
|
-
...otherProps
|
|
158
|
-
} = props;
|
|
159
|
-
|
|
160
|
-
const {
|
|
161
239
|
calendarActiveDateRanges,
|
|
162
240
|
calendarDisabledDateIds,
|
|
163
241
|
calendarInstanceId,
|
|
@@ -168,9 +246,82 @@ export function CalendarList({
|
|
|
168
246
|
getCalendarWeekDayFormat,
|
|
169
247
|
onCalendarDayPress,
|
|
170
248
|
CalendarPressableComponent,
|
|
249
|
+
renderItem,
|
|
171
250
|
...flatListProps
|
|
172
|
-
} =
|
|
251
|
+
} = props;
|
|
252
|
+
return (
|
|
253
|
+
<CalendarListInner
|
|
254
|
+
CalendarPressableComponent={CalendarPressableComponent}
|
|
255
|
+
calendarActiveDateRanges={calendarActiveDateRanges}
|
|
256
|
+
calendarAdditionalHeight={calendarAdditionalHeight}
|
|
257
|
+
calendarColorScheme={calendarColorScheme}
|
|
258
|
+
calendarDayHeight={calendarDayHeight}
|
|
259
|
+
calendarDisabledDateIds={calendarDisabledDateIds}
|
|
260
|
+
calendarFirstDayOfWeek={calendarFirstDayOfWeek}
|
|
261
|
+
calendarFormatLocale={calendarFormatLocale}
|
|
262
|
+
calendarFutureScrollRangeInMonths={calendarFutureScrollRangeInMonths}
|
|
263
|
+
calendarInitialMonthId={calendarInitialMonthId}
|
|
264
|
+
calendarInitialScrollToActiveRange={calendarInitialScrollToActiveRange}
|
|
265
|
+
calendarInstanceId={calendarInstanceId}
|
|
266
|
+
calendarMaxDateId={calendarMaxDateId}
|
|
267
|
+
calendarMinDateId={calendarMinDateId}
|
|
268
|
+
calendarMonthHeaderHeight={calendarMonthHeaderHeight}
|
|
269
|
+
calendarPastScrollRangeInMonths={calendarPastScrollRangeInMonths}
|
|
270
|
+
calendarRowHorizontalSpacing={calendarRowHorizontalSpacing}
|
|
271
|
+
calendarRowVerticalSpacing={calendarRowVerticalSpacing}
|
|
272
|
+
calendarSpacing={calendarSpacing}
|
|
273
|
+
calendarWeekHeaderHeight={calendarWeekHeaderHeight}
|
|
274
|
+
flatListProps={flatListProps}
|
|
275
|
+
getCalendarDayFormat={getCalendarDayFormat}
|
|
276
|
+
getCalendarMonthFormat={getCalendarMonthFormat}
|
|
277
|
+
getCalendarWeekDayFormat={getCalendarWeekDayFormat}
|
|
278
|
+
onCalendarDayPress={onCalendarDayPress}
|
|
279
|
+
onEndReached={onEndReached}
|
|
280
|
+
onStartReached={onStartReached}
|
|
281
|
+
ref={ref}
|
|
282
|
+
renderItem={renderItem}
|
|
283
|
+
theme={theme}
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
173
287
|
|
|
288
|
+
function CalendarListInner({
|
|
289
|
+
ref,
|
|
290
|
+
// List-related props
|
|
291
|
+
calendarInitialMonthId,
|
|
292
|
+
calendarInitialScrollToActiveRange = true,
|
|
293
|
+
calendarPastScrollRangeInMonths = 12,
|
|
294
|
+
calendarFutureScrollRangeInMonths = 12,
|
|
295
|
+
calendarFirstDayOfWeek = "sunday",
|
|
296
|
+
calendarFormatLocale,
|
|
297
|
+
// Spacings
|
|
298
|
+
calendarSpacing = 20,
|
|
299
|
+
calendarRowHorizontalSpacing,
|
|
300
|
+
calendarRowVerticalSpacing = 8,
|
|
301
|
+
// Heights
|
|
302
|
+
calendarMonthHeaderHeight = 20,
|
|
303
|
+
calendarDayHeight = 32,
|
|
304
|
+
calendarWeekHeaderHeight: calendarWeekHeaderHeightProp,
|
|
305
|
+
calendarAdditionalHeight = 0,
|
|
306
|
+
// Other props
|
|
307
|
+
calendarColorScheme,
|
|
308
|
+
theme,
|
|
309
|
+
onEndReached,
|
|
310
|
+
onStartReached,
|
|
311
|
+
// Calendar config props
|
|
312
|
+
calendarActiveDateRanges,
|
|
313
|
+
calendarDisabledDateIds,
|
|
314
|
+
calendarInstanceId,
|
|
315
|
+
calendarMaxDateId,
|
|
316
|
+
calendarMinDateId,
|
|
317
|
+
getCalendarDayFormat,
|
|
318
|
+
getCalendarMonthFormat,
|
|
319
|
+
getCalendarWeekDayFormat,
|
|
320
|
+
onCalendarDayPress,
|
|
321
|
+
CalendarPressableComponent,
|
|
322
|
+
renderItem: customRenderItem,
|
|
323
|
+
flatListProps,
|
|
324
|
+
}: CalendarListInnerProps) {
|
|
174
325
|
// Write directly to store to bypass the entire render cascade.
|
|
175
326
|
// This means calendarProps stays stable and monthListWithCalendarProps
|
|
176
327
|
// doesn't recompute on every date tap.
|
|
@@ -181,27 +332,57 @@ export function CalendarList({
|
|
|
181
332
|
);
|
|
182
333
|
}, [calendarActiveDateRanges, calendarInstanceId]);
|
|
183
334
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
335
|
+
// calendarActiveDateRanges intentionally omitted - written to store above.
|
|
336
|
+
// useMemo is required here: the React Compiler cannot cache the result of
|
|
337
|
+
// buildCalendarConfig because all its arguments flow through `tN === undefined
|
|
338
|
+
// ? default : tN` conditional expressions (the compiler's own pattern for
|
|
339
|
+
// defaulted props), which block cache slot generation.
|
|
340
|
+
const calendarProps = useMemo(
|
|
341
|
+
() =>
|
|
342
|
+
buildCalendarConfig(
|
|
343
|
+
calendarColorScheme,
|
|
344
|
+
calendarDayHeight,
|
|
345
|
+
calendarDisabledDateIds,
|
|
346
|
+
calendarFirstDayOfWeek,
|
|
347
|
+
calendarFormatLocale,
|
|
348
|
+
calendarInstanceId,
|
|
349
|
+
calendarMaxDateId,
|
|
350
|
+
calendarMinDateId,
|
|
351
|
+
calendarMonthHeaderHeight,
|
|
352
|
+
calendarRowHorizontalSpacing,
|
|
353
|
+
calendarRowVerticalSpacing,
|
|
354
|
+
calendarWeekHeaderHeightProp,
|
|
355
|
+
getCalendarDayFormat,
|
|
356
|
+
getCalendarMonthFormat,
|
|
357
|
+
getCalendarWeekDayFormat,
|
|
358
|
+
onCalendarDayPress,
|
|
359
|
+
theme,
|
|
360
|
+
CalendarPressableComponent
|
|
361
|
+
),
|
|
362
|
+
[
|
|
363
|
+
calendarColorScheme,
|
|
364
|
+
calendarDayHeight,
|
|
365
|
+
calendarDisabledDateIds,
|
|
366
|
+
calendarFirstDayOfWeek,
|
|
367
|
+
calendarFormatLocale,
|
|
368
|
+
calendarInstanceId,
|
|
369
|
+
calendarMaxDateId,
|
|
370
|
+
calendarMinDateId,
|
|
371
|
+
calendarMonthHeaderHeight,
|
|
372
|
+
calendarRowHorizontalSpacing,
|
|
373
|
+
calendarRowVerticalSpacing,
|
|
374
|
+
calendarWeekHeaderHeightProp,
|
|
375
|
+
getCalendarDayFormat,
|
|
376
|
+
getCalendarMonthFormat,
|
|
377
|
+
getCalendarWeekDayFormat,
|
|
378
|
+
onCalendarDayPress,
|
|
379
|
+
theme,
|
|
380
|
+
CalendarPressableComponent,
|
|
381
|
+
]
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const calendarWeekHeaderHeight =
|
|
385
|
+
calendarWeekHeaderHeightProp ?? calendarDayHeight;
|
|
205
386
|
|
|
206
387
|
const {
|
|
207
388
|
initialMonthIndex,
|
|
@@ -218,29 +399,33 @@ export function CalendarList({
|
|
|
218
399
|
calendarMinDateId,
|
|
219
400
|
});
|
|
220
401
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (monthIndex !== -1) {
|
|
235
|
-
computedInitialScrollIndex = monthIndex;
|
|
402
|
+
// Frozen after mount — initialScrollIndex must not change after first render
|
|
403
|
+
// or LegendList re-renders its entire inner tree on every update.
|
|
404
|
+
// useState initializer runs exactly once on mount.
|
|
405
|
+
const [computedInitialScrollIndex] = useState(() => {
|
|
406
|
+
if (calendarInitialScrollToActiveRange && calendarActiveDateRanges) {
|
|
407
|
+
const firstRange = calendarActiveDateRanges[0];
|
|
408
|
+
if (firstRange?.startId) {
|
|
409
|
+
const startDate = fromDateId(firstRange.startId);
|
|
410
|
+
const monthId = toDateId(startOfMonth(startDate));
|
|
411
|
+
const monthIndex = monthList.findIndex((month) => month.id === monthId);
|
|
412
|
+
if (monthIndex !== -1) {
|
|
413
|
+
return monthIndex;
|
|
414
|
+
}
|
|
236
415
|
}
|
|
237
416
|
}
|
|
238
|
-
|
|
417
|
+
return initialMonthIndex;
|
|
418
|
+
});
|
|
239
419
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
420
|
+
// Only build the enhanced list when user provides a custom renderItem
|
|
421
|
+
// (backwards-compat path). Otherwise use plain monthList so the data
|
|
422
|
+
// identity stays stable and LegendList can skip item re-renders.
|
|
423
|
+
const listData = customRenderItem
|
|
424
|
+
? monthList.map((month) => ({
|
|
425
|
+
...month,
|
|
426
|
+
calendarProps,
|
|
427
|
+
}))
|
|
428
|
+
: (monthList as unknown as CalendarMonthEnhanced[]);
|
|
244
429
|
|
|
245
430
|
const handleOnEndReached = (info: { distanceFromEnd: number }) => {
|
|
246
431
|
appendMonths(calendarFutureScrollRangeInMonths);
|
|
@@ -331,7 +516,7 @@ export function CalendarList({
|
|
|
331
516
|
|
|
332
517
|
const calendarContainerStyle = { paddingBottom: calendarSpacing };
|
|
333
518
|
|
|
334
|
-
const getFixedItemSize = (item: CalendarMonthEnhanced) => {
|
|
519
|
+
const getFixedItemSize = (item: CalendarMonth | CalendarMonthEnhanced) => {
|
|
335
520
|
return getHeightForMonth({
|
|
336
521
|
calendarMonth: item,
|
|
337
522
|
calendarSpacing,
|
|
@@ -343,35 +528,37 @@ export function CalendarList({
|
|
|
343
528
|
});
|
|
344
529
|
};
|
|
345
530
|
|
|
531
|
+
// onCalendarDayPress is provided via CalendarListConfigContext at runtime,
|
|
532
|
+
// so we only need to pass calendarMonthId here.
|
|
346
533
|
const handleRenderItem = ({ item }: { item: CalendarMonthEnhanced }) => (
|
|
347
534
|
<View style={calendarContainerStyle}>
|
|
348
|
-
<Calendar
|
|
535
|
+
<Calendar
|
|
536
|
+
calendarMonthId={item.id}
|
|
537
|
+
onCalendarDayPress={onCalendarDayPress}
|
|
538
|
+
/>
|
|
349
539
|
</View>
|
|
350
540
|
);
|
|
351
541
|
|
|
352
|
-
// Uncertain why but passing this as a no op resolved blanking issues after adding
|
|
353
|
-
// getFixedItemSize + recycleItems
|
|
354
|
-
const handleViewableItemsChanged = () => {};
|
|
355
|
-
|
|
356
542
|
return (
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
543
|
+
<CalendarListConfigProvider value={calendarProps}>
|
|
544
|
+
<LegendList
|
|
545
|
+
data={listData}
|
|
546
|
+
drawDistance={560}
|
|
547
|
+
estimatedItemSize={273}
|
|
548
|
+
getFixedItemSize={getFixedItemSize}
|
|
549
|
+
initialScrollIndex={computedInitialScrollIndex}
|
|
550
|
+
keyExtractor={keyExtractor}
|
|
551
|
+
maintainVisibleContentPosition
|
|
552
|
+
onEndReached={handleOnEndReached}
|
|
553
|
+
onStartReached={handleOnStartReached}
|
|
554
|
+
recycleItems
|
|
555
|
+
ref={legendListRef}
|
|
556
|
+
renderItem={customRenderItem ?? handleRenderItem}
|
|
557
|
+
showsVerticalScrollIndicator={false}
|
|
558
|
+
style={styles.container}
|
|
559
|
+
{...flatListProps}
|
|
560
|
+
/>
|
|
561
|
+
</CalendarListConfigProvider>
|
|
375
562
|
);
|
|
376
563
|
}
|
|
377
564
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import type { CalendarProps } from "@/components/Calendar";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The calendar configuration props shared across all items in a
|
|
7
|
+
* `CalendarList`. When `Calendar` is rendered inside `CalendarList`, it reads
|
|
8
|
+
* these values from context instead of receiving them through the list item's
|
|
9
|
+
* data, which keeps the `data` array identity stable and allows LegendList to
|
|
10
|
+
* skip unnecessary re-renders.
|
|
11
|
+
*
|
|
12
|
+
* When `Calendar` is used standalone (outside a list), the context is `null`
|
|
13
|
+
* and all props are passed directly.
|
|
14
|
+
*/
|
|
15
|
+
export type CalendarListConfig = Omit<CalendarProps, "calendarMonthId">;
|
|
16
|
+
|
|
17
|
+
const CalendarListConfigContext = createContext<CalendarListConfig | null>(
|
|
18
|
+
null
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const CalendarListConfigProvider = CalendarListConfigContext.Provider;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns the shared calendar configuration from the nearest
|
|
25
|
+
* `CalendarListConfigProvider`, or `null` when used outside a `CalendarList`.
|
|
26
|
+
*/
|
|
27
|
+
export const useCalendarListConfig = (): CalendarListConfig | null => {
|
|
28
|
+
return useContext(CalendarListConfigContext);
|
|
29
|
+
};
|