react-native-bigger-calendar 0.1.0 → 0.2.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/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
+ import { addDays, addMonths, differenceInCalendarDays, differenceInCalendarMonths, differenceInMinutes, eachDayOfInterval, endOfDay, endOfMonth, endOfWeek, format, getHours, getISOWeek, getMinutes, isSameDay, isSameMonth, isToday, max, min, startOfDay, startOfMonth, startOfWeek } from "date-fns";
1
2
  import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
3
  import Animated, { scrollTo, useAnimatedReaction, useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue } from "react-native-reanimated";
3
- import { addDays, addMonths, differenceInCalendarDays, differenceInCalendarMonths, differenceInMinutes, eachDayOfInterval, endOfMonth, endOfWeek, format, getHours, getMinutes, isSameDay, isSameMonth, isToday, max, min, startOfDay, startOfMonth, startOfWeek } from "date-fns";
4
+ import { LegendList } from "@legendapp/list/react-native";
4
5
  import { Pressable, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions } from "react-native";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
- import { LegendList } from "@legendapp/list/react-native";
7
7
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
8
8
  //#region src/theme.ts
9
9
  const defaultTheme = {
@@ -63,18 +63,169 @@ const CalendarThemeContext = createContext(defaultTheme);
63
63
  const CalendarThemeProvider = CalendarThemeContext.Provider;
64
64
  const useCalendarTheme = () => useContext(CalendarThemeContext);
65
65
  //#endregion
66
+ //#region src/utils/dates.ts
67
+ /** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
68
+ const getWeekDays = (date, weekStartsOn) => {
69
+ const start = startOfWeek(date, { weekStartsOn });
70
+ return Array.from({ length: 7 }, (_, index) => addDays(start, index));
71
+ };
72
+ /** How many day columns a time-grid mode shows. `custom` uses `numberOfDays`. */
73
+ const viewDayCount = (mode, numberOfDays = 1) => {
74
+ switch (mode) {
75
+ case "week": return 7;
76
+ case "3days": return 3;
77
+ case "custom": return Math.max(1, Math.floor(numberOfDays));
78
+ default: return 1;
79
+ }
80
+ };
81
+ /**
82
+ * Days in the inclusive span from `weekStartsOn` to `weekEndsOn` (1–7),
83
+ * wrapping when the end precedes the start (e.g. Sat→Wed). Mirrors
84
+ * react-native-big-calendar's `weekDaysCount`.
85
+ */
86
+ const weekDaysCount = (weekStartsOn, weekEndsOn) => {
87
+ if (weekEndsOn < weekStartsOn) {
88
+ let count = 1;
89
+ let i = weekStartsOn;
90
+ while (i !== weekEndsOn && count <= 7) {
91
+ i = (i + 1) % 7;
92
+ count++;
93
+ }
94
+ return count;
95
+ }
96
+ if (weekEndsOn > weekStartsOn) return weekEndsOn - weekStartsOn + 1;
97
+ return 1;
98
+ };
99
+ /**
100
+ * The day columns to render for a time-grid page. `week` spans the calendar week
101
+ * (honouring `weekStartsOn`). `custom` with a `weekEndsOn` spans the partial week
102
+ * from `weekStartsOn` to `weekEndsOn` (anchored to `date`'s week, paging by week);
103
+ * otherwise every mode shows `viewDayCount` consecutive days starting at `date`.
104
+ */
105
+ const getViewDays = (mode, date, weekStartsOn, numberOfDays = 1, isRTL = false, weekEndsOn) => {
106
+ let days;
107
+ if (mode === "week") days = getWeekDays(date, weekStartsOn);
108
+ else if (mode === "custom" && weekEndsOn != null) {
109
+ const subject = startOfDay(date);
110
+ const offset = weekStartsOn - subject.getDay();
111
+ days = Array.from({ length: weekDaysCount(weekStartsOn, weekEndsOn) }, (_, index) => addDays(subject, index + offset));
112
+ } else days = Array.from({ length: viewDayCount(mode, numberOfDays) }, (_, index) => addDays(startOfDay(date), index));
113
+ return isRTL ? days.reverse() : days;
114
+ };
115
+ const isWeekend = (date) => {
116
+ const day = date.getDay();
117
+ return day === 0 || day === 6;
118
+ };
119
+ const getIsToday = (date) => isToday(date);
120
+ const isSameCalendarDay = (a, b) => isSameDay(a, b);
121
+ /** Minutes elapsed since midnight (0–1439). */
122
+ const minutesIntoDay = (date) => getHours(date) * 60 + getMinutes(date);
123
+ //#endregion
124
+ //#region src/components/Agenda.tsx
125
+ /**
126
+ * A vertical, day-grouped list of events (no time grid). Events are sorted by
127
+ * start, grouped under a date header per day. The consumer controls which
128
+ * events (and therefore which date range) are shown.
129
+ */
130
+ function Agenda({ events, locale, renderEvent, keyExtractor, onPressEvent, onLongPressEvent, onPressDay, activeDate, itemSeparatorComponent }) {
131
+ const theme = useCalendarTheme();
132
+ const RenderEventComponent = renderEvent;
133
+ const rows = useMemo(() => {
134
+ const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
135
+ const out = [];
136
+ let currentDay = null;
137
+ sorted.forEach((event, index) => {
138
+ if (!currentDay || !isSameDay(event.start, currentDay)) {
139
+ currentDay = startOfDay(event.start);
140
+ out.push({
141
+ kind: "header",
142
+ date: currentDay,
143
+ key: `h-${currentDay.toISOString()}`
144
+ });
145
+ }
146
+ out.push({
147
+ kind: "event",
148
+ event,
149
+ index,
150
+ key: `e-${keyExtractor(event, index)}`
151
+ });
152
+ });
153
+ return out;
154
+ }, [events, keyExtractor]);
155
+ const keyExtractorRow = useCallback((row) => row.key, []);
156
+ const renderItem = useCallback(({ item }) => {
157
+ if (item.kind === "header") {
158
+ const isHighlighted = activeDate ? isSameDay(item.date, activeDate) : getIsToday(item.date);
159
+ return /* @__PURE__ */ jsx(Text, {
160
+ style: [
161
+ theme.text.weekday,
162
+ styles$5.header,
163
+ { color: isHighlighted ? theme.colors.todayBackground : theme.colors.textMuted }
164
+ ],
165
+ onPress: onPressDay ? () => onPressDay(item.date) : void 0,
166
+ accessibilityRole: onPressDay ? "button" : "header",
167
+ children: format(item.date, "EEEE, d LLLL", { locale })
168
+ });
169
+ }
170
+ return /* @__PURE__ */ jsx(View, {
171
+ style: styles$5.eventRow,
172
+ children: /* @__PURE__ */ jsx(RenderEventComponent, {
173
+ event: item.event,
174
+ mode: "schedule",
175
+ onPress: () => onPressEvent(item.event),
176
+ onLongPress: onLongPressEvent ? () => onLongPressEvent(item.event) : void 0
177
+ })
178
+ });
179
+ }, [
180
+ theme,
181
+ locale,
182
+ activeDate,
183
+ onPressDay,
184
+ onPressEvent,
185
+ onLongPressEvent,
186
+ RenderEventComponent
187
+ ]);
188
+ return /* @__PURE__ */ jsx(LegendList, {
189
+ style: styles$5.list,
190
+ data: rows,
191
+ keyExtractor: keyExtractorRow,
192
+ renderItem,
193
+ ItemSeparatorComponent: itemSeparatorComponent ?? void 0,
194
+ recycleItems: false
195
+ });
196
+ }
197
+ const styles$5 = StyleSheet.create({
198
+ list: { flex: 1 },
199
+ header: {
200
+ paddingTop: 12,
201
+ paddingBottom: 4,
202
+ paddingHorizontal: 12
203
+ },
204
+ eventRow: {
205
+ paddingHorizontal: 12,
206
+ paddingVertical: 2
207
+ }
208
+ });
209
+ //#endregion
66
210
  //#region src/components/DefaultEvent.tsx
67
211
  /**
68
212
  * The built-in event renderer: a filled, rounded box showing the event title
69
213
  * and (on the day/week grid) its time range. Pass your own `renderEvent` to
70
214
  * `<Calendar>` to replace it entirely.
71
215
  */
72
- function DefaultEvent({ event, mode, onPress }) {
216
+ function DefaultEvent({ event, mode, isAllDay, ampm = false, showTime = true, cellStyle, onPress, onLongPress }) {
73
217
  const theme = useCalendarTheme();
74
- const showTime = mode !== "month";
218
+ const timeFormat = ampm ? "h:mm a" : "HH:mm";
219
+ const shouldShowTime = mode !== "month" && !isAllDay && showTime;
75
220
  return /* @__PURE__ */ jsxs(TouchableOpacity, {
76
- style: [styles$3.box, { backgroundColor: theme.colors.eventBackground }],
221
+ style: [
222
+ styles$4.box,
223
+ { backgroundColor: theme.colors.eventBackground },
224
+ event.disabled && styles$4.disabled,
225
+ cellStyle
226
+ ],
77
227
  onPress,
228
+ onLongPress,
78
229
  activeOpacity: .7,
79
230
  accessibilityRole: "button",
80
231
  accessibilityLabel: event.title,
@@ -84,15 +235,15 @@ function DefaultEvent({ event, mode, onPress }) {
84
235
  ellipsizeMode: "tail",
85
236
  allowFontScaling: false,
86
237
  children: event.title
87
- }) : null, showTime ? /* @__PURE__ */ jsx(Text, {
88
- style: [styles$3.time, { color: theme.colors.eventText }],
238
+ }) : null, shouldShowTime ? /* @__PURE__ */ jsx(Text, {
239
+ style: [styles$4.time, { color: theme.colors.eventText }],
89
240
  numberOfLines: 1,
90
241
  allowFontScaling: false,
91
- children: `${format(event.start, "HH:mm")} - ${format(event.end, "HH:mm")}`
242
+ children: `${format(event.start, timeFormat)} - ${format(event.end, timeFormat)}`
92
243
  }) : null]
93
244
  });
94
245
  }
95
- const styles$3 = StyleSheet.create({
246
+ const styles$4 = StyleSheet.create({
96
247
  box: {
97
248
  flex: 1,
98
249
  borderRadius: 6,
@@ -100,24 +251,10 @@ const styles$3 = StyleSheet.create({
100
251
  paddingHorizontal: 4,
101
252
  overflow: "hidden"
102
253
  },
103
- time: { fontSize: 11 }
254
+ time: { fontSize: 11 },
255
+ disabled: { opacity: .5 }
104
256
  });
105
257
  //#endregion
106
- //#region src/utils/dates.ts
107
- /** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
108
- const getWeekDays = (date, weekStartsOn) => {
109
- const start = startOfWeek(date, { weekStartsOn });
110
- return Array.from({ length: 7 }, (_, index) => addDays(start, index));
111
- };
112
- const isWeekend = (date) => {
113
- const day = date.getDay();
114
- return day === 0 || day === 6;
115
- };
116
- const getIsToday = (date) => isToday(date);
117
- const isSameCalendarDay = (a, b) => isSameDay(a, b);
118
- /** Minutes elapsed since midnight (0–1439). */
119
- const minutesIntoDay = (date) => getHours(date) * 60 + getMinutes(date);
120
- //#endregion
121
258
  //#region src/utils/layout.ts
122
259
  const MINUTES_PER_HOUR$1 = 60;
123
260
  const MIN_DURATION_HOURS = .25;
@@ -130,7 +267,7 @@ const MIN_DURATION_HOURS = .25;
130
267
  function layoutDayEvents(events, day) {
131
268
  const dayStart = startOfDay(day);
132
269
  const nextDayStart = addDays(dayStart, 1);
133
- const segments = events.filter((event) => event.start < nextDayStart && event.end > dayStart).map((event) => {
270
+ const segments = events.filter((event) => !isAllDayEvent(event)).filter((event) => event.start < nextDayStart && event.end > dayStart).map((event) => {
134
271
  const segStart = max([event.start, dayStart]);
135
272
  const segEnd = min([event.end, nextDayStart]);
136
273
  return {
@@ -174,6 +311,16 @@ function layoutDayEvents(events, day) {
174
311
  if (cluster.length > 0) flushCluster();
175
312
  return positioned;
176
313
  }
314
+ const atMidnight = (date) => date.getHours() === 0 && date.getMinutes() === 0 && date.getSeconds() === 0 && date.getMilliseconds() === 0;
315
+ /**
316
+ * Whether an event belongs in the all-day lane. An explicit `allDay` flag wins;
317
+ * otherwise it's inferred when the event spans whole days (both `start` and
318
+ * `end` land on midnight, e.g. an iCal-style all-day event). Pure.
319
+ */
320
+ function isAllDayEvent(event) {
321
+ if (typeof event.allDay === "boolean") return event.allDay;
322
+ return event.end > event.start && atMidnight(event.start) && atMidnight(event.end);
323
+ }
177
324
  /**
178
325
  * The `startOfDay` ISO keys of every calendar day an event touches (inclusive).
179
326
  * An event ending exactly at midnight does not count the following day. Used to
@@ -193,15 +340,23 @@ const chunkIntoWeeks = (days) => {
193
340
  for (let index = 0; index < days.length; index += 7) weeks.push(days.slice(index, index + 7));
194
341
  return weeks;
195
342
  };
196
- function MonthViewInner({ date, events, maxVisibleEventCount, weekStartsOn, renderEvent, keyExtractor, onPressDay, onPressEvent, onPressMore }) {
343
+ function MonthViewInner({ date, events, maxVisibleEventCount, weekStartsOn, locale, sortedMonthView = true, moreLabel = "{moreCount} More", showAdjacentMonths = true, disableMonthEventCellPress = false, isRTL = false, showSixWeeks = false, activeDate, calendarCellStyle, renderEvent, keyExtractor, onPressDay, onLongPressDay, onPressEvent, onLongPressEvent, onPressMore }) {
197
344
  const theme = useCalendarTheme();
198
345
  const RenderEventComponent = renderEvent;
199
346
  const weeks = useMemo(() => {
200
- return chunkIntoWeeks(eachDayOfInterval({
201
- start: startOfWeek(startOfMonth(date), { weekStartsOn }),
202
- end: endOfWeek(endOfMonth(date), { weekStartsOn })
347
+ const start = startOfWeek(startOfMonth(date), { weekStartsOn });
348
+ const naturalEnd = endOfWeek(endOfMonth(date), { weekStartsOn });
349
+ const chunked = chunkIntoWeeks(eachDayOfInterval({
350
+ start,
351
+ end: showSixWeeks ? addDays(start, 41) : naturalEnd
203
352
  }));
204
- }, [date, weekStartsOn]);
353
+ return isRTL ? chunked.map((week) => [...week].reverse()) : chunked;
354
+ }, [
355
+ date,
356
+ weekStartsOn,
357
+ isRTL,
358
+ showSixWeeks
359
+ ]);
205
360
  const eventsByDay = useMemo(() => {
206
361
  const map = /* @__PURE__ */ new Map();
207
362
  for (const event of events) for (const key of eventDayKeys(event)) {
@@ -209,29 +364,38 @@ function MonthViewInner({ date, events, maxVisibleEventCount, weekStartsOn, rend
209
364
  if (existing) existing.push(event);
210
365
  else map.set(key, [event]);
211
366
  }
367
+ if (sortedMonthView) for (const list of map.values()) list.sort((a, b) => a.start.getTime() - b.start.getTime());
212
368
  return map;
213
- }, [events]);
369
+ }, [events, sortedMonthView]);
214
370
  const renderDay = (day) => {
215
- const dayEvents = eventsByDay.get(startOfDay(day).toISOString()) ?? [];
216
371
  const isCurrentMonth = isSameMonth(day, date);
372
+ if (!isCurrentMonth && !showAdjacentMonths) return /* @__PURE__ */ jsx(View, { style: [
373
+ styles$3.dayCell,
374
+ { borderColor: theme.colors.gridLine },
375
+ isWeekend(day) && { backgroundColor: theme.colors.weekendBackground }
376
+ ] }, day.toISOString());
377
+ const dayEvents = eventsByDay.get(startOfDay(day).toISOString()) ?? [];
217
378
  const isToday = getIsToday(day);
379
+ const isHighlighted = activeDate ? isSameCalendarDay(day, activeDate) : isToday;
218
380
  const hiddenCount = dayEvents.length - maxVisibleEventCount;
219
- const dateColor = isToday ? theme.colors.todayText : isCurrentMonth ? theme.colors.text : theme.colors.textDisabled;
381
+ const dateColor = isHighlighted ? theme.colors.todayText : isCurrentMonth ? theme.colors.text : theme.colors.textDisabled;
220
382
  const eventCount = dayEvents.length;
221
- const accessibilityLabel = `${format(day, "EEEE, d LLLL yyyy")}${isToday ? ", today" : ""}, ${eventCount} ${eventCount === 1 ? "event" : "events"}`;
383
+ const accessibilityLabel = `${format(day, "EEEE, d LLLL yyyy", { locale })}${isToday ? ", today" : ""}, ${eventCount} ${eventCount === 1 ? "event" : "events"}`;
222
384
  return /* @__PURE__ */ jsxs(TouchableOpacity, {
223
385
  style: [
224
- styles$2.dayCell,
386
+ styles$3.dayCell,
225
387
  { borderColor: theme.colors.gridLine },
226
- isWeekend(day) && { backgroundColor: theme.colors.weekendBackground }
388
+ isWeekend(day) && { backgroundColor: theme.colors.weekendBackground },
389
+ calendarCellStyle?.(day)
227
390
  ],
228
391
  onPress: onPressDay ? () => onPressDay(day) : void 0,
229
- disabled: !onPressDay,
392
+ onLongPress: onLongPressDay ? () => onLongPressDay(day) : void 0,
393
+ disabled: !onPressDay && !onLongPressDay,
230
394
  accessibilityRole: onPressDay ? "button" : void 0,
231
395
  accessibilityLabel,
232
396
  children: [
233
397
  /* @__PURE__ */ jsx(View, {
234
- style: [styles$2.dateBadge, isToday && {
398
+ style: [styles$3.dateBadge, isHighlighted && {
235
399
  backgroundColor: theme.colors.todayBackground,
236
400
  borderRadius: theme.todayBadgeRadius
237
401
  }],
@@ -242,38 +406,40 @@ function MonthViewInner({ date, events, maxVisibleEventCount, weekStartsOn, rend
242
406
  })
243
407
  }),
244
408
  dayEvents.slice(0, maxVisibleEventCount).map((event, index) => /* @__PURE__ */ jsx(View, {
245
- style: styles$2.monthEvent,
409
+ style: styles$3.monthEvent,
246
410
  children: /* @__PURE__ */ jsx(RenderEventComponent, {
247
411
  event,
248
412
  mode: "month",
249
- onPress: () => onPressEvent(event)
413
+ isAllDay: isAllDayEvent(event),
414
+ onPress: disableMonthEventCellPress ? () => {} : () => onPressEvent(event),
415
+ onLongPress: disableMonthEventCellPress || !onLongPressEvent ? void 0 : () => onLongPressEvent(event)
250
416
  })
251
417
  }, keyExtractor(event, index))),
252
418
  hiddenCount > 0 ? /* @__PURE__ */ jsx(Text, {
253
419
  style: [
254
420
  theme.text.more,
255
- styles$2.moreLabel,
421
+ styles$3.moreLabel,
256
422
  { color: theme.colors.textMuted }
257
423
  ],
258
424
  onPress: onPressMore ? () => onPressMore(dayEvents, day) : void 0,
259
425
  accessibilityRole: "button",
260
426
  accessibilityLabel: `Show ${hiddenCount} more events`,
261
427
  allowFontScaling: false,
262
- children: `${hiddenCount} More`
428
+ children: moreLabel.replace("{moreCount}", String(hiddenCount))
263
429
  }) : null
264
430
  ]
265
431
  }, day.toISOString());
266
432
  };
267
433
  return /* @__PURE__ */ jsx(View, {
268
- style: styles$2.container,
434
+ style: styles$3.container,
269
435
  children: weeks.map((week) => /* @__PURE__ */ jsx(View, {
270
- style: styles$2.weekRow,
436
+ style: styles$3.weekRow,
271
437
  children: week.map((day) => renderDay(day))
272
438
  }, week[0].toISOString()))
273
439
  });
274
440
  }
275
441
  const MonthView = memo(MonthViewInner);
276
- const styles$2 = StyleSheet.create({
442
+ const styles$3 = StyleSheet.create({
277
443
  container: { flex: 1 },
278
444
  weekRow: {
279
445
  flex: 1,
@@ -301,7 +467,7 @@ const styles$2 = StyleSheet.create({
301
467
  //#region src/components/MonthPager.tsx
302
468
  const PAGE_WINDOW$1 = 60;
303
469
  const PAGE_VIEWABILITY$1 = { itemVisiblePercentThreshold: 90 };
304
- function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, renderEvent, keyExtractor, onPressDay, onPressEvent, onPressMore, onChangeDate, freeSwipe = false }) {
470
+ function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, locale, sortedMonthView, moreLabel, showAdjacentMonths, disableMonthEventCellPress, isRTL, calendarCellStyle, renderEvent, keyExtractor, onPressDay, onLongPressDay, onPressEvent, onLongPressEvent, onPressMore, onChangeDate, freeSwipe = false, swipeEnabled = true, showSixWeeks = false, activeDate, renderHeaderForMonthView }) {
305
471
  const { width, height } = useWindowDimensions();
306
472
  const listRef = useRef(null);
307
473
  const [pageHeight, setPageHeight] = useState(height);
@@ -324,6 +490,14 @@ function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, ren
324
490
  animated: false
325
491
  });
326
492
  }, [activeIndex]);
493
+ const weekDays = useMemo(() => {
494
+ const days = getWeekDays(anchor, weekStartsOn);
495
+ return isRTL ? days.reverse() : days;
496
+ }, [
497
+ anchor,
498
+ weekStartsOn,
499
+ isRTL
500
+ ]);
327
501
  const snapToIndices = useMemo(() => monthDates.map((_, index) => index), [monthDates]);
328
502
  const keyExtractorList = useCallback((item) => item.toISOString(), []);
329
503
  const getFixedItemSize = useCallback(() => width, [width]);
@@ -337,10 +511,21 @@ function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, ren
337
511
  events,
338
512
  maxVisibleEventCount,
339
513
  weekStartsOn,
514
+ locale,
515
+ sortedMonthView,
516
+ moreLabel,
517
+ showAdjacentMonths,
518
+ disableMonthEventCellPress,
519
+ isRTL,
520
+ showSixWeeks,
521
+ activeDate,
522
+ calendarCellStyle,
340
523
  renderEvent,
341
524
  keyExtractor,
342
525
  onPressDay,
526
+ onLongPressDay,
343
527
  onPressEvent,
528
+ onLongPressEvent,
344
529
  onPressMore
345
530
  })
346
531
  }), [
@@ -349,44 +534,131 @@ function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, ren
349
534
  events,
350
535
  maxVisibleEventCount,
351
536
  weekStartsOn,
537
+ locale,
538
+ sortedMonthView,
539
+ moreLabel,
540
+ showAdjacentMonths,
541
+ disableMonthEventCellPress,
542
+ isRTL,
543
+ showSixWeeks,
544
+ activeDate,
545
+ calendarCellStyle,
352
546
  renderEvent,
353
547
  keyExtractor,
354
548
  onPressDay,
549
+ onLongPressDay,
355
550
  onPressEvent,
551
+ onLongPressEvent,
356
552
  onPressMore
357
553
  ]);
358
- return /* @__PURE__ */ jsx(View, {
359
- style: styles$1.pager,
360
- onLayout: (event) => setPageHeight(event.nativeEvent.layout.height),
361
- children: /* @__PURE__ */ jsx(LegendList, {
362
- ref: listRef,
363
- style: styles$1.pagerList,
364
- data: monthDates,
365
- horizontal: true,
366
- recycleItems: false,
367
- keyExtractor: keyExtractorList,
368
- getFixedItemSize,
369
- pagingEnabled: !freeSwipe,
370
- snapToIndices: freeSwipe ? snapToIndices : void 0,
371
- initialScrollIndex: activeIndex,
372
- showsHorizontalScrollIndicator: false,
373
- viewabilityConfig: PAGE_VIEWABILITY$1,
374
- onViewableItemsChanged: handleViewableItemsChanged,
375
- renderItem
376
- }, pageHeight)
554
+ return /* @__PURE__ */ jsxs(View, {
555
+ style: styles$2.container,
556
+ children: [renderHeaderForMonthView ? renderHeaderForMonthView(weekDays) : /* @__PURE__ */ jsx(MonthWeekdayHeader, {
557
+ weekDays,
558
+ locale
559
+ }), /* @__PURE__ */ jsx(View, {
560
+ style: styles$2.pager,
561
+ onLayout: (event) => setPageHeight(event.nativeEvent.layout.height),
562
+ children: /* @__PURE__ */ jsx(LegendList, {
563
+ ref: listRef,
564
+ style: styles$2.pagerList,
565
+ data: monthDates,
566
+ horizontal: true,
567
+ recycleItems: false,
568
+ keyExtractor: keyExtractorList,
569
+ getFixedItemSize,
570
+ scrollEnabled: swipeEnabled,
571
+ pagingEnabled: !freeSwipe,
572
+ snapToIndices: freeSwipe ? snapToIndices : void 0,
573
+ initialScrollIndex: activeIndex,
574
+ showsHorizontalScrollIndicator: false,
575
+ viewabilityConfig: PAGE_VIEWABILITY$1,
576
+ onViewableItemsChanged: handleViewableItemsChanged,
577
+ renderItem
578
+ }, pageHeight)
579
+ })]
377
580
  });
378
581
  }
379
582
  const MonthPager = memo(MonthPagerInner);
380
- const styles$1 = StyleSheet.create({
583
+ const MonthWeekdayHeader = ({ weekDays, locale }) => {
584
+ const theme = useCalendarTheme();
585
+ return /* @__PURE__ */ jsx(View, {
586
+ style: styles$2.weekdayHeader,
587
+ children: weekDays.map((day) => /* @__PURE__ */ jsx(Text, {
588
+ style: [
589
+ theme.text.weekday,
590
+ styles$2.weekdayLabel,
591
+ { color: theme.colors.textMuted }
592
+ ],
593
+ allowFontScaling: false,
594
+ children: format(day, "EEE", { locale })
595
+ }, day.toISOString()))
596
+ });
597
+ };
598
+ const styles$2 = StyleSheet.create({
599
+ container: { flex: 1 },
381
600
  pager: { flex: 1 },
382
- pagerList: { flex: 1 }
601
+ pagerList: { flex: 1 },
602
+ weekdayHeader: {
603
+ flexDirection: "row",
604
+ paddingBottom: 4
605
+ },
606
+ weekdayLabel: {
607
+ flex: 1,
608
+ textAlign: "center"
609
+ }
610
+ });
611
+ //#endregion
612
+ //#region src/components/AllDayLane.tsx
613
+ /**
614
+ * The all-day lane that sits above the scrolling time grid. All-day events are
615
+ * excluded from the timed columns (see `layoutDayEvents`) and shown here,
616
+ * stacked under their day(s). Renders nothing when no day has an all-day event,
617
+ * so timed-only calendars are unaffected.
618
+ */
619
+ function AllDayLane({ days, events, mode, hourColumnWidth, dayWidth, renderEvent, keyExtractor, onPressEvent, onLongPressEvent }) {
620
+ const theme = useCalendarTheme();
621
+ const RenderEventComponent = renderEvent;
622
+ const allDay = events.filter(isAllDayEvent);
623
+ const perDay = days.map((day) => {
624
+ const start = startOfDay(day);
625
+ const next = addDays(start, 1);
626
+ return allDay.filter((event) => event.start < next && event.end > start);
627
+ });
628
+ if (perDay.every((list) => list.length === 0)) return null;
629
+ return /* @__PURE__ */ jsxs(View, {
630
+ style: [styles$1.lane, { borderBottomColor: theme.colors.gridLine }],
631
+ children: [/* @__PURE__ */ jsx(View, { style: { width: hourColumnWidth } }), days.map((day, dayIndex) => /* @__PURE__ */ jsx(View, {
632
+ style: [styles$1.column, { width: dayWidth }],
633
+ children: perDay[dayIndex].map((event, index) => /* @__PURE__ */ jsx(View, {
634
+ style: styles$1.chip,
635
+ children: /* @__PURE__ */ jsx(RenderEventComponent, {
636
+ event,
637
+ mode,
638
+ isAllDay: true,
639
+ onPress: () => onPressEvent(event),
640
+ onLongPress: onLongPressEvent ? () => onLongPressEvent(event) : void 0
641
+ })
642
+ }, keyExtractor(event, index)))
643
+ }, day.toISOString()))]
644
+ });
645
+ }
646
+ const styles$1 = StyleSheet.create({
647
+ lane: {
648
+ flexDirection: "row",
649
+ borderBottomWidth: StyleSheet.hairlineWidth,
650
+ paddingBottom: 2
651
+ },
652
+ column: {
653
+ paddingHorizontal: 1,
654
+ gap: 2
655
+ },
656
+ chip: { minHeight: 18 }
383
657
  });
384
658
  //#endregion
385
659
  //#region src/components/TimeGrid.tsx
386
660
  const MINUTES_PER_HOUR = 60;
387
661
  const HOURS_PER_DAY = 24;
388
- const DAY_VIEW_STEP = 1;
389
- const WEEK_VIEW_STEP = 7;
390
662
  const PAGE_WINDOW = 180;
391
663
  const PAGE_VIEWABILITY = { itemVisiblePercentThreshold: 90 };
392
664
  const DEFAULT_HOUR_HEIGHT = 64;
@@ -411,7 +683,7 @@ function formatHourLabel(hour, ampm) {
411
683
  const period = hour < 12 ? "AM" : "PM";
412
684
  return `${hour % 12 === 0 ? 12 : hour % 12} ${period}`;
413
685
  }
414
- function AnimatedEventBox({ positioned, cellHeight, minHour, left, width, mode, renderEvent, onPress }) {
686
+ function AnimatedEventBox({ positioned, cellHeight, minHour, left, width, mode, renderEvent, onPress, onLongPress }) {
415
687
  const RenderEventComponent = renderEvent;
416
688
  const boxHeight = useDerivedValue(() => Math.max(positioned.durationHours * cellHeight.value, MIN_EVENT_HEIGHT), [positioned.durationHours]);
417
689
  const boxStyle = useAnimatedStyle(() => ({
@@ -423,6 +695,7 @@ function AnimatedEventBox({ positioned, cellHeight, minHour, left, width, mode,
423
695
  minHour
424
696
  ]);
425
697
  const handlePress = () => onPress(positioned.event);
698
+ const handleLongPress = onLongPress ? () => onLongPress(positioned.event) : void 0;
426
699
  return /* @__PURE__ */ jsx(Animated.View, {
427
700
  style: [
428
701
  styles.eventBox,
@@ -438,17 +711,21 @@ function AnimatedEventBox({ positioned, cellHeight, minHour, left, width, mode,
438
711
  boxHeight,
439
712
  continuesBefore: positioned.continuesBefore,
440
713
  continuesAfter: positioned.continuesAfter,
441
- onPress: handlePress
714
+ onPress: handlePress,
715
+ onLongPress: handleLongPress
442
716
  })
443
717
  });
444
718
  }
445
- const HourRow = ({ hour, minHour, cellHeight, hourColumnWidth, label }) => {
719
+ const HourRow = ({ hour, minHour, cellHeight, hourColumnWidth, label, ampm, hourComponent }) => {
446
720
  const theme = useCalendarTheme();
447
721
  const animatedStyle = useAnimatedStyle(() => ({ top: (hour - minHour) * cellHeight.value }), [hour, minHour]);
448
722
  return /* @__PURE__ */ jsxs(Animated.View, {
449
723
  style: [styles.hourRow, animatedStyle],
450
724
  pointerEvents: "none",
451
- children: [/* @__PURE__ */ jsx(Text, {
725
+ children: [hourComponent ? /* @__PURE__ */ jsx(View, {
726
+ style: { width: hourColumnWidth },
727
+ children: hourComponent(hour, ampm)
728
+ }) : /* @__PURE__ */ jsx(Text, {
452
729
  style: [
453
730
  theme.text.hourLabel,
454
731
  styles.hourLabel,
@@ -462,6 +739,25 @@ const HourRow = ({ hour, minHour, cellHeight, hourColumnWidth, label }) => {
462
739
  }), /* @__PURE__ */ jsx(View, { style: [styles.hourLine, { backgroundColor: theme.colors.gridLine }] })]
463
740
  });
464
741
  };
742
+ const TimeslotLine = ({ hour, minHour, fraction, cellHeight, hourColumnWidth }) => {
743
+ const theme = useCalendarTheme();
744
+ const animatedStyle = useAnimatedStyle(() => ({ top: (hour - minHour + fraction) * cellHeight.value }), [
745
+ hour,
746
+ minHour,
747
+ fraction
748
+ ]);
749
+ return /* @__PURE__ */ jsx(Animated.View, {
750
+ style: [
751
+ styles.timeslotLine,
752
+ {
753
+ left: hourColumnWidth,
754
+ backgroundColor: theme.colors.gridLine
755
+ },
756
+ animatedStyle
757
+ ],
758
+ pointerEvents: "none"
759
+ });
760
+ };
465
761
  const NowIndicator = ({ cellHeight, nowHours, minHour, left, color }) => {
466
762
  const animatedStyle = useAnimatedStyle(() => ({ top: (nowHours - minHour) * cellHeight.value }), [nowHours, minHour]);
467
763
  return /* @__PURE__ */ jsx(Animated.View, {
@@ -476,7 +772,7 @@ const NowIndicator = ({ cellHeight, nowHours, minHour, left, color }) => {
476
772
  pointerEvents: "none"
477
773
  });
478
774
  };
479
- function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, committedCellHeight, scrollY, isActive, scrollOffsetMinutes, weekStartsOn, hourColumnWidth, minHour, maxHour, ampm, minHourHeight, maxHourHeight, showNowIndicator, renderEvent, keyExtractor, onPressEvent, onPressCell }) {
775
+ function TimetablePageInner({ mode, numberOfDays, date, events, cellHeight, hourHeight, committedCellHeight, scrollY, isActive, scrollOffsetMinutes, weekStartsOn, weekEndsOn, hourColumnWidth, minHour, maxHour, ampm, timeslots, isRTL, showVerticalScrollIndicator, verticalScrollEnabled, hourComponent, calendarCellStyle, minHourHeight, maxHourHeight, showNowIndicator, renderEvent, keyExtractor, onPressEvent, onLongPressEvent, onPressCell, onLongPressCell }) {
480
776
  const theme = useCalendarTheme();
481
777
  const { width } = useWindowDimensions();
482
778
  const scrollRef = useAnimatedRef();
@@ -487,24 +783,34 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
487
783
  useAnimatedReaction(() => scrollY.value, (current, previous) => {
488
784
  if (!isActive && current !== previous) scrollTo(scrollRef, 0, current, false);
489
785
  });
490
- const days = useMemo(() => mode === "week" ? getWeekDays(date, weekStartsOn) : [date], [
786
+ const days = useMemo(() => getViewDays(mode, date, weekStartsOn, numberOfDays, isRTL, weekEndsOn), [
491
787
  mode,
492
788
  date,
493
- weekStartsOn
789
+ weekStartsOn,
790
+ numberOfDays,
791
+ isRTL,
792
+ weekEndsOn
494
793
  ]);
495
794
  const dayWidth = (width - hourColumnWidth) / days.length;
496
795
  const dayLeft = (dayIndex) => hourColumnWidth + dayIndex * dayWidth;
497
796
  const dayLayouts = useMemo(() => days.map((day) => layoutDayEvents(events, day)), [days, events]);
498
- const handleBackgroundPress = (event) => {
499
- if (!onPressCell) return;
797
+ const cellDateFromTouch = (event) => {
500
798
  const { locationX, locationY } = event.nativeEvent;
501
799
  const day = days[days.length === 1 ? 0 : Math.floor(locationX / dayWidth)];
502
- if (!day) return;
800
+ if (!day) return null;
503
801
  const minutes = Math.round((minHour + locationY / heightSource.value) * MINUTES_PER_HOUR);
504
802
  const pressed = new Date(day);
505
803
  pressed.setHours(0, 0, 0, 0);
506
804
  pressed.setMinutes(minutes);
507
- onPressCell(pressed);
805
+ return pressed;
806
+ };
807
+ const handleBackgroundPress = (event) => {
808
+ const date = onPressCell && cellDateFromTouch(event);
809
+ if (date) onPressCell?.(date);
810
+ };
811
+ const handleBackgroundLongPress = (event) => {
812
+ const date = onLongPressCell && cellDateFromTouch(event);
813
+ if (date) onLongPressCell?.(date);
508
814
  };
509
815
  const hoursRange = useMemo(() => Array.from({ length: maxHour - minHour }, (_, index) => minHour + index), [minHour, maxHour]);
510
816
  const now = useNow(showNowIndicator && isActive);
@@ -533,13 +839,24 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
533
839
  minHourHeight,
534
840
  maxHourHeight
535
841
  ]);
536
- return /* @__PURE__ */ jsx(View, {
842
+ return /* @__PURE__ */ jsxs(View, {
537
843
  style: styles.container,
538
- children: /* @__PURE__ */ jsx(GestureDetector, {
844
+ children: [/* @__PURE__ */ jsx(AllDayLane, {
845
+ days,
846
+ events,
847
+ mode,
848
+ hourColumnWidth,
849
+ dayWidth,
850
+ renderEvent,
851
+ keyExtractor,
852
+ onPressEvent,
853
+ onLongPressEvent
854
+ }), /* @__PURE__ */ jsx(GestureDetector, {
539
855
  gesture: zoomGesture,
540
856
  children: /* @__PURE__ */ jsx(Animated.ScrollView, {
541
857
  ref: scrollRef,
542
- showsVerticalScrollIndicator: true,
858
+ showsVerticalScrollIndicator: showVerticalScrollIndicator,
859
+ scrollEnabled: verticalScrollEnabled,
543
860
  onScroll: scrollHandler,
544
861
  scrollEventThrottle: 16,
545
862
  contentContainerStyle: { paddingTop: HOUR_LABEL_TOP_INSET },
@@ -550,9 +867,10 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
550
867
  children: /* @__PURE__ */ jsxs(Animated.View, {
551
868
  style: [styles.content, fullHeightStyle],
552
869
  children: [
553
- onPressCell ? /* @__PURE__ */ jsx(Pressable, {
870
+ onPressCell || onLongPressCell ? /* @__PURE__ */ jsx(Pressable, {
554
871
  style: [styles.cellPressLayer, { left: hourColumnWidth }],
555
- onPress: handleBackgroundPress,
872
+ onPress: onPressCell ? handleBackgroundPress : void 0,
873
+ onLongPress: onLongPressCell ? handleBackgroundLongPress : void 0,
556
874
  importantForAccessibility: "no",
557
875
  accessibilityElementsHidden: true
558
876
  }) : null,
@@ -568,6 +886,21 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
568
886
  ],
569
887
  pointerEvents: "none"
570
888
  }, `weekend-${day.toISOString()}`) : null),
889
+ calendarCellStyle ? days.map((day, dayIndex) => {
890
+ const cellStyle = calendarCellStyle(day);
891
+ return cellStyle ? /* @__PURE__ */ jsx(Animated.View, {
892
+ style: [
893
+ styles.weekendColumn,
894
+ {
895
+ left: dayLeft(dayIndex),
896
+ width: dayWidth
897
+ },
898
+ cellStyle,
899
+ fullHeightStyle
900
+ ],
901
+ pointerEvents: "none"
902
+ }, `cell-${day.toISOString()}`) : null;
903
+ }) : null,
571
904
  days.map((day, dayIndex) => /* @__PURE__ */ jsx(Animated.View, {
572
905
  style: [
573
906
  styles.daySeparator,
@@ -582,8 +915,17 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
582
915
  minHour,
583
916
  cellHeight: heightSource,
584
917
  hourColumnWidth,
585
- label: formatHourLabel(hour, ampm)
918
+ label: formatHourLabel(hour, ampm),
919
+ ampm,
920
+ hourComponent
586
921
  }, hour)),
922
+ timeslots > 1 ? hoursRange.flatMap((hour) => Array.from({ length: timeslots - 1 }, (_, i) => /* @__PURE__ */ jsx(TimeslotLine, {
923
+ hour,
924
+ minHour,
925
+ fraction: (i + 1) / timeslots,
926
+ cellHeight: heightSource,
927
+ hourColumnWidth
928
+ }, `slot-${hour}-${i}`))) : null,
587
929
  dayLayouts.flatMap((layout, dayIndex) => layout.filter((p) => p.startHours < maxHour && p.startHours + p.durationHours > minHour).map((positioned, eventIndex) => {
588
930
  const columnWidth = dayWidth / positioned.columns;
589
931
  return /* @__PURE__ */ jsx(AnimatedEventBox, {
@@ -594,7 +936,8 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
594
936
  width: columnWidth,
595
937
  mode,
596
938
  renderEvent,
597
- onPress: onPressEvent
939
+ onPress: onPressEvent,
940
+ onLongPress: onLongPressEvent
598
941
  }, keyExtractor(positioned.event, eventIndex));
599
942
  })),
600
943
  showNowIndicator && nowDayIndex >= 0 && nowInWindow ? /* @__PURE__ */ jsx(NowIndicator, {
@@ -607,40 +950,48 @@ function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, commit
607
950
  ]
608
951
  })
609
952
  })
610
- })
953
+ })]
611
954
  });
612
955
  }
613
956
  const TimetablePage = memo(TimetablePageInner);
614
- function TimeGridInner({ mode, date, events, cellHeight, hourHeight = 64, weekStartsOn, renderEvent, keyExtractor, scrollOffsetMinutes = 0, hourColumnWidth = DEFAULT_HOUR_COLUMN_WIDTH, minHour = 0, maxHour = HOURS_PER_DAY, ampm = false, minHourHeight = DEFAULT_MIN_HOUR_HEIGHT, maxHourHeight = DEFAULT_MAX_HOUR_HEIGHT, showNowIndicator = true, locale, freeSwipe = false, onPressEvent, onPressCell, onChangeDate, renderHeader }) {
957
+ function TimeGridInner({ mode, numberOfDays = 1, weekEndsOn, date, events, cellHeight, hourHeight = 64, weekStartsOn, renderEvent, keyExtractor, scrollOffsetMinutes = 0, hourColumnWidth: hourColumnWidthProp = DEFAULT_HOUR_COLUMN_WIDTH, hideHours = false, timeslots = 1, calendarCellStyle, showWeekNumber = false, headerComponent, minHour = 0, maxHour = HOURS_PER_DAY, ampm = false, isRTL = false, minHourHeight = DEFAULT_MIN_HOUR_HEIGHT, maxHourHeight = DEFAULT_MAX_HOUR_HEIGHT, showNowIndicator = true, locale, freeSwipe = false, swipeEnabled = true, showVerticalScrollIndicator = true, verticalScrollEnabled = true, weekNumberPrefix = "W", hourComponent, activeDate, resetPageOnPressCell = false, onPressEvent, onLongPressEvent, onPressCell, onLongPressCell, onPressDateHeader, onChangeDate, renderHeader }) {
615
958
  const clampedMinHour = Math.max(0, Math.min(minHour, HOURS_PER_DAY - 1));
616
959
  const clampedMaxHour = Math.max(clampedMinHour + 1, Math.min(maxHour, HOURS_PER_DAY));
960
+ const hourColumnWidth = hideHours ? 0 : hourColumnWidthProp;
617
961
  const { width, height } = useWindowDimensions();
618
962
  const listRef = useRef(null);
619
963
  const [pageHeight, setPageHeight] = useState(height);
620
- const step = mode === "week" ? WEEK_VIEW_STEP : DAY_VIEW_STEP;
964
+ const [measured, setMeasured] = useState(false);
965
+ const weekAnchored = mode === "week" || mode === "custom" && weekEndsOn != null;
966
+ const step = weekAnchored ? 7 : viewDayCount(mode, numberOfDays);
621
967
  const scrollY = useSharedValue(Math.max(0, scrollOffsetMinutes / MINUTES_PER_HOUR - clampedMinHour) * hourHeight);
622
968
  const committedCellHeight = useSharedValue(hourHeight);
623
969
  const [anchorDate] = useState(date);
624
- const anchor = useMemo(() => mode === "week" ? startOfWeek(anchorDate, { weekStartsOn }) : startOfDay(anchorDate), [
625
- mode,
970
+ const anchor = useMemo(() => weekAnchored ? startOfWeek(anchorDate, { weekStartsOn }) : startOfDay(anchorDate), [
971
+ weekAnchored,
626
972
  anchorDate,
627
973
  weekStartsOn
628
974
  ]);
629
975
  const pageDates = useMemo(() => Array.from({ length: 361 }, (_, i) => addDays(anchor, (i - PAGE_WINDOW) * step)), [anchor, step]);
630
976
  const activeIndex = useCallback((target) => {
631
- const aligned = mode === "week" ? startOfWeek(target, { weekStartsOn }) : startOfDay(target);
632
- return Math.round(differenceInCalendarDays(aligned, anchor) / step) + PAGE_WINDOW;
977
+ const aligned = weekAnchored ? startOfWeek(target, { weekStartsOn }) : startOfDay(target);
978
+ return Math.floor(differenceInCalendarDays(aligned, anchor) / step) + PAGE_WINDOW;
633
979
  }, [
634
980
  anchor,
635
- mode,
981
+ weekAnchored,
636
982
  step,
637
983
  weekStartsOn
638
984
  ])(date);
639
985
  const viewedIndexRef = useRef(activeIndex);
640
- const headerDays = useMemo(() => mode === "week" ? getWeekDays(date, weekStartsOn) : [date], [
986
+ const headerDays = useMemo(() => getViewDays(mode, pageDates[activeIndex] ?? date, weekStartsOn, numberOfDays, isRTL, weekEndsOn), [
641
987
  mode,
988
+ pageDates,
989
+ activeIndex,
642
990
  date,
643
- weekStartsOn
991
+ weekStartsOn,
992
+ numberOfDays,
993
+ isRTL,
994
+ weekEndsOn
644
995
  ]);
645
996
  const handleViewableItemsChanged = useCallback((info) => {
646
997
  const settled = info.viewableItems.find((token) => token.isViewable);
@@ -656,6 +1007,21 @@ function TimeGridInner({ mode, date, events, cellHeight, hourHeight = 64, weekSt
656
1007
  animated: false
657
1008
  });
658
1009
  }, [activeIndex]);
1010
+ const handlePressCell = useMemo(() => {
1011
+ if (!onPressCell) return void 0;
1012
+ if (!resetPageOnPressCell) return onPressCell;
1013
+ return (cellDate) => {
1014
+ onPressCell(cellDate);
1015
+ listRef.current?.scrollToIndex({
1016
+ index: activeIndex,
1017
+ animated: true
1018
+ });
1019
+ };
1020
+ }, [
1021
+ onPressCell,
1022
+ resetPageOnPressCell,
1023
+ activeIndex
1024
+ ]);
659
1025
  const snapToIndices = useMemo(() => pageDates.map((_, index) => index), [pageDates]);
660
1026
  const keyExtractorList = useCallback((item) => item.toISOString(), []);
661
1027
  const getFixedItemSize = useCallback(() => width, [width]);
@@ -666,6 +1032,7 @@ function TimeGridInner({ mode, date, events, cellHeight, hourHeight = 64, weekSt
666
1032
  },
667
1033
  children: /* @__PURE__ */ jsx(TimetablePage, {
668
1034
  mode,
1035
+ numberOfDays,
669
1036
  date: item,
670
1037
  events,
671
1038
  cellHeight,
@@ -675,22 +1042,32 @@ function TimeGridInner({ mode, date, events, cellHeight, hourHeight = 64, weekSt
675
1042
  isActive: index === activeIndex,
676
1043
  scrollOffsetMinutes,
677
1044
  weekStartsOn,
1045
+ weekEndsOn,
678
1046
  hourColumnWidth,
679
1047
  minHour: clampedMinHour,
680
1048
  maxHour: clampedMaxHour,
681
1049
  ampm,
1050
+ timeslots,
1051
+ isRTL,
1052
+ showVerticalScrollIndicator,
1053
+ verticalScrollEnabled,
1054
+ hourComponent,
1055
+ calendarCellStyle,
682
1056
  minHourHeight,
683
1057
  maxHourHeight,
684
1058
  showNowIndicator,
685
1059
  renderEvent,
686
1060
  keyExtractor,
687
1061
  onPressEvent,
688
- onPressCell
1062
+ onLongPressEvent,
1063
+ onPressCell: handlePressCell,
1064
+ onLongPressCell
689
1065
  })
690
1066
  }), [
691
1067
  width,
692
1068
  pageHeight,
693
1069
  mode,
1070
+ numberOfDays,
694
1071
  events,
695
1072
  cellHeight,
696
1073
  hourHeight,
@@ -699,79 +1076,114 @@ function TimeGridInner({ mode, date, events, cellHeight, hourHeight = 64, weekSt
699
1076
  activeIndex,
700
1077
  scrollOffsetMinutes,
701
1078
  weekStartsOn,
1079
+ weekEndsOn,
702
1080
  hourColumnWidth,
703
1081
  clampedMinHour,
704
1082
  clampedMaxHour,
705
1083
  ampm,
1084
+ timeslots,
1085
+ isRTL,
1086
+ showVerticalScrollIndicator,
1087
+ verticalScrollEnabled,
1088
+ hourComponent,
1089
+ calendarCellStyle,
706
1090
  minHourHeight,
707
1091
  maxHourHeight,
708
1092
  showNowIndicator,
709
1093
  renderEvent,
710
1094
  keyExtractor,
711
1095
  onPressEvent,
712
- onPressCell
1096
+ onLongPressEvent,
1097
+ handlePressCell,
1098
+ onLongPressCell
713
1099
  ]);
714
1100
  return /* @__PURE__ */ jsxs(View, {
715
1101
  style: styles.container,
716
- children: [renderHeader ? renderHeader(headerDays) : /* @__PURE__ */ jsx(DefaultHeader, {
717
- days: headerDays,
718
- mode,
719
- width,
720
- hourColumnWidth,
721
- locale
722
- }), /* @__PURE__ */ jsx(View, {
723
- style: styles.pager,
724
- onLayout: (event) => setPageHeight(event.nativeEvent.layout.height),
725
- children: /* @__PURE__ */ jsx(LegendList, {
726
- ref: listRef,
727
- style: styles.pagerList,
728
- data: pageDates,
729
- horizontal: true,
730
- recycleItems: false,
731
- keyExtractor: keyExtractorList,
732
- getFixedItemSize,
733
- pagingEnabled: !freeSwipe,
734
- snapToIndices: freeSwipe ? snapToIndices : void 0,
735
- initialScrollIndex: activeIndex,
736
- showsHorizontalScrollIndicator: false,
737
- viewabilityConfig: PAGE_VIEWABILITY,
738
- onViewableItemsChanged: handleViewableItemsChanged,
739
- renderItem
740
- }, pageHeight)
741
- })]
1102
+ children: [
1103
+ renderHeader ? renderHeader(headerDays) : /* @__PURE__ */ jsx(DefaultHeader, {
1104
+ days: headerDays,
1105
+ mode,
1106
+ width,
1107
+ hourColumnWidth,
1108
+ showWeekNumber,
1109
+ weekNumberPrefix,
1110
+ locale,
1111
+ activeDate,
1112
+ onPressDateHeader
1113
+ }),
1114
+ headerComponent,
1115
+ /* @__PURE__ */ jsx(View, {
1116
+ style: styles.pager,
1117
+ onLayout: (event) => {
1118
+ setPageHeight(event.nativeEvent.layout.height);
1119
+ setMeasured(true);
1120
+ },
1121
+ children: /* @__PURE__ */ jsx(LegendList, {
1122
+ ref: listRef,
1123
+ style: styles.pagerList,
1124
+ data: pageDates,
1125
+ horizontal: true,
1126
+ recycleItems: false,
1127
+ keyExtractor: keyExtractorList,
1128
+ getFixedItemSize,
1129
+ scrollEnabled: swipeEnabled,
1130
+ pagingEnabled: !freeSwipe,
1131
+ snapToIndices: freeSwipe ? snapToIndices : void 0,
1132
+ initialScrollIndex: activeIndex,
1133
+ showsHorizontalScrollIndicator: false,
1134
+ viewabilityConfig: PAGE_VIEWABILITY,
1135
+ onViewableItemsChanged: handleViewableItemsChanged,
1136
+ renderItem
1137
+ }, measured ? "grid" : "grid-seed")
1138
+ })
1139
+ ]
742
1140
  });
743
1141
  }
744
1142
  const TimeGrid = memo(TimeGridInner);
745
- const DefaultHeader = ({ days, mode, width, hourColumnWidth, locale }) => {
746
- const dayWidth = mode === "week" ? (width - hourColumnWidth) / days.length : width;
1143
+ const DefaultHeader = ({ days, mode, width, hourColumnWidth, showWeekNumber, weekNumberPrefix = "W", locale, activeDate, onPressDateHeader }) => {
1144
+ const theme = useCalendarTheme();
1145
+ const dayWidth = (width - hourColumnWidth) / days.length;
747
1146
  return /* @__PURE__ */ jsxs(View, {
748
1147
  style: styles.headerRow,
749
- children: [mode === "week" ? /* @__PURE__ */ jsx(View, { style: { width: hourColumnWidth } }) : null, days.map((day) => /* @__PURE__ */ jsx(DayHeader, {
1148
+ children: [/* @__PURE__ */ jsx(View, {
1149
+ style: [styles.weekNumberGutter, { width: hourColumnWidth }],
1150
+ children: showWeekNumber && hourColumnWidth > 0 && days[0] ? /* @__PURE__ */ jsx(Text, {
1151
+ style: [theme.text.hourLabel, { color: theme.colors.textMuted }],
1152
+ allowFontScaling: false,
1153
+ children: `${weekNumberPrefix}${getISOWeek(days[0])}`
1154
+ }) : null
1155
+ }), days.map((day) => /* @__PURE__ */ jsx(DayHeader, {
750
1156
  day,
751
1157
  mode,
752
1158
  width: dayWidth,
753
- locale
1159
+ locale,
1160
+ activeDate,
1161
+ onPressDateHeader
754
1162
  }, day.toISOString()))]
755
1163
  });
756
1164
  };
757
- const DayHeader = ({ day, mode, width, locale }) => {
1165
+ const DayHeader = ({ day, mode, width, locale, activeDate, onPressDateHeader }) => {
758
1166
  const theme = useCalendarTheme();
759
1167
  const isToday = getIsToday(day);
1168
+ const isHighlighted = activeDate ? isSameCalendarDay(day, activeDate) : isToday;
760
1169
  const badgeSize = mode === "day" ? 44 : 32;
761
- return /* @__PURE__ */ jsxs(View, {
1170
+ return /* @__PURE__ */ jsxs(Pressable, {
762
1171
  style: [styles.dayHeader, {
763
1172
  width,
764
1173
  gap: mode === "day" ? 4 : 2
765
1174
  }],
1175
+ onPress: onPressDateHeader ? () => onPressDateHeader(day) : void 0,
1176
+ disabled: !onPressDateHeader,
1177
+ accessibilityRole: onPressDateHeader ? "button" : void 0,
766
1178
  children: [/* @__PURE__ */ jsx(View, {
767
- style: [styles.dayHeaderBadge, isToday && {
1179
+ style: [styles.dayHeaderBadge, isHighlighted && {
768
1180
  backgroundColor: theme.colors.todayBackground,
769
1181
  borderRadius: 999,
770
1182
  width: badgeSize,
771
1183
  height: badgeSize
772
1184
  }],
773
1185
  children: /* @__PURE__ */ jsx(Text, {
774
- style: [theme.text.dayNumber, { color: isToday ? theme.colors.todayText : theme.colors.text }],
1186
+ style: [theme.text.dayNumber, { color: isHighlighted ? theme.colors.todayText : theme.colors.text }],
775
1187
  allowFontScaling: false,
776
1188
  ...isToday && { accessibilityLabel: `Today, ${day.getDate()}` },
777
1189
  children: day.getDate()
@@ -779,7 +1191,7 @@ const DayHeader = ({ day, mode, width, locale }) => {
779
1191
  }), /* @__PURE__ */ jsx(Text, {
780
1192
  style: [theme.text.weekday, { color: theme.colors.text }],
781
1193
  allowFontScaling: false,
782
- children: day.toLocaleDateString(locale, { weekday: "short" })
1194
+ children: format(day, "EEE", { locale })
783
1195
  })]
784
1196
  });
785
1197
  };
@@ -792,6 +1204,10 @@ const styles = StyleSheet.create({
792
1204
  alignItems: "center",
793
1205
  paddingBottom: 8
794
1206
  },
1207
+ weekNumberGutter: {
1208
+ alignItems: "center",
1209
+ justifyContent: "flex-end"
1210
+ },
795
1211
  dayHeader: { alignItems: "center" },
796
1212
  dayHeaderBadge: {
797
1213
  justifyContent: "center",
@@ -831,6 +1247,12 @@ const styles = StyleSheet.create({
831
1247
  flex: 1,
832
1248
  height: StyleSheet.hairlineWidth
833
1249
  },
1250
+ timeslotLine: {
1251
+ position: "absolute",
1252
+ right: 0,
1253
+ height: StyleSheet.hairlineWidth,
1254
+ opacity: .5
1255
+ },
834
1256
  eventBox: {
835
1257
  position: "absolute",
836
1258
  overflow: "hidden"
@@ -844,9 +1266,50 @@ const styles = StyleSheet.create({
844
1266
  //#endregion
845
1267
  //#region src/components/Calendar.tsx
846
1268
  const defaultKeyExtractor = (event) => `${event.start.toISOString()}|${event.end.toISOString()}|${event.title ?? ""}`;
847
- function Calendar({ events, mode, date, onChangeDate, onPressEvent, onPressDay, onPressMore, onPressCell, maxVisibleEventCount = 2, weekStartsOn = 0, renderEvent = DefaultEvent, keyExtractor = defaultKeyExtractor, theme, cellHeight: cellHeightProp, hourHeight = 64, minHourHeight, maxHourHeight, hourColumnWidth, minHour, maxHour, ampm, scrollOffsetMinutes, showNowIndicator, locale, freeSwipe, renderTimeGridHeader }) {
1269
+ function visibleRange(mode, date, weekStartsOn, numberOfDays, weekEndsOn) {
1270
+ if (mode === "month") return [startOfWeek(startOfMonth(date), { weekStartsOn }), endOfWeek(endOfMonth(date), { weekStartsOn })];
1271
+ const days = getViewDays(mode, date, weekStartsOn, numberOfDays, false, weekEndsOn);
1272
+ return [startOfDay(days[0]), endOfDay(days[days.length - 1])];
1273
+ }
1274
+ function Calendar({ events, mode, date, onChangeDate, onChangeDateRange, onPressEvent, onLongPressEvent, onPressDay, onLongPressDay, onPressMore, onPressCell, resetPageOnPressCell, onLongPressCell, onPressDateHeader, maxVisibleEventCount = 2, sortedMonthView, moreLabel, showAdjacentMonths, disableMonthEventCellPress, weekStartsOn = 0, numberOfDays, weekEndsOn, renderEvent = DefaultEvent, eventCellStyle, calendarCellStyle, keyExtractor = defaultKeyExtractor, theme, cellHeight: cellHeightProp, hourHeight = 64, minHourHeight, maxHourHeight, hourColumnWidth, hideHours, timeslots, showWeekNumber, weekNumberPrefix, hourComponent, showSixWeeks, swipeEnabled, showVerticalScrollIndicator, verticalScrollEnabled, headerComponent, minHour, maxHour, ampm, showTime, scrollOffsetMinutes, showNowIndicator, locale, activeDate, isRTL, freeSwipe, renderTimeGridHeader, renderHeaderForMonthView, itemSeparatorComponent }) {
848
1275
  const mergedTheme = useMemo(() => mergeTheme(theme), [theme]);
849
1276
  const internalCellHeight = useSharedValue(hourHeight);
1277
+ const cellHeight = cellHeightProp ?? internalCellHeight;
1278
+ const handlePressEvent = useCallback((event) => {
1279
+ if (!event.disabled) onPressEvent(event);
1280
+ }, [onPressEvent]);
1281
+ const handleLongPressEvent = useMemo(() => onLongPressEvent ? (event) => {
1282
+ if (!event.disabled) onLongPressEvent(event);
1283
+ } : void 0, [onLongPressEvent]);
1284
+ const handleChangeDate = useCallback((next) => {
1285
+ onChangeDate(next);
1286
+ onChangeDateRange?.(visibleRange(mode, next, weekStartsOn, numberOfDays ?? 1, weekEndsOn));
1287
+ }, [
1288
+ onChangeDate,
1289
+ onChangeDateRange,
1290
+ mode,
1291
+ weekStartsOn,
1292
+ numberOfDays,
1293
+ weekEndsOn
1294
+ ]);
1295
+ const resolvedRenderEvent = useMemo(() => {
1296
+ if (eventCellStyle == null && ampm == null && showTime == null) return renderEvent;
1297
+ const Base = renderEvent;
1298
+ return function StyledEvent(props) {
1299
+ const cellStyle = typeof eventCellStyle === "function" ? eventCellStyle(props.event) : eventCellStyle;
1300
+ return /* @__PURE__ */ jsx(Base, {
1301
+ ...props,
1302
+ cellStyle,
1303
+ ampm,
1304
+ showTime
1305
+ });
1306
+ };
1307
+ }, [
1308
+ renderEvent,
1309
+ eventCellStyle,
1310
+ ampm,
1311
+ showTime
1312
+ ]);
850
1313
  return /* @__PURE__ */ jsx(CalendarThemeProvider, {
851
1314
  value: mergedTheme,
852
1315
  children: mode === "month" ? /* @__PURE__ */ jsx(MonthPager, {
@@ -854,24 +1317,58 @@ function Calendar({ events, mode, date, onChangeDate, onPressEvent, onPressDay,
854
1317
  events,
855
1318
  maxVisibleEventCount,
856
1319
  weekStartsOn,
857
- renderEvent,
1320
+ locale,
1321
+ sortedMonthView,
1322
+ moreLabel,
1323
+ showAdjacentMonths,
1324
+ disableMonthEventCellPress,
1325
+ isRTL,
1326
+ showSixWeeks,
1327
+ activeDate,
1328
+ renderHeaderForMonthView,
1329
+ calendarCellStyle,
1330
+ renderEvent: resolvedRenderEvent,
858
1331
  keyExtractor,
859
1332
  onPressDay,
860
- onPressEvent,
1333
+ onLongPressDay,
1334
+ onPressEvent: handlePressEvent,
1335
+ onLongPressEvent: handleLongPressEvent,
861
1336
  onPressMore,
862
- onChangeDate,
863
- freeSwipe
1337
+ onChangeDate: handleChangeDate,
1338
+ freeSwipe,
1339
+ swipeEnabled
1340
+ }) : mode === "schedule" ? /* @__PURE__ */ jsx(Agenda, {
1341
+ events,
1342
+ locale,
1343
+ renderEvent: resolvedRenderEvent,
1344
+ keyExtractor,
1345
+ onPressEvent: handlePressEvent,
1346
+ onLongPressEvent: handleLongPressEvent,
1347
+ onPressDay,
1348
+ activeDate,
1349
+ itemSeparatorComponent
864
1350
  }) : /* @__PURE__ */ jsx(TimeGrid, {
865
1351
  mode,
1352
+ numberOfDays,
1353
+ weekEndsOn,
866
1354
  date,
867
1355
  events,
868
- cellHeight: cellHeightProp ?? internalCellHeight,
1356
+ cellHeight,
869
1357
  hourHeight,
870
1358
  weekStartsOn,
871
- renderEvent,
1359
+ renderEvent: resolvedRenderEvent,
872
1360
  keyExtractor,
873
1361
  scrollOffsetMinutes,
874
1362
  hourColumnWidth,
1363
+ hideHours,
1364
+ timeslots,
1365
+ calendarCellStyle,
1366
+ showWeekNumber,
1367
+ weekNumberPrefix,
1368
+ hourComponent,
1369
+ showVerticalScrollIndicator,
1370
+ verticalScrollEnabled,
1371
+ headerComponent,
875
1372
  minHour,
876
1373
  maxHour,
877
1374
  ampm,
@@ -879,13 +1376,20 @@ function Calendar({ events, mode, date, onChangeDate, onPressEvent, onPressDay,
879
1376
  maxHourHeight,
880
1377
  showNowIndicator,
881
1378
  locale,
1379
+ activeDate,
1380
+ isRTL,
882
1381
  freeSwipe,
883
- onPressEvent,
1382
+ swipeEnabled,
1383
+ onPressEvent: handlePressEvent,
1384
+ onLongPressEvent: handleLongPressEvent,
884
1385
  onPressCell,
885
- onChangeDate,
1386
+ resetPageOnPressCell,
1387
+ onLongPressCell,
1388
+ onPressDateHeader,
1389
+ onChangeDate: handleChangeDate,
886
1390
  renderHeader: renderTimeGridHeader
887
1391
  })
888
1392
  });
889
1393
  }
890
1394
  //#endregion
891
- export { Calendar, CalendarThemeProvider, DEFAULT_HOUR_HEIGHT, DefaultEvent, MonthPager, MonthView, TimeGrid, defaultTheme, getIsToday, getWeekDays, isSameCalendarDay, isWeekend, layoutDayEvents, mergeTheme, minutesIntoDay, useCalendarTheme };
1395
+ export { Agenda, Calendar, CalendarThemeProvider, DEFAULT_HOUR_HEIGHT, DefaultEvent, MonthPager, MonthView, TimeGrid, defaultTheme, getIsToday, getWeekDays, isAllDayEvent, isSameCalendarDay, isWeekend, layoutDayEvents, mergeTheme, minutesIntoDay, useCalendarTheme };