@@ -13,7 +13,7 @@ import { View } from "react-native";
13
13
14
14
import type { CalendarProps } from "@/components/Calendar" ;
15
15
import { Calendar } from "@/components/Calendar" ;
16
- import { startOfMonth , toDateId } from "@/helpers/dates" ;
16
+ import { getWeekOfMonth , startOfMonth , toDateId } from "@/helpers/dates" ;
17
17
import type { CalendarMonth } from "@/hooks/useCalendarList" ;
18
18
import { getHeightForMonth , useCalendarList } from "@/hooks/useCalendarList" ;
19
19
@@ -89,8 +89,25 @@ export interface CalendarListProps
89
89
renderItem ?: FlashListProps < CalendarMonthEnhanced > [ "renderItem" ] ;
90
90
}
91
91
92
+ interface ImperativeScrollParams {
93
+ /**
94
+ * An additional offset to add to the final scroll position. Useful when
95
+ * you need to slightly change the final scroll position.
96
+ */
97
+ additionalOffset ?: number ;
98
+ }
92
99
export interface CalendarListRef {
93
- scrollToDate : ( date : Date , animated : boolean ) => void ;
100
+ scrollToMonth : (
101
+ date : Date ,
102
+ animated : boolean ,
103
+ params ?: ImperativeScrollParams
104
+ ) => void ;
105
+ scrollToDate : (
106
+ date : Date ,
107
+ animated : boolean ,
108
+ params ?: ImperativeScrollParams
109
+ ) => void ;
110
+ scrollToOffset : ( offset : number , animated : boolean ) => void ;
94
111
}
95
112
96
113
export const CalendarList = memo (
@@ -224,10 +241,12 @@ export const CalendarList = memo(
224
241
]
225
242
) ;
226
243
227
- const flashListRef = useRef < FlashList < CalendarMonthEnhanced > > ( null ) ;
228
-
229
- useImperativeHandle ( ref , ( ) => ( {
230
- scrollToDate ( date , animated ) {
244
+ /**
245
+ * Returns the offset for the given month (how much the user needs to
246
+ * scroll to reach the month).
247
+ */
248
+ const getScrollOffsetForMonth = useCallback (
249
+ ( date : Date ) => {
231
250
const monthId = toDateId ( startOfMonth ( date ) ) ;
232
251
233
252
let baseMonthList = monthList ;
@@ -238,31 +257,79 @@ export const CalendarList = memo(
238
257
index = baseMonthList . findIndex ( ( month ) => month . id === monthId ) ;
239
258
}
240
259
241
- const currentOffset = baseMonthList
242
- . slice ( 0 , index )
243
- . reduce ( ( acc , month ) => {
244
- const currentHeight = getHeightForMonth ( {
245
- calendarMonth : month ,
246
- calendarSpacing,
247
- calendarDayHeight,
248
- calendarMonthHeaderHeight,
249
- calendarRowVerticalSpacing,
250
- calendarWeekHeaderHeight,
251
- calendarAdditionalHeight,
252
- } ) ;
253
-
254
- return acc + currentHeight ;
255
- } , 0 ) ;
260
+ return baseMonthList . slice ( 0 , index ) . reduce ( ( acc , month ) => {
261
+ const currentHeight = getHeightForMonth ( {
262
+ calendarMonth : month ,
263
+ calendarSpacing,
264
+ calendarDayHeight,
265
+ calendarMonthHeaderHeight,
266
+ calendarRowVerticalSpacing,
267
+ calendarWeekHeaderHeight,
268
+ calendarAdditionalHeight,
269
+ } ) ;
256
270
271
+ return acc + currentHeight ;
272
+ } , 0 ) ;
273
+ } ,
274
+ [
275
+ addMissingMonths ,
276
+ calendarAdditionalHeight ,
277
+ calendarDayHeight ,
278
+ calendarMonthHeaderHeight ,
279
+ calendarRowVerticalSpacing ,
280
+ calendarSpacing ,
281
+ calendarWeekHeaderHeight ,
282
+ monthList ,
283
+ ]
284
+ ) ;
285
+
286
+ const flashListRef = useRef < FlashList < CalendarMonthEnhanced > > ( null ) ;
287
+
288
+ useImperativeHandle ( ref , ( ) => ( {
289
+ scrollToMonth (
290
+ date ,
291
+ animated ,
292
+ { additionalOffset = 0 } = { additionalOffset : 0 }
293
+ ) {
257
294
// Wait for the next render cycle to ensure the list has been
258
295
// updated with the new months.
259
296
setTimeout ( ( ) => {
260
297
flashListRef . current ?. scrollToOffset ( {
261
- offset : currentOffset ,
298
+ offset : getScrollOffsetForMonth ( date ) + additionalOffset ,
262
299
animated,
263
300
} ) ;
264
301
} , 0 ) ;
265
302
} ,
303
+ scrollToDate (
304
+ date ,
305
+ animated ,
306
+ { additionalOffset = 0 } = {
307
+ additionalOffset : 0 ,
308
+ }
309
+ ) {
310
+ const currentMonthOffset = getScrollOffsetForMonth ( date ) ;
311
+ const weekOfMonthIndex = getWeekOfMonth ( date , calendarFirstDayOfWeek ) ;
312
+ const rowHeight = calendarDayHeight + calendarRowVerticalSpacing ;
313
+
314
+ let weekOffset =
315
+ calendarWeekHeaderHeight + rowHeight * weekOfMonthIndex ;
316
+
317
+ /**
318
+ * We need to subtract one vertical spacing to avoid cutting off the
319
+ * desired date. A simple way of understanding why is imagining we
320
+ * want to scroll exactly to the given date, but leave a little bit of
321
+ * breathing room (`calendarRowVerticalSpacing`) above it.
322
+ */
323
+ weekOffset = weekOffset - calendarRowVerticalSpacing ;
324
+
325
+ flashListRef . current ?. scrollToOffset ( {
326
+ offset : currentMonthOffset + weekOffset + additionalOffset ,
327
+ animated,
328
+ } ) ;
329
+ } ,
330
+ scrollToOffset ( offset , animated ) {
331
+ flashListRef . current ?. scrollToOffset ( { offset, animated } ) ;
332
+ } ,
266
333
} ) ) ;
267
334
268
335
const calendarContainerStyle = useMemo ( ( ) => {
0 commit comments