@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/README.md +21 -0
- package/dist/{DefaultMonthEvent-BW6qy50X.d.ts → DefaultMonthEvent-BSvuJUFk.d.ts} +174 -173
- package/dist/{DefaultMonthEvent-BbCMc60z.d.mts → DefaultMonthEvent-zDj9dHEv.d.mts} +174 -173
- package/dist/{MonthList-CFXm7BK_.js → MonthList-Aczb2RSY.js} +219 -108
- package/dist/{MonthList-BidXJMfm.mjs → MonthList-zPLklHAY.mjs} +203 -110
- package/dist/index.d.mts +247 -263
- package/dist/index.d.ts +247 -263
- package/dist/index.js +793 -259
- package/dist/index.mjs +786 -264
- package/dist/picker.d.mts +1 -1
- package/dist/picker.d.ts +1 -1
- package/dist/picker.js +1 -1
- package/dist/picker.mjs +1 -1
- package/package.json +2 -2
- package/src/components/Agenda.tsx +52 -11
- package/src/components/AllDayLane.tsx +22 -8
- package/src/components/Calendar.tsx +84 -5
- package/src/components/DefaultEvent.tsx +4 -0
- package/src/components/MonthList.tsx +52 -10
- package/src/components/MonthPager.tsx +45 -6
- package/src/components/MonthView.tsx +133 -74
- package/src/components/ResourceTimeline.tsx +581 -35
- package/src/components/TimeGrid.tsx +282 -162
- package/src/index.tsx +12 -4
- package/src/types.ts +16 -1
- package/src/utils/slots.ts +83 -0
package/dist/picker.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as
|
|
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
|
|
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-
|
|
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 {
|
|
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
|
+
"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.
|
|
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
|
-
|
|
70
|
-
styles.header,
|
|
71
|
-
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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
|
-
|
|
51
|
-
styles.lane,
|
|
52
|
-
{ borderBottomColor: theme.colors.gridLine },
|
|
53
|
-
|
|
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
|
|
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
|
-
|
|
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)}
|
|
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 {
|
|
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={
|
|
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={
|
|
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={
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
642
|
+
weekdayLabel: { flex: 1, textAlign: "center" },
|
|
643
|
+
weekdayLabelText: { fontSize: 12, fontWeight: "600" },
|
|
602
644
|
});
|