@super-calendar/native 2.3.2 → 2.5.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/picker.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { C as CalendarThemeProvider, D as mergeTheme, E as defaultTheme, O as useCalendarTheme, S as CalendarTheme, T as darkTheme, _ as RenderEvent, a as MonthPagerProps, b as WeekStartsOn, i as MonthPager, l as CalendarEvent, m as ICalendarEvent, n as MonthList, o as MonthView, p as EventKeyExtractor, r as MonthListProps, s as MonthViewProps, t as DefaultMonthEvent, u as CalendarMode, v as RenderEventArgs, w as PartialCalendarTheme } from "./DefaultMonthEvent-BbCMc60z.mjs";
1
+ import { A as darkTheme, C as RenderEventArgs, D as CalendarTheme, M as mergeTheme, N as useCalendarTheme, O as CalendarThemeProvider, S as RenderEvent, T as WeekStartsOn, a as MonthPager, c as MonthViewProps, h as CalendarMode, j as defaultTheme, k as PartialCalendarTheme, m as CalendarEvent, n as MonthList, o as MonthPagerProps, r as MonthListProps, s as MonthView, t as DefaultMonthEvent, v as EventKeyExtractor, y as ICalendarEvent } from "./DefaultMonthEvent-zDj9dHEv.mjs";
2
2
  import { DateRange, DateSelectionConstraints, DaySelectionState, MonthGrid, MonthGridDay, MonthGridWeek, MonthGridWeekday, UseDateRangeOptions, UseMonthGridOptions, buildMonthGrid, buildMonthWeeks, daySelectionState, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, minutesIntoDay, nextDateRange, useDateRange, useMonthGrid } from "@super-calendar/core";
3
3
  export { type CalendarEvent, type CalendarMode, type CalendarTheme, CalendarThemeProvider, type DateRange, type DateSelectionConstraints, type DaySelectionState, DefaultMonthEvent, type EventKeyExtractor, type ICalendarEvent, type MonthGrid, type MonthGridDay, type MonthGridWeek, type MonthGridWeekday, MonthList, type MonthListProps, MonthPager, type MonthPagerProps, MonthView, type MonthViewProps, type PartialCalendarTheme, type RenderEvent, type RenderEventArgs, type UseDateRangeOptions, type UseMonthGridOptions, type WeekStartsOn, buildMonthGrid, buildMonthWeeks, darkTheme, daySelectionState, defaultTheme, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, mergeTheme, minutesIntoDay, nextDateRange, useCalendarTheme, useDateRange, useMonthGrid };
package/dist/picker.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { C as CalendarThemeProvider, D as mergeTheme, E as defaultTheme, O as useCalendarTheme, S as CalendarTheme, T as darkTheme, _ as RenderEvent, a as MonthPagerProps, b as WeekStartsOn, i as MonthPager, l as CalendarEvent, m as ICalendarEvent, n as MonthList, o as MonthView, p as EventKeyExtractor, r as MonthListProps, s as MonthViewProps, t as DefaultMonthEvent, u as CalendarMode, v as RenderEventArgs, w as PartialCalendarTheme } from "./DefaultMonthEvent-BW6qy50X.js";
1
+ import { A as darkTheme, C as RenderEventArgs, D as CalendarTheme, M as mergeTheme, N as useCalendarTheme, O as CalendarThemeProvider, S as RenderEvent, T as WeekStartsOn, a as MonthPager, c as MonthViewProps, h as CalendarMode, j as defaultTheme, k as PartialCalendarTheme, m as CalendarEvent, n as MonthList, o as MonthPagerProps, r as MonthListProps, s as MonthView, t as DefaultMonthEvent, v as EventKeyExtractor, y as ICalendarEvent } from "./DefaultMonthEvent-BSvuJUFk.js";
2
2
  import { DateRange, DateSelectionConstraints, DaySelectionState, MonthGrid, MonthGridDay, MonthGridWeek, MonthGridWeekday, UseDateRangeOptions, UseMonthGridOptions, buildMonthGrid, buildMonthWeeks, daySelectionState, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, minutesIntoDay, nextDateRange, useDateRange, useMonthGrid } from "@super-calendar/core";
3
3
  export { type CalendarEvent, type CalendarMode, type CalendarTheme, CalendarThemeProvider, type DateRange, type DateSelectionConstraints, type DaySelectionState, DefaultMonthEvent, type EventKeyExtractor, type ICalendarEvent, type MonthGrid, type MonthGridDay, type MonthGridWeek, type MonthGridWeekday, MonthList, type MonthListProps, MonthPager, type MonthPagerProps, MonthView, type MonthViewProps, type PartialCalendarTheme, type RenderEvent, type RenderEventArgs, type UseDateRangeOptions, type UseMonthGridOptions, type WeekStartsOn, buildMonthGrid, buildMonthWeeks, darkTheme, daySelectionState, defaultTheme, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, mergeTheme, minutesIntoDay, nextDateRange, useCalendarTheme, useDateRange, useMonthGrid };
package/dist/picker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_MonthList = require("./MonthList-CFXm7BK_.js");
2
+ const require_MonthList = require("./MonthList-Aczb2RSY.js");
3
3
  let _super_calendar_core = require("@super-calendar/core");
4
4
  exports.CalendarThemeProvider = require_MonthList.CalendarThemeProvider;
5
5
  exports.DefaultMonthEvent = require_MonthList.DefaultMonthEvent;
package/dist/picker.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { c as darkTheme, d as useCalendarTheme, i as MonthView, l as defaultTheme, n as DefaultMonthEvent, r as MonthPager, s as CalendarThemeProvider, t as MonthList, u as mergeTheme } from "./MonthList-BidXJMfm.mjs";
1
+ import { d as darkTheme, f as defaultTheme, i as MonthView, m as useCalendarTheme, n as DefaultMonthEvent, p as mergeTheme, r as MonthPager, t as MonthList, u as CalendarThemeProvider } from "./MonthList-zPLklHAY.mjs";
2
2
  import { buildMonthGrid, buildMonthWeeks, daySelectionState, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, minutesIntoDay, nextDateRange, useDateRange, useMonthGrid } from "@super-calendar/core";
3
3
  export { CalendarThemeProvider, DefaultMonthEvent, MonthList, MonthPager, MonthView, buildMonthGrid, buildMonthWeeks, darkTheme, daySelectionState, defaultTheme, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, mergeTheme, minutesIntoDay, nextDateRange, useCalendarTheme, useDateRange, useMonthGrid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super-calendar/native",
3
- "version": "2.3.2",
3
+ "version": "2.5.0",
4
4
  "description": "Gesture-driven, virtualized month / week / day calendar and date picker for React Native (and web via react-native-web).",
5
5
  "keywords": [
6
6
  "agenda",
@@ -64,7 +64,7 @@
64
64
  "access": "public"
65
65
  },
66
66
  "dependencies": {
67
- "@super-calendar/core": "2.3.2"
67
+ "@super-calendar/core": "2.5.0"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@legendapp/list": ">=3",
@@ -4,11 +4,18 @@ import { type ComponentType, type ReactElement, useCallback, useMemo } from "rea
4
4
  import { StyleSheet, Text, View } from "react-native";
5
5
  import { useCalendarTheme } from "../theme";
6
6
  import type { CalendarEvent, EventKeyExtractor, RenderEvent } from "../types";
7
+ import { createSlots, type SlotStyleProps } from "../utils/slots";
7
8
  import { getIsToday } from "@super-calendar/core";
8
9
  import { isAllDayEvent } from "@super-calendar/core";
9
10
 
11
+ /**
12
+ * The styleable parts of {@link Agenda}. Mirrors the dom renderer's slot names;
13
+ * the event itself is styled by `renderEvent` (or the theme), not a slot.
14
+ */
15
+ export type AgendaSlot = "dayHeader" | "eventRow" | "empty";
16
+
10
17
  /** Props for {@link Agenda}, the day-grouped list view of events. */
11
- export type AgendaProps<T> = {
18
+ export type AgendaProps<T> = SlotStyleProps<AgendaSlot> & {
12
19
  events: CalendarEvent<T>[];
13
20
  locale?: Locale;
14
21
  renderEvent: RenderEvent<T>;
@@ -41,8 +48,15 @@ export function Agenda<T>({
41
48
  onPressDay,
42
49
  activeDate,
43
50
  itemSeparatorComponent,
51
+ classNames,
52
+ styles: styleOverrides,
44
53
  }: AgendaProps<T>): ReactElement {
45
54
  const theme = useCalendarTheme();
55
+ // Stable across renders (it feeds the memoized renderItem below).
56
+ const slot = useMemo(
57
+ () => createSlots<AgendaSlot>({ classNames, styles: styleOverrides }),
58
+ [classNames, styleOverrides],
59
+ );
46
60
  const RenderEventComponent = renderEvent;
47
61
 
48
62
  const rows = useMemo<Row<T>[]>(() => {
@@ -66,10 +80,13 @@ export function Agenda<T>({
66
80
  const isHighlighted = activeDate ? isSameDay(item.date, activeDate) : getIsToday(item.date);
67
81
  return (
68
82
  <Text
69
- style={[
70
- styles.header,
71
- { color: isHighlighted ? theme.colors.todayBackground : theme.colors.textMuted },
72
- ]}
83
+ {...slot("dayHeader", {
84
+ base: styles.header,
85
+ themed: [
86
+ styles.headerText,
87
+ { color: isHighlighted ? theme.colors.todayBackground : theme.colors.textMuted },
88
+ ],
89
+ })}
73
90
  onPress={onPressDay ? () => onPressDay(item.date) : undefined}
74
91
  accessibilityRole={onPressDay ? "button" : "header"}
75
92
  >
@@ -78,7 +95,7 @@ export function Agenda<T>({
78
95
  );
79
96
  }
80
97
  return (
81
- <View style={[styles.eventRow, theme.containers.agendaRow]}>
98
+ <View {...slot("eventRow", { base: styles.eventRow, themed: theme.containers.agendaRow })}>
82
99
  <RenderEventComponent
83
100
  event={item.event}
84
101
  mode="schedule"
@@ -89,11 +106,29 @@ export function Agenda<T>({
89
106
  </View>
90
107
  );
91
108
  },
92
- [theme, locale, activeDate, onPressDay, onPressEvent, onLongPressEvent, RenderEventComponent],
109
+ [
110
+ theme,
111
+ locale,
112
+ activeDate,
113
+ onPressDay,
114
+ onPressEvent,
115
+ onLongPressEvent,
116
+ RenderEventComponent,
117
+ slot,
118
+ ],
93
119
  );
94
120
 
95
121
  if (rows.length === 0) {
96
- return <Text style={[styles.empty, { color: theme.colors.textMuted }]}>No events</Text>;
122
+ return (
123
+ <Text
124
+ {...slot("empty", {
125
+ base: styles.empty,
126
+ themed: [styles.emptyText, { color: theme.colors.textMuted }],
127
+ })}
128
+ >
129
+ No events
130
+ </Text>
131
+ );
97
132
  }
98
133
 
99
134
  return (
@@ -115,20 +150,26 @@ const styles = StyleSheet.create({
115
150
  list: {
116
151
  flex: 1,
117
152
  },
153
+ // Structural layout / themed typography split per slot, so a slot class can
154
+ // replace the look without breaking the layout.
118
155
  header: {
119
- fontSize: 13,
120
- fontWeight: "600",
121
156
  paddingTop: 12,
122
157
  paddingBottom: 4,
123
158
  paddingHorizontal: 12,
124
159
  },
160
+ headerText: {
161
+ fontSize: 13,
162
+ fontWeight: "600",
163
+ },
125
164
  eventRow: {
126
165
  paddingHorizontal: 12,
127
166
  paddingVertical: 2,
128
167
  },
129
168
  empty: {
130
- fontSize: 14,
131
169
  paddingVertical: 16,
132
170
  paddingHorizontal: 12,
133
171
  },
172
+ emptyText: {
173
+ fontSize: 14,
174
+ },
134
175
  });
@@ -2,8 +2,13 @@ import { addDays, startOfDay } from "date-fns";
2
2
  import { StyleSheet, Text, View } from "react-native";
3
3
  import { useCalendarTheme } from "../theme";
4
4
  import type { CalendarEvent, CalendarMode, EventKeyExtractor, RenderEvent } from "../types";
5
+ import { useSlots } from "../utils/slots";
5
6
  import { isAllDayEvent } from "@super-calendar/core";
6
7
 
8
+ // The lane's slots are a subset of the TimeGrid slot union; typed locally so
9
+ // this file doesn't import the full TimeGrid type.
10
+ type AllDayLaneSlot = "allDayLane" | "allDayLabel" | "allDayColumn" | "allDayEvent";
11
+
7
12
  type AllDayLaneProps<T> = {
8
13
  days: Date[];
9
14
  events: CalendarEvent<T>[];
@@ -34,6 +39,7 @@ export function AllDayLane<T>({
34
39
  onLongPressEvent,
35
40
  }: AllDayLaneProps<T>) {
36
41
  const theme = useCalendarTheme();
42
+ const slot = useSlots<AllDayLaneSlot>();
37
43
  const RenderEventComponent = renderEvent;
38
44
 
39
45
  const allDay = events.filter(isAllDayEvent);
@@ -47,27 +53,35 @@ export function AllDayLane<T>({
47
53
 
48
54
  return (
49
55
  <View
50
- style={[
51
- styles.lane,
52
- { borderBottomColor: theme.colors.gridLine },
53
- theme.containers.allDayLane,
54
- ]}
56
+ {...slot("allDayLane", {
57
+ base: styles.lane,
58
+ themed: [{ borderBottomColor: theme.colors.gridLine }, theme.containers.allDayLane],
59
+ })}
55
60
  >
56
61
  <View style={[styles.gutter, { width: hourColumnWidth }]}>
57
62
  {/* The "all-day" gutter label, mirroring the dom renderer: small, muted,
58
63
  and right-aligned against the timed columns. Centered vertically so it
59
64
  lines up with the chips whether the lane holds one event or several. */}
60
- <Text style={[styles.label, { color: theme.colors.textMuted }]} allowFontScaling={false}>
65
+ <Text
66
+ {...slot("allDayLabel", {
67
+ base: styles.label,
68
+ themed: { color: theme.colors.textMuted },
69
+ })}
70
+ allowFontScaling={false}
71
+ >
61
72
  all-day
62
73
  </Text>
63
74
  </View>
64
75
  {days.map((day, dayIndex) => (
65
76
  <View
66
77
  key={day.toISOString()}
67
- style={[styles.column, { width: dayWidth }, theme.containers.allDayColumn]}
78
+ {...slot("allDayColumn", {
79
+ base: [styles.column, { width: dayWidth }],
80
+ themed: theme.containers.allDayColumn,
81
+ })}
68
82
  >
69
83
  {perDay[dayIndex].map((event, index) => (
70
- <View key={keyExtractor(event, index)} style={styles.chip}>
84
+ <View key={keyExtractor(event, index)} {...slot("allDayEvent", { base: styles.chip })}>
71
85
  <RenderEventComponent
72
86
  event={event}
73
87
  mode={mode}
@@ -1,4 +1,7 @@
1
1
  import {
2
+ addDays,
3
+ addMonths,
4
+ addWeeks,
2
5
  endOfDay,
3
6
  endOfMonth,
4
7
  endOfWeek,
@@ -22,22 +25,34 @@ import type {
22
25
  } from "../types";
23
26
  import {
24
27
  type EventAccessibilityLabeler,
28
+ eventsInTimeZone,
29
+ expandRecurringEvents,
25
30
  type WeekdayFormat,
26
31
  getViewDays,
27
32
  } from "@super-calendar/core";
28
- import { Agenda } from "./Agenda";
33
+ import { type SlotStyleProps } from "../utils/slots";
34
+ import { Agenda, type AgendaSlot } from "./Agenda";
29
35
  import { DefaultEvent } from "./DefaultEvent";
30
36
  import { MonthPager } from "./MonthPager";
37
+ import type { MonthViewSlot } from "./MonthView";
31
38
  import {
32
39
  DEFAULT_HOUR_HEIGHT,
33
40
  type EventDragHandler,
34
41
  type EventDragStartHandler,
35
42
  type HourRenderer,
36
43
  TimeGrid,
44
+ type TimeGridSlot,
37
45
  } from "./TimeGrid";
38
46
 
47
+ /**
48
+ * Every styleable slot across the Calendar's views. The month slots reach the
49
+ * month pager, the grid slots reach the time grid, and the agenda slots reach
50
+ * the schedule view; slots for views that aren't rendered are ignored.
51
+ */
52
+ export type CalendarSlot = MonthViewSlot | TimeGridSlot | AgendaSlot;
53
+
39
54
  /** Props for the {@link Calendar} component. */
40
- export type CalendarProps<T> = {
55
+ export type CalendarProps<T> = SlotStyleProps<CalendarSlot> & {
41
56
  events: CalendarEvent<T>[];
42
57
  mode: CalendarMode;
43
58
  date: Date;
@@ -191,6 +206,12 @@ export type CalendarProps<T> = {
191
206
  showNowIndicator?: boolean;
192
207
  /** A date-fns `Locale` for weekday/date labels. Defaults to English. */
193
208
  locale?: Locale;
209
+ /**
210
+ * Display events in this IANA time zone (e.g. `"America/New_York"`), DST-correct
211
+ * and independent of the device zone. Display-only: it shifts the wall-clock the
212
+ * grid lays out from, it doesn't change your `Date`s. Uses `eventsInTimeZone`.
213
+ */
214
+ timeZone?: string;
194
215
  /** Highlight this date (header/cell/agenda) instead of the real "today". */
195
216
  activeDate?: Date;
196
217
  /**
@@ -244,6 +265,34 @@ function visibleRange(
244
265
  return [startOfDay(days[0]), endOfDay(days[days.length - 1])];
245
266
  }
246
267
 
268
+ // The range to materialise recurring occurrences over. Wider than `visibleRange`
269
+ // by one page on each side, because the pager pre-renders the neighbour pages;
270
+ // without the pad, recurring events would blink out for a page mid-swipe.
271
+ function expansionRange(
272
+ mode: CalendarMode,
273
+ date: Date,
274
+ weekStartsOn: WeekStartsOn,
275
+ numberOfDays: number,
276
+ weekEndsOn?: WeekStartsOn,
277
+ ): [Date, Date] {
278
+ if (mode === "month") {
279
+ return [
280
+ startOfWeek(startOfMonth(addMonths(date, -1)), { weekStartsOn }),
281
+ // The extra week covers a `showSixWeeks` neighbour page whose own month
282
+ // fits in five weeks but is padded to six (the 6th row trails the month).
283
+ addWeeks(endOfWeek(endOfMonth(addMonths(date, 1)), { weekStartsOn }), 1),
284
+ ];
285
+ }
286
+ if (mode === "schedule") {
287
+ // The agenda lists forward from the anchor date; three months is a sensible
288
+ // default look-ahead. Pre-expand for a different window.
289
+ return [startOfDay(date), endOfDay(addMonths(date, 3))];
290
+ }
291
+ const days = getViewDays(mode, date, weekStartsOn, numberOfDays, false, weekEndsOn);
292
+ const span = days.length;
293
+ return [startOfDay(addDays(days[0], -span)), endOfDay(addDays(days[days.length - 1], span))];
294
+ }
295
+
247
296
  /**
248
297
  * The top-level calendar. Switches between month, week, day, 3days, custom, and
249
298
  * schedule modes, and is gesture-driven and virtualized. It is a controlled
@@ -331,6 +380,7 @@ export function Calendar<T>({
331
380
  scrollOffsetMinutes,
332
381
  showNowIndicator,
333
382
  locale,
383
+ timeZone,
334
384
  activeDate,
335
385
  isRTL,
336
386
  freeSwipe,
@@ -338,11 +388,34 @@ export function Calendar<T>({
338
388
  renderHeaderForMonthView,
339
389
  renderCustomDateForMonth,
340
390
  itemSeparatorComponent,
391
+ classNames,
392
+ styles: styleOverrides,
341
393
  }: CalendarProps<T>): ReactElement {
342
394
  const mergedTheme = useMemo(() => mergeTheme(theme), [theme]);
343
395
  const internalCellHeight = useSharedValue(hourHeight);
344
396
  const cellHeight = cellHeightProp ?? internalCellHeight;
345
397
 
398
+ // Materialise recurring events over the range this mode renders (plus a one-page
399
+ // buffer so occurrences survive the pager's pre-rendered neighbours), then apply
400
+ // the display zone. Non-recurring and already-expanded events pass through, so
401
+ // identity is preserved for the common (no-recurrence) case.
402
+ const displayEvents = useMemo(() => {
403
+ let out = events;
404
+ if (out.some((e) => e.recurrence)) {
405
+ let [start, end] = expansionRange(mode, date, weekStartsOn, numberOfDays ?? 1, weekEndsOn);
406
+ // A display zone shifts each occurrence's wall-clock by up to ~a day, which
407
+ // can move an edge occurrence into a visible column; widen so it's generated.
408
+ if (timeZone) {
409
+ start = addDays(start, -1);
410
+ end = addDays(end, 1);
411
+ }
412
+ out = expandRecurringEvents(out, start, end);
413
+ }
414
+ if (timeZone) out = eventsInTimeZone(out, timeZone);
415
+ return out;
416
+ // `date.getTime()`: recompute on the instant, not a re-created Date identity.
417
+ }, [events, mode, date.getTime(), weekStartsOn, numberOfDays, weekEndsOn, timeZone]);
418
+
346
419
  // Swallow presses on disabled events once, so every view inherits the guard.
347
420
  const handlePressEvent = useCallback(
348
421
  (event: CalendarEvent<T>) => {
@@ -422,7 +495,7 @@ export function Calendar<T>({
422
495
  {mode === "month" ? (
423
496
  <MonthPager
424
497
  date={date}
425
- events={events}
498
+ events={displayEvents}
426
499
  maxVisibleEventCount={maxVisibleEventCount}
427
500
  weekStartsOn={weekStartsOn}
428
501
  weekdayFormat={weekdayFormat}
@@ -447,10 +520,12 @@ export function Calendar<T>({
447
520
  onChangeDate={handleChangeDate}
448
521
  freeSwipe={freeSwipe}
449
522
  swipeEnabled={swipeEnabled}
523
+ classNames={classNames}
524
+ styles={styleOverrides}
450
525
  />
451
526
  ) : mode === "schedule" ? (
452
527
  <Agenda
453
- events={events}
528
+ events={displayEvents}
454
529
  locale={locale}
455
530
  renderEvent={resolvedRenderEvent}
456
531
  keyExtractor={keyExtractor}
@@ -459,6 +534,8 @@ export function Calendar<T>({
459
534
  onPressDay={onPressDay}
460
535
  activeDate={activeDate}
461
536
  itemSeparatorComponent={itemSeparatorComponent}
537
+ classNames={classNames}
538
+ styles={styleOverrides}
462
539
  />
463
540
  ) : (
464
541
  <TimeGrid
@@ -466,7 +543,7 @@ export function Calendar<T>({
466
543
  numberOfDays={numberOfDays}
467
544
  weekEndsOn={weekEndsOn}
468
545
  date={date}
469
- events={events}
546
+ events={displayEvents}
470
547
  cellHeight={cellHeight}
471
548
  hourHeight={hourHeight}
472
549
  weekStartsOn={weekStartsOn}
@@ -510,6 +587,8 @@ export function Calendar<T>({
510
587
  onPressDateHeader={onPressDateHeader}
511
588
  onChangeDate={handleChangeDate}
512
589
  renderHeader={renderTimeGridHeader}
590
+ classNames={classNames}
591
+ styles={styleOverrides}
513
592
  />
514
593
  )}
515
594
  </CalendarThemeProvider>
@@ -39,6 +39,8 @@ export function DefaultEvent<T>({
39
39
  ellipsizeTitle = false,
40
40
  allDayLabel,
41
41
  accessibilityLabel: accessibilityLabelProp,
42
+ accessibilityActions,
43
+ onAccessibilityAction,
42
44
  cellStyle,
43
45
  onPress,
44
46
  onLongPress,
@@ -134,6 +136,8 @@ export function DefaultEvent<T>({
134
136
  accessibilityRole="button"
135
137
  accessibilityLabel={accessibilityLabel}
136
138
  accessibilityState={{ disabled: event.disabled ?? false }}
139
+ accessibilityActions={accessibilityActions}
140
+ onAccessibilityAction={onAccessibilityAction}
137
141
  >
138
142
  <Animated.View testID="event-chip-content" style={[styles.content, contentStyle]}>
139
143
  {event.title ? (
@@ -36,8 +36,12 @@ import {
36
36
  isDateSelectable,
37
37
  } from "@super-calendar/core";
38
38
  import { buildMonthWeeks, getWeekDays, weekdayFormatToken } from "@super-calendar/core";
39
+ import { createSlots, type SlotStyleProps } from "../utils/slots";
39
40
  import { DefaultMonthEvent } from "./DefaultMonthEvent";
40
- import { MonthView } from "./MonthView";
41
+ import { MonthView, type MonthViewSlot } from "./MonthView";
42
+
43
+ /** The styleable parts of {@link MonthList}: the same slots as {@link MonthView}. */
44
+ export type MonthListSlot = MonthViewSlot;
41
45
 
42
46
  const isWeb = Platform.OS === "web";
43
47
 
@@ -133,7 +137,7 @@ export type MonthListProps<T> = {
133
137
  onChangeVisibleMonth?: (month: Date) => void;
134
138
  /** Replace the per-month title (default "LLLL yyyy"). */
135
139
  renderMonthHeader?: (month: Date) => React.ReactNode;
136
- };
140
+ } & SlotStyleProps<MonthListSlot>;
137
141
 
138
142
  function MonthListInner<T>({
139
143
  date,
@@ -168,8 +172,15 @@ function MonthListInner<T>({
168
172
  onSelectDrag,
169
173
  onChangeVisibleMonth,
170
174
  renderMonthHeader,
175
+ classNames,
176
+ styles: styleOverrides,
171
177
  }: MonthListProps<T>): ReactElement {
172
178
  const theme = useCalendarTheme();
179
+ // Stable across renders (it feeds the memoized renderItem below).
180
+ const slot = useMemo(
181
+ () => createSlots<MonthListSlot>({ classNames, styles: styleOverrides }),
182
+ [classNames, styleOverrides],
183
+ );
173
184
  const listRef = useRef<LegendListRef>(null);
174
185
 
175
186
  // Compact rows for the picker; taller rows once events are shown, so a day cell
@@ -183,6 +194,17 @@ function MonthListInner<T>({
183
194
  // land on an off-screen month and scroll the list backwards.
184
195
  const [firstViewableIndex, setFirstViewableIndex] = useState(0);
185
196
 
197
+ // Months are keyed by dates that never change, so LegendList keeps mounted
198
+ // rows and only re-renders them when `data` or `extraData` changes. Feed
199
+ // `events` and `activeDate` (renderItem captures them; without this, events
200
+ // that arrive after mount never paint, same bug as the MonthPager had) plus
201
+ // `firstViewableIndex` (the web `inert` cutoff must stay current). Selection
202
+ // needs no entry: it reaches MonthView through context.
203
+ const listExtraData = useMemo(
204
+ () => ({ events, activeDate, firstViewableIndex }),
205
+ [events, activeDate, firstViewableIndex],
206
+ );
207
+
186
208
  // A fixed window of months anchored once, aligned to the month start.
187
209
  const [anchorDate] = useState(date);
188
210
  const anchor = useMemo(() => startOfMonth(anchorDate), [anchorDate]);
@@ -414,7 +436,12 @@ function MonthListInner<T>({
414
436
  {renderMonthHeader ? (
415
437
  renderMonthHeader(item)
416
438
  ) : (
417
- <Text style={[styles.monthTitle, { color: theme.colors.text }]}>
439
+ <Text
440
+ {...slot("title", {
441
+ base: styles.monthTitle,
442
+ themed: [styles.monthTitleText, { color: theme.colors.text }],
443
+ })}
444
+ >
418
445
  {format(item, "LLLL yyyy", { locale })}
419
446
  </Text>
420
447
  )}
@@ -451,6 +478,8 @@ function MonthListInner<T>({
451
478
  onPressMore={onPressMore}
452
479
  onDayPointerDown={isWeb && dragEnabled ? onDayPointerDown : undefined}
453
480
  onDayPointerEnter={isWeb && dragEnabled ? onDayPointerEnter : undefined}
481
+ classNames={classNames}
482
+ styles={styleOverrides}
454
483
  />
455
484
  </View>
456
485
  </MonthBlock>
@@ -484,6 +513,9 @@ function MonthListInner<T>({
484
513
  onPressMore,
485
514
  onDayPointerDown,
486
515
  onDayPointerEnter,
516
+ slot,
517
+ classNames,
518
+ styleOverrides,
487
519
  ],
488
520
  );
489
521
 
@@ -493,9 +525,7 @@ function MonthListInner<T>({
493
525
  style={styles.list}
494
526
  data={monthDates}
495
527
  recycleItems={false}
496
- // Re-render mounted months when the viewport cutoff changes, so the inert
497
- // flag on above-viewport months (web focus containment) stays current.
498
- extraData={firstViewableIndex}
528
+ extraData={listExtraData}
499
529
  keyExtractor={keyExtractorList}
500
530
  getFixedItemSize={getFixedItemSize}
501
531
  initialScrollIndex={initialIndex}
@@ -517,11 +547,19 @@ function MonthListInner<T>({
517
547
  return (
518
548
  <CalendarSelectionProvider value={selection}>
519
549
  <View style={styles.container}>
520
- <View style={[styles.weekdayHeader, { borderBottomColor: theme.colors.gridLine }]}>
550
+ <View
551
+ {...slot("weekdays", {
552
+ base: styles.weekdayHeader,
553
+ themed: { borderBottomColor: theme.colors.gridLine },
554
+ })}
555
+ >
521
556
  {weekDays.map((day) => (
522
557
  <Text
523
558
  key={day.toISOString()}
524
- style={[styles.weekdayLabel, { color: theme.colors.textMuted }]}
559
+ {...slot("weekday", {
560
+ base: styles.weekdayLabel,
561
+ themed: [styles.weekdayLabelText, { color: theme.colors.textMuted }],
562
+ })}
525
563
  allowFontScaling={false}
526
564
  >
527
565
  {format(day, weekdayFormatToken(weekdayFormat), { locale })}
@@ -591,12 +629,16 @@ const styles = StyleSheet.create({
591
629
  container: { flex: 1 },
592
630
  list: { flex: 1 },
593
631
  monthHeader: { justifyContent: "center" },
594
- monthTitle: { paddingHorizontal: 8, fontSize: 17, fontWeight: "700" },
632
+ // Structural layout / themed typography split per slot, so a slot class can
633
+ // replace the look without breaking the layout.
634
+ monthTitle: { paddingHorizontal: 8 },
635
+ monthTitleText: { fontSize: 17, fontWeight: "700" },
595
636
  grid: { flex: 1 },
596
637
  weekdayHeader: {
597
638
  flexDirection: "row",
598
639
  paddingVertical: 8,
599
640
  borderBottomWidth: StyleSheet.hairlineWidth,
600
641
  },
601
- weekdayLabel: { flex: 1, textAlign: "center", fontSize: 12, fontWeight: "600" },
642
+ weekdayLabel: { flex: 1, textAlign: "center" },
643
+ weekdayLabelText: { fontSize: 12, fontWeight: "600" },
602
644
  });