react-native-bigger-calendar 0.1.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 +182 -0
- package/dist/index.d.mts +302 -0
- package/dist/index.d.ts +302 -0
- package/dist/index.js +930 -0
- package/dist/index.mjs +891 -0
- package/package.json +75 -0
- package/src/components/Calendar.tsx +147 -0
- package/src/components/DefaultEvent.tsx +57 -0
- package/src/components/MonthPager.tsx +165 -0
- package/src/components/MonthView.tsx +178 -0
- package/src/components/TimeGrid.tsx +825 -0
- package/src/index.tsx +30 -0
- package/src/theme.ts +94 -0
- package/src/types.ts +56 -0
- package/src/utils/dates.ts +20 -0
- package/src/utils/layout.ts +119 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let react = require("react");
|
|
25
|
+
let react_native_reanimated = require("react-native-reanimated");
|
|
26
|
+
react_native_reanimated = __toESM(react_native_reanimated);
|
|
27
|
+
let date_fns = require("date-fns");
|
|
28
|
+
let react_native = require("react-native");
|
|
29
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
let _legendapp_list_react_native = require("@legendapp/list/react-native");
|
|
31
|
+
let react_native_gesture_handler = require("react-native-gesture-handler");
|
|
32
|
+
//#region src/theme.ts
|
|
33
|
+
const defaultTheme = {
|
|
34
|
+
colors: {
|
|
35
|
+
gridLine: "#E2E4E9",
|
|
36
|
+
weekendBackground: "#F6F7F9",
|
|
37
|
+
todayBackground: "#1F6FEB",
|
|
38
|
+
todayText: "#FFFFFF",
|
|
39
|
+
nowIndicator: "#E5484D",
|
|
40
|
+
text: "#1A1B1E",
|
|
41
|
+
textMuted: "#6B7280",
|
|
42
|
+
textDisabled: "#B5B9C0",
|
|
43
|
+
eventBackground: "#DCE7FF",
|
|
44
|
+
eventText: "#1A1B1E"
|
|
45
|
+
},
|
|
46
|
+
text: {
|
|
47
|
+
dayNumber: {
|
|
48
|
+
fontSize: 22,
|
|
49
|
+
fontWeight: "700"
|
|
50
|
+
},
|
|
51
|
+
weekday: {
|
|
52
|
+
fontSize: 13,
|
|
53
|
+
fontWeight: "700"
|
|
54
|
+
},
|
|
55
|
+
dateCell: {
|
|
56
|
+
fontSize: 13,
|
|
57
|
+
fontWeight: "700"
|
|
58
|
+
},
|
|
59
|
+
hourLabel: { fontSize: 12 },
|
|
60
|
+
more: {
|
|
61
|
+
fontSize: 11,
|
|
62
|
+
fontWeight: "700"
|
|
63
|
+
},
|
|
64
|
+
eventTitle: {
|
|
65
|
+
fontSize: 12,
|
|
66
|
+
fontWeight: "700"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
todayBadgeRadius: 999
|
|
70
|
+
};
|
|
71
|
+
/** Deep-merge a partial theme over {@link defaultTheme}. */
|
|
72
|
+
function mergeTheme(theme) {
|
|
73
|
+
if (!theme) return defaultTheme;
|
|
74
|
+
return {
|
|
75
|
+
colors: {
|
|
76
|
+
...defaultTheme.colors,
|
|
77
|
+
...theme.colors
|
|
78
|
+
},
|
|
79
|
+
text: {
|
|
80
|
+
...defaultTheme.text,
|
|
81
|
+
...theme.text
|
|
82
|
+
},
|
|
83
|
+
todayBadgeRadius: theme.todayBadgeRadius ?? defaultTheme.todayBadgeRadius
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const CalendarThemeContext = (0, react.createContext)(defaultTheme);
|
|
87
|
+
const CalendarThemeProvider = CalendarThemeContext.Provider;
|
|
88
|
+
const useCalendarTheme = () => (0, react.useContext)(CalendarThemeContext);
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/components/DefaultEvent.tsx
|
|
91
|
+
/**
|
|
92
|
+
* The built-in event renderer: a filled, rounded box showing the event title
|
|
93
|
+
* and (on the day/week grid) its time range. Pass your own `renderEvent` to
|
|
94
|
+
* `<Calendar>` to replace it entirely.
|
|
95
|
+
*/
|
|
96
|
+
function DefaultEvent({ event, mode, onPress }) {
|
|
97
|
+
const theme = useCalendarTheme();
|
|
98
|
+
const showTime = mode !== "month";
|
|
99
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native.TouchableOpacity, {
|
|
100
|
+
style: [styles$3.box, { backgroundColor: theme.colors.eventBackground }],
|
|
101
|
+
onPress,
|
|
102
|
+
activeOpacity: .7,
|
|
103
|
+
accessibilityRole: "button",
|
|
104
|
+
accessibilityLabel: event.title,
|
|
105
|
+
children: [event.title ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
106
|
+
style: [theme.text.eventTitle, { color: theme.colors.eventText }],
|
|
107
|
+
numberOfLines: mode === "day" ? void 0 : 1,
|
|
108
|
+
ellipsizeMode: "tail",
|
|
109
|
+
allowFontScaling: false,
|
|
110
|
+
children: event.title
|
|
111
|
+
}) : null, showTime ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
112
|
+
style: [styles$3.time, { color: theme.colors.eventText }],
|
|
113
|
+
numberOfLines: 1,
|
|
114
|
+
allowFontScaling: false,
|
|
115
|
+
children: `${(0, date_fns.format)(event.start, "HH:mm")} - ${(0, date_fns.format)(event.end, "HH:mm")}`
|
|
116
|
+
}) : null]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const styles$3 = react_native.StyleSheet.create({
|
|
120
|
+
box: {
|
|
121
|
+
flex: 1,
|
|
122
|
+
borderRadius: 6,
|
|
123
|
+
paddingVertical: 2,
|
|
124
|
+
paddingHorizontal: 4,
|
|
125
|
+
overflow: "hidden"
|
|
126
|
+
},
|
|
127
|
+
time: { fontSize: 11 }
|
|
128
|
+
});
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/utils/dates.ts
|
|
131
|
+
/** The seven dates of the week containing `date`, starting on `weekStartsOn`. */
|
|
132
|
+
const getWeekDays = (date, weekStartsOn) => {
|
|
133
|
+
const start = (0, date_fns.startOfWeek)(date, { weekStartsOn });
|
|
134
|
+
return Array.from({ length: 7 }, (_, index) => (0, date_fns.addDays)(start, index));
|
|
135
|
+
};
|
|
136
|
+
const isWeekend = (date) => {
|
|
137
|
+
const day = date.getDay();
|
|
138
|
+
return day === 0 || day === 6;
|
|
139
|
+
};
|
|
140
|
+
const getIsToday = (date) => (0, date_fns.isToday)(date);
|
|
141
|
+
const isSameCalendarDay = (a, b) => (0, date_fns.isSameDay)(a, b);
|
|
142
|
+
/** Minutes elapsed since midnight (0–1439). */
|
|
143
|
+
const minutesIntoDay = (date) => (0, date_fns.getHours)(date) * 60 + (0, date_fns.getMinutes)(date);
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/utils/layout.ts
|
|
146
|
+
const MINUTES_PER_HOUR$1 = 60;
|
|
147
|
+
const MIN_DURATION_HOURS = .25;
|
|
148
|
+
/**
|
|
149
|
+
* Lay out a single day's events: events that overlap in time are split into
|
|
150
|
+
* side-by-side columns. Multi-day events are clipped to the portion that falls
|
|
151
|
+
* on `day` (e.g. a 23:00→01:00 event renders 23:00–24:00 on the start day and
|
|
152
|
+
* 00:00–01:00 on the next). Pure — safe to call per render, never per frame.
|
|
153
|
+
*/
|
|
154
|
+
function layoutDayEvents(events, day) {
|
|
155
|
+
const dayStart = (0, date_fns.startOfDay)(day);
|
|
156
|
+
const nextDayStart = (0, date_fns.addDays)(dayStart, 1);
|
|
157
|
+
const segments = events.filter((event) => event.start < nextDayStart && event.end > dayStart).map((event) => {
|
|
158
|
+
const segStart = (0, date_fns.max)([event.start, dayStart]);
|
|
159
|
+
const segEnd = (0, date_fns.min)([event.end, nextDayStart]);
|
|
160
|
+
return {
|
|
161
|
+
event,
|
|
162
|
+
start: (0, date_fns.differenceInMinutes)(segStart, dayStart) / MINUTES_PER_HOUR$1,
|
|
163
|
+
end: (0, date_fns.differenceInMinutes)(segEnd, dayStart) / MINUTES_PER_HOUR$1,
|
|
164
|
+
continuesBefore: event.start < dayStart,
|
|
165
|
+
continuesAfter: event.end > nextDayStart
|
|
166
|
+
};
|
|
167
|
+
}).sort((a, b) => a.start - b.start);
|
|
168
|
+
const positioned = [];
|
|
169
|
+
let cluster = [];
|
|
170
|
+
let clusterEnd = Number.NEGATIVE_INFINITY;
|
|
171
|
+
const flushCluster = () => {
|
|
172
|
+
const columnEnds = [];
|
|
173
|
+
const columnOf = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const seg of cluster) {
|
|
175
|
+
let column = columnEnds.findIndex((end) => end <= seg.start);
|
|
176
|
+
if (column === -1) {
|
|
177
|
+
column = columnEnds.length;
|
|
178
|
+
columnEnds.push(seg.end);
|
|
179
|
+
} else columnEnds[column] = seg.end;
|
|
180
|
+
columnOf.set(seg, column);
|
|
181
|
+
}
|
|
182
|
+
for (const seg of cluster) positioned.push({
|
|
183
|
+
event: seg.event,
|
|
184
|
+
startHours: seg.start,
|
|
185
|
+
durationHours: Math.max(seg.end - seg.start, MIN_DURATION_HOURS),
|
|
186
|
+
column: columnOf.get(seg) ?? 0,
|
|
187
|
+
columns: columnEnds.length,
|
|
188
|
+
continuesBefore: seg.continuesBefore,
|
|
189
|
+
continuesAfter: seg.continuesAfter
|
|
190
|
+
});
|
|
191
|
+
cluster = [];
|
|
192
|
+
};
|
|
193
|
+
for (const seg of segments) {
|
|
194
|
+
if (cluster.length > 0 && seg.start >= clusterEnd) flushCluster();
|
|
195
|
+
cluster.push(seg);
|
|
196
|
+
clusterEnd = Math.max(clusterEnd, seg.end);
|
|
197
|
+
}
|
|
198
|
+
if (cluster.length > 0) flushCluster();
|
|
199
|
+
return positioned;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The `startOfDay` ISO keys of every calendar day an event touches (inclusive).
|
|
203
|
+
* An event ending exactly at midnight does not count the following day. Used to
|
|
204
|
+
* index events by day for the month grid. Pure.
|
|
205
|
+
*/
|
|
206
|
+
function eventDayKeys(event) {
|
|
207
|
+
const first = (0, date_fns.startOfDay)(event.start);
|
|
208
|
+
const last = (0, date_fns.startOfDay)(event.end > event.start ? /* @__PURE__ */ new Date(event.end.getTime() - 1) : event.start);
|
|
209
|
+
const keys = [];
|
|
210
|
+
for (let cursor = first; cursor <= last; cursor = (0, date_fns.addDays)(cursor, 1)) keys.push(cursor.toISOString());
|
|
211
|
+
return keys;
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/components/MonthView.tsx
|
|
215
|
+
const chunkIntoWeeks = (days) => {
|
|
216
|
+
const weeks = [];
|
|
217
|
+
for (let index = 0; index < days.length; index += 7) weeks.push(days.slice(index, index + 7));
|
|
218
|
+
return weeks;
|
|
219
|
+
};
|
|
220
|
+
function MonthViewInner({ date, events, maxVisibleEventCount, weekStartsOn, renderEvent, keyExtractor, onPressDay, onPressEvent, onPressMore }) {
|
|
221
|
+
const theme = useCalendarTheme();
|
|
222
|
+
const RenderEventComponent = renderEvent;
|
|
223
|
+
const weeks = (0, react.useMemo)(() => {
|
|
224
|
+
return chunkIntoWeeks((0, date_fns.eachDayOfInterval)({
|
|
225
|
+
start: (0, date_fns.startOfWeek)((0, date_fns.startOfMonth)(date), { weekStartsOn }),
|
|
226
|
+
end: (0, date_fns.endOfWeek)((0, date_fns.endOfMonth)(date), { weekStartsOn })
|
|
227
|
+
}));
|
|
228
|
+
}, [date, weekStartsOn]);
|
|
229
|
+
const eventsByDay = (0, react.useMemo)(() => {
|
|
230
|
+
const map = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const event of events) for (const key of eventDayKeys(event)) {
|
|
232
|
+
const existing = map.get(key);
|
|
233
|
+
if (existing) existing.push(event);
|
|
234
|
+
else map.set(key, [event]);
|
|
235
|
+
}
|
|
236
|
+
return map;
|
|
237
|
+
}, [events]);
|
|
238
|
+
const renderDay = (day) => {
|
|
239
|
+
const dayEvents = eventsByDay.get((0, date_fns.startOfDay)(day).toISOString()) ?? [];
|
|
240
|
+
const isCurrentMonth = (0, date_fns.isSameMonth)(day, date);
|
|
241
|
+
const isToday = getIsToday(day);
|
|
242
|
+
const hiddenCount = dayEvents.length - maxVisibleEventCount;
|
|
243
|
+
const dateColor = isToday ? theme.colors.todayText : isCurrentMonth ? theme.colors.text : theme.colors.textDisabled;
|
|
244
|
+
const eventCount = dayEvents.length;
|
|
245
|
+
const accessibilityLabel = `${(0, date_fns.format)(day, "EEEE, d LLLL yyyy")}${isToday ? ", today" : ""}, ${eventCount} ${eventCount === 1 ? "event" : "events"}`;
|
|
246
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native.TouchableOpacity, {
|
|
247
|
+
style: [
|
|
248
|
+
styles$2.dayCell,
|
|
249
|
+
{ borderColor: theme.colors.gridLine },
|
|
250
|
+
isWeekend(day) && { backgroundColor: theme.colors.weekendBackground }
|
|
251
|
+
],
|
|
252
|
+
onPress: onPressDay ? () => onPressDay(day) : void 0,
|
|
253
|
+
disabled: !onPressDay,
|
|
254
|
+
accessibilityRole: onPressDay ? "button" : void 0,
|
|
255
|
+
accessibilityLabel,
|
|
256
|
+
children: [
|
|
257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
258
|
+
style: [styles$2.dateBadge, isToday && {
|
|
259
|
+
backgroundColor: theme.colors.todayBackground,
|
|
260
|
+
borderRadius: theme.todayBadgeRadius
|
|
261
|
+
}],
|
|
262
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
263
|
+
style: [theme.text.dateCell, { color: dateColor }],
|
|
264
|
+
allowFontScaling: false,
|
|
265
|
+
children: (0, date_fns.format)(day, "d")
|
|
266
|
+
})
|
|
267
|
+
}),
|
|
268
|
+
dayEvents.slice(0, maxVisibleEventCount).map((event, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
269
|
+
style: styles$2.monthEvent,
|
|
270
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderEventComponent, {
|
|
271
|
+
event,
|
|
272
|
+
mode: "month",
|
|
273
|
+
onPress: () => onPressEvent(event)
|
|
274
|
+
})
|
|
275
|
+
}, keyExtractor(event, index))),
|
|
276
|
+
hiddenCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
277
|
+
style: [
|
|
278
|
+
theme.text.more,
|
|
279
|
+
styles$2.moreLabel,
|
|
280
|
+
{ color: theme.colors.textMuted }
|
|
281
|
+
],
|
|
282
|
+
onPress: onPressMore ? () => onPressMore(dayEvents, day) : void 0,
|
|
283
|
+
accessibilityRole: "button",
|
|
284
|
+
accessibilityLabel: `Show ${hiddenCount} more events`,
|
|
285
|
+
allowFontScaling: false,
|
|
286
|
+
children: `${hiddenCount} More`
|
|
287
|
+
}) : null
|
|
288
|
+
]
|
|
289
|
+
}, day.toISOString());
|
|
290
|
+
};
|
|
291
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
292
|
+
style: styles$2.container,
|
|
293
|
+
children: weeks.map((week) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
294
|
+
style: styles$2.weekRow,
|
|
295
|
+
children: week.map((day) => renderDay(day))
|
|
296
|
+
}, week[0].toISOString()))
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const MonthView = (0, react.memo)(MonthViewInner);
|
|
300
|
+
const styles$2 = react_native.StyleSheet.create({
|
|
301
|
+
container: { flex: 1 },
|
|
302
|
+
weekRow: {
|
|
303
|
+
flex: 1,
|
|
304
|
+
flexDirection: "row"
|
|
305
|
+
},
|
|
306
|
+
dayCell: {
|
|
307
|
+
flex: 1,
|
|
308
|
+
alignItems: "center",
|
|
309
|
+
paddingTop: 4,
|
|
310
|
+
gap: 2,
|
|
311
|
+
overflow: "hidden",
|
|
312
|
+
borderTopWidth: react_native.StyleSheet.hairlineWidth,
|
|
313
|
+
borderRightWidth: react_native.StyleSheet.hairlineWidth
|
|
314
|
+
},
|
|
315
|
+
dateBadge: {
|
|
316
|
+
justifyContent: "center",
|
|
317
|
+
alignItems: "center",
|
|
318
|
+
height: 24,
|
|
319
|
+
width: 24
|
|
320
|
+
},
|
|
321
|
+
monthEvent: { width: "92%" },
|
|
322
|
+
moreLabel: { marginTop: 2 }
|
|
323
|
+
});
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/components/MonthPager.tsx
|
|
326
|
+
const PAGE_WINDOW$1 = 60;
|
|
327
|
+
const PAGE_VIEWABILITY$1 = { itemVisiblePercentThreshold: 90 };
|
|
328
|
+
function MonthPagerInner({ date, events, maxVisibleEventCount, weekStartsOn, renderEvent, keyExtractor, onPressDay, onPressEvent, onPressMore, onChangeDate, freeSwipe = false }) {
|
|
329
|
+
const { width, height } = (0, react_native.useWindowDimensions)();
|
|
330
|
+
const listRef = (0, react.useRef)(null);
|
|
331
|
+
const [pageHeight, setPageHeight] = (0, react.useState)(height);
|
|
332
|
+
const [anchorDate] = (0, react.useState)(date);
|
|
333
|
+
const anchor = (0, react.useMemo)(() => (0, date_fns.startOfMonth)(anchorDate), [anchorDate]);
|
|
334
|
+
const monthDates = (0, react.useMemo)(() => Array.from({ length: 121 }, (_, i) => (0, date_fns.addMonths)(anchor, i - PAGE_WINDOW$1)), [anchor]);
|
|
335
|
+
const activeIndex = (0, react.useCallback)((target) => (0, date_fns.differenceInCalendarMonths)((0, date_fns.startOfMonth)(target), anchor) + PAGE_WINDOW$1, [anchor])(date);
|
|
336
|
+
const viewedIndexRef = (0, react.useRef)(activeIndex);
|
|
337
|
+
const handleViewableItemsChanged = (0, react.useCallback)((info) => {
|
|
338
|
+
const settled = info.viewableItems.find((token) => token.isViewable);
|
|
339
|
+
if (settled?.index == null || settled.index === viewedIndexRef.current) return;
|
|
340
|
+
viewedIndexRef.current = settled.index;
|
|
341
|
+
if (settled.item) onChangeDate(settled.item);
|
|
342
|
+
}, [onChangeDate]);
|
|
343
|
+
(0, react.useEffect)(() => {
|
|
344
|
+
if (activeIndex === viewedIndexRef.current) return;
|
|
345
|
+
viewedIndexRef.current = activeIndex;
|
|
346
|
+
listRef.current?.scrollToIndex({
|
|
347
|
+
index: activeIndex,
|
|
348
|
+
animated: false
|
|
349
|
+
});
|
|
350
|
+
}, [activeIndex]);
|
|
351
|
+
const snapToIndices = (0, react.useMemo)(() => monthDates.map((_, index) => index), [monthDates]);
|
|
352
|
+
const keyExtractorList = (0, react.useCallback)((item) => item.toISOString(), []);
|
|
353
|
+
const getFixedItemSize = (0, react.useCallback)(() => width, [width]);
|
|
354
|
+
const renderItem = (0, react.useCallback)(({ item }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
355
|
+
style: {
|
|
356
|
+
width,
|
|
357
|
+
height: pageHeight
|
|
358
|
+
},
|
|
359
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthView, {
|
|
360
|
+
date: item,
|
|
361
|
+
events,
|
|
362
|
+
maxVisibleEventCount,
|
|
363
|
+
weekStartsOn,
|
|
364
|
+
renderEvent,
|
|
365
|
+
keyExtractor,
|
|
366
|
+
onPressDay,
|
|
367
|
+
onPressEvent,
|
|
368
|
+
onPressMore
|
|
369
|
+
})
|
|
370
|
+
}), [
|
|
371
|
+
width,
|
|
372
|
+
pageHeight,
|
|
373
|
+
events,
|
|
374
|
+
maxVisibleEventCount,
|
|
375
|
+
weekStartsOn,
|
|
376
|
+
renderEvent,
|
|
377
|
+
keyExtractor,
|
|
378
|
+
onPressDay,
|
|
379
|
+
onPressEvent,
|
|
380
|
+
onPressMore
|
|
381
|
+
]);
|
|
382
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
383
|
+
style: styles$1.pager,
|
|
384
|
+
onLayout: (event) => setPageHeight(event.nativeEvent.layout.height),
|
|
385
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_legendapp_list_react_native.LegendList, {
|
|
386
|
+
ref: listRef,
|
|
387
|
+
style: styles$1.pagerList,
|
|
388
|
+
data: monthDates,
|
|
389
|
+
horizontal: true,
|
|
390
|
+
recycleItems: false,
|
|
391
|
+
keyExtractor: keyExtractorList,
|
|
392
|
+
getFixedItemSize,
|
|
393
|
+
pagingEnabled: !freeSwipe,
|
|
394
|
+
snapToIndices: freeSwipe ? snapToIndices : void 0,
|
|
395
|
+
initialScrollIndex: activeIndex,
|
|
396
|
+
showsHorizontalScrollIndicator: false,
|
|
397
|
+
viewabilityConfig: PAGE_VIEWABILITY$1,
|
|
398
|
+
onViewableItemsChanged: handleViewableItemsChanged,
|
|
399
|
+
renderItem
|
|
400
|
+
}, pageHeight)
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const MonthPager = (0, react.memo)(MonthPagerInner);
|
|
404
|
+
const styles$1 = react_native.StyleSheet.create({
|
|
405
|
+
pager: { flex: 1 },
|
|
406
|
+
pagerList: { flex: 1 }
|
|
407
|
+
});
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/components/TimeGrid.tsx
|
|
410
|
+
const MINUTES_PER_HOUR = 60;
|
|
411
|
+
const HOURS_PER_DAY = 24;
|
|
412
|
+
const DAY_VIEW_STEP = 1;
|
|
413
|
+
const WEEK_VIEW_STEP = 7;
|
|
414
|
+
const PAGE_WINDOW = 180;
|
|
415
|
+
const PAGE_VIEWABILITY = { itemVisiblePercentThreshold: 90 };
|
|
416
|
+
const DEFAULT_HOUR_HEIGHT = 64;
|
|
417
|
+
const DEFAULT_MIN_HOUR_HEIGHT = 32;
|
|
418
|
+
const DEFAULT_MAX_HOUR_HEIGHT = 160;
|
|
419
|
+
const DEFAULT_HOUR_COLUMN_WIDTH = 50;
|
|
420
|
+
const MIN_EVENT_HEIGHT = 32;
|
|
421
|
+
const HOUR_LABEL_TOP_INSET = 12;
|
|
422
|
+
const NOW_TICK_MS = 6e4;
|
|
423
|
+
function useNow(enabled) {
|
|
424
|
+
const [now, setNow] = (0, react.useState)(() => /* @__PURE__ */ new Date());
|
|
425
|
+
(0, react.useEffect)(() => {
|
|
426
|
+
if (!enabled) return;
|
|
427
|
+
setNow(/* @__PURE__ */ new Date());
|
|
428
|
+
const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), NOW_TICK_MS);
|
|
429
|
+
return () => clearInterval(id);
|
|
430
|
+
}, [enabled]);
|
|
431
|
+
return now;
|
|
432
|
+
}
|
|
433
|
+
function formatHourLabel(hour, ampm) {
|
|
434
|
+
if (!ampm) return String(hour);
|
|
435
|
+
const period = hour < 12 ? "AM" : "PM";
|
|
436
|
+
return `${hour % 12 === 0 ? 12 : hour % 12} ${period}`;
|
|
437
|
+
}
|
|
438
|
+
function AnimatedEventBox({ positioned, cellHeight, minHour, left, width, mode, renderEvent, onPress }) {
|
|
439
|
+
const RenderEventComponent = renderEvent;
|
|
440
|
+
const boxHeight = (0, react_native_reanimated.useDerivedValue)(() => Math.max(positioned.durationHours * cellHeight.value, MIN_EVENT_HEIGHT), [positioned.durationHours]);
|
|
441
|
+
const boxStyle = (0, react_native_reanimated.useAnimatedStyle)(() => ({
|
|
442
|
+
top: (positioned.startHours - minHour) * cellHeight.value,
|
|
443
|
+
height: boxHeight.value
|
|
444
|
+
}), [
|
|
445
|
+
positioned.startHours,
|
|
446
|
+
positioned.durationHours,
|
|
447
|
+
minHour
|
|
448
|
+
]);
|
|
449
|
+
const handlePress = () => onPress(positioned.event);
|
|
450
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
|
|
451
|
+
style: [
|
|
452
|
+
styles.eventBox,
|
|
453
|
+
{
|
|
454
|
+
left,
|
|
455
|
+
width
|
|
456
|
+
},
|
|
457
|
+
boxStyle
|
|
458
|
+
],
|
|
459
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderEventComponent, {
|
|
460
|
+
event: positioned.event,
|
|
461
|
+
mode,
|
|
462
|
+
boxHeight,
|
|
463
|
+
continuesBefore: positioned.continuesBefore,
|
|
464
|
+
continuesAfter: positioned.continuesAfter,
|
|
465
|
+
onPress: handlePress
|
|
466
|
+
})
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
const HourRow = ({ hour, minHour, cellHeight, hourColumnWidth, label }) => {
|
|
470
|
+
const theme = useCalendarTheme();
|
|
471
|
+
const animatedStyle = (0, react_native_reanimated.useAnimatedStyle)(() => ({ top: (hour - minHour) * cellHeight.value }), [hour, minHour]);
|
|
472
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native_reanimated.default.View, {
|
|
473
|
+
style: [styles.hourRow, animatedStyle],
|
|
474
|
+
pointerEvents: "none",
|
|
475
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
476
|
+
style: [
|
|
477
|
+
theme.text.hourLabel,
|
|
478
|
+
styles.hourLabel,
|
|
479
|
+
{
|
|
480
|
+
width: hourColumnWidth,
|
|
481
|
+
color: theme.colors.textMuted
|
|
482
|
+
}
|
|
483
|
+
],
|
|
484
|
+
allowFontScaling: false,
|
|
485
|
+
children: label
|
|
486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, { style: [styles.hourLine, { backgroundColor: theme.colors.gridLine }] })]
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const NowIndicator = ({ cellHeight, nowHours, minHour, left, color }) => {
|
|
490
|
+
const animatedStyle = (0, react_native_reanimated.useAnimatedStyle)(() => ({ top: (nowHours - minHour) * cellHeight.value }), [nowHours, minHour]);
|
|
491
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
|
|
492
|
+
style: [
|
|
493
|
+
styles.nowIndicator,
|
|
494
|
+
{
|
|
495
|
+
left,
|
|
496
|
+
backgroundColor: color
|
|
497
|
+
},
|
|
498
|
+
animatedStyle
|
|
499
|
+
],
|
|
500
|
+
pointerEvents: "none"
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
function TimetablePageInner({ mode, date, events, cellHeight, hourHeight, committedCellHeight, scrollY, isActive, scrollOffsetMinutes, weekStartsOn, hourColumnWidth, minHour, maxHour, ampm, minHourHeight, maxHourHeight, showNowIndicator, renderEvent, keyExtractor, onPressEvent, onPressCell }) {
|
|
504
|
+
const theme = useCalendarTheme();
|
|
505
|
+
const { width } = (0, react_native.useWindowDimensions)();
|
|
506
|
+
const scrollRef = (0, react_native_reanimated.useAnimatedRef)();
|
|
507
|
+
const heightSource = isActive ? cellHeight : committedCellHeight;
|
|
508
|
+
const scrollHandler = (0, react_native_reanimated.useAnimatedScrollHandler)((event) => {
|
|
509
|
+
if (isActive) scrollY.value = event.contentOffset.y;
|
|
510
|
+
});
|
|
511
|
+
(0, react_native_reanimated.useAnimatedReaction)(() => scrollY.value, (current, previous) => {
|
|
512
|
+
if (!isActive && current !== previous) (0, react_native_reanimated.scrollTo)(scrollRef, 0, current, false);
|
|
513
|
+
});
|
|
514
|
+
const days = (0, react.useMemo)(() => mode === "week" ? getWeekDays(date, weekStartsOn) : [date], [
|
|
515
|
+
mode,
|
|
516
|
+
date,
|
|
517
|
+
weekStartsOn
|
|
518
|
+
]);
|
|
519
|
+
const dayWidth = (width - hourColumnWidth) / days.length;
|
|
520
|
+
const dayLeft = (dayIndex) => hourColumnWidth + dayIndex * dayWidth;
|
|
521
|
+
const dayLayouts = (0, react.useMemo)(() => days.map((day) => layoutDayEvents(events, day)), [days, events]);
|
|
522
|
+
const handleBackgroundPress = (event) => {
|
|
523
|
+
if (!onPressCell) return;
|
|
524
|
+
const { locationX, locationY } = event.nativeEvent;
|
|
525
|
+
const day = days[days.length === 1 ? 0 : Math.floor(locationX / dayWidth)];
|
|
526
|
+
if (!day) return;
|
|
527
|
+
const minutes = Math.round((minHour + locationY / heightSource.value) * MINUTES_PER_HOUR);
|
|
528
|
+
const pressed = new Date(day);
|
|
529
|
+
pressed.setHours(0, 0, 0, 0);
|
|
530
|
+
pressed.setMinutes(minutes);
|
|
531
|
+
onPressCell(pressed);
|
|
532
|
+
};
|
|
533
|
+
const hoursRange = (0, react.useMemo)(() => Array.from({ length: maxHour - minHour }, (_, index) => minHour + index), [minHour, maxHour]);
|
|
534
|
+
const now = useNow(showNowIndicator && isActive);
|
|
535
|
+
const nowDayIndex = days.findIndex((day) => getIsToday(day));
|
|
536
|
+
const nowHours = ((0, date_fns.getHours)(now) * MINUTES_PER_HOUR + (0, date_fns.getMinutes)(now)) / MINUTES_PER_HOUR;
|
|
537
|
+
const nowInWindow = nowHours >= minHour && nowHours <= maxHour;
|
|
538
|
+
const fullHeightStyle = (0, react_native_reanimated.useAnimatedStyle)(() => ({ height: (maxHour - minHour) * heightSource.value }), [
|
|
539
|
+
minHour,
|
|
540
|
+
maxHour,
|
|
541
|
+
heightSource
|
|
542
|
+
]);
|
|
543
|
+
const pinchStartCellHeight = (0, react_native_reanimated.useSharedValue)(hourHeight);
|
|
544
|
+
const zoomGesture = (0, react.useMemo)(() => {
|
|
545
|
+
const pinch = react_native_gesture_handler.Gesture.Pinch().onStart(() => {
|
|
546
|
+
pinchStartCellHeight.value = cellHeight.value;
|
|
547
|
+
}).onUpdate((event) => {
|
|
548
|
+
cellHeight.value = Math.min(maxHourHeight, Math.max(minHourHeight, pinchStartCellHeight.value * event.scale));
|
|
549
|
+
}).onEnd(() => {
|
|
550
|
+
committedCellHeight.value = cellHeight.value;
|
|
551
|
+
});
|
|
552
|
+
return react_native_gesture_handler.Gesture.Simultaneous(pinch, react_native_gesture_handler.Gesture.Native());
|
|
553
|
+
}, [
|
|
554
|
+
cellHeight,
|
|
555
|
+
committedCellHeight,
|
|
556
|
+
pinchStartCellHeight,
|
|
557
|
+
minHourHeight,
|
|
558
|
+
maxHourHeight
|
|
559
|
+
]);
|
|
560
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
561
|
+
style: styles.container,
|
|
562
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_gesture_handler.GestureDetector, {
|
|
563
|
+
gesture: zoomGesture,
|
|
564
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.ScrollView, {
|
|
565
|
+
ref: scrollRef,
|
|
566
|
+
showsVerticalScrollIndicator: true,
|
|
567
|
+
onScroll: scrollHandler,
|
|
568
|
+
scrollEventThrottle: 16,
|
|
569
|
+
contentContainerStyle: { paddingTop: HOUR_LABEL_TOP_INSET },
|
|
570
|
+
contentOffset: {
|
|
571
|
+
x: 0,
|
|
572
|
+
y: Math.max(0, scrollOffsetMinutes / MINUTES_PER_HOUR - minHour) * hourHeight
|
|
573
|
+
},
|
|
574
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native_reanimated.default.View, {
|
|
575
|
+
style: [styles.content, fullHeightStyle],
|
|
576
|
+
children: [
|
|
577
|
+
onPressCell ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Pressable, {
|
|
578
|
+
style: [styles.cellPressLayer, { left: hourColumnWidth }],
|
|
579
|
+
onPress: handleBackgroundPress,
|
|
580
|
+
importantForAccessibility: "no",
|
|
581
|
+
accessibilityElementsHidden: true
|
|
582
|
+
}) : null,
|
|
583
|
+
days.map((day, dayIndex) => isWeekend(day) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
|
|
584
|
+
style: [
|
|
585
|
+
styles.weekendColumn,
|
|
586
|
+
{ backgroundColor: theme.colors.weekendBackground },
|
|
587
|
+
{
|
|
588
|
+
left: dayLeft(dayIndex),
|
|
589
|
+
width: dayWidth
|
|
590
|
+
},
|
|
591
|
+
fullHeightStyle
|
|
592
|
+
],
|
|
593
|
+
pointerEvents: "none"
|
|
594
|
+
}, `weekend-${day.toISOString()}`) : null),
|
|
595
|
+
days.map((day, dayIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
|
|
596
|
+
style: [
|
|
597
|
+
styles.daySeparator,
|
|
598
|
+
{ backgroundColor: theme.colors.gridLine },
|
|
599
|
+
{ left: dayLeft(dayIndex) },
|
|
600
|
+
fullHeightStyle
|
|
601
|
+
],
|
|
602
|
+
pointerEvents: "none"
|
|
603
|
+
}, `separator-${day.toISOString()}`)),
|
|
604
|
+
hoursRange.map((hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HourRow, {
|
|
605
|
+
hour,
|
|
606
|
+
minHour,
|
|
607
|
+
cellHeight: heightSource,
|
|
608
|
+
hourColumnWidth,
|
|
609
|
+
label: formatHourLabel(hour, ampm)
|
|
610
|
+
}, hour)),
|
|
611
|
+
dayLayouts.flatMap((layout, dayIndex) => layout.filter((p) => p.startHours < maxHour && p.startHours + p.durationHours > minHour).map((positioned, eventIndex) => {
|
|
612
|
+
const columnWidth = dayWidth / positioned.columns;
|
|
613
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedEventBox, {
|
|
614
|
+
positioned,
|
|
615
|
+
cellHeight: heightSource,
|
|
616
|
+
minHour,
|
|
617
|
+
left: dayLeft(dayIndex) + positioned.column * columnWidth,
|
|
618
|
+
width: columnWidth,
|
|
619
|
+
mode,
|
|
620
|
+
renderEvent,
|
|
621
|
+
onPress: onPressEvent
|
|
622
|
+
}, keyExtractor(positioned.event, eventIndex));
|
|
623
|
+
})),
|
|
624
|
+
showNowIndicator && nowDayIndex >= 0 && nowInWindow ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NowIndicator, {
|
|
625
|
+
cellHeight: heightSource,
|
|
626
|
+
nowHours,
|
|
627
|
+
minHour,
|
|
628
|
+
left: dayLeft(nowDayIndex),
|
|
629
|
+
color: theme.colors.nowIndicator
|
|
630
|
+
}) : null
|
|
631
|
+
]
|
|
632
|
+
})
|
|
633
|
+
})
|
|
634
|
+
})
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
const TimetablePage = (0, react.memo)(TimetablePageInner);
|
|
638
|
+
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 }) {
|
|
639
|
+
const clampedMinHour = Math.max(0, Math.min(minHour, HOURS_PER_DAY - 1));
|
|
640
|
+
const clampedMaxHour = Math.max(clampedMinHour + 1, Math.min(maxHour, HOURS_PER_DAY));
|
|
641
|
+
const { width, height } = (0, react_native.useWindowDimensions)();
|
|
642
|
+
const listRef = (0, react.useRef)(null);
|
|
643
|
+
const [pageHeight, setPageHeight] = (0, react.useState)(height);
|
|
644
|
+
const step = mode === "week" ? WEEK_VIEW_STEP : DAY_VIEW_STEP;
|
|
645
|
+
const scrollY = (0, react_native_reanimated.useSharedValue)(Math.max(0, scrollOffsetMinutes / MINUTES_PER_HOUR - clampedMinHour) * hourHeight);
|
|
646
|
+
const committedCellHeight = (0, react_native_reanimated.useSharedValue)(hourHeight);
|
|
647
|
+
const [anchorDate] = (0, react.useState)(date);
|
|
648
|
+
const anchor = (0, react.useMemo)(() => mode === "week" ? (0, date_fns.startOfWeek)(anchorDate, { weekStartsOn }) : (0, date_fns.startOfDay)(anchorDate), [
|
|
649
|
+
mode,
|
|
650
|
+
anchorDate,
|
|
651
|
+
weekStartsOn
|
|
652
|
+
]);
|
|
653
|
+
const pageDates = (0, react.useMemo)(() => Array.from({ length: 361 }, (_, i) => (0, date_fns.addDays)(anchor, (i - PAGE_WINDOW) * step)), [anchor, step]);
|
|
654
|
+
const activeIndex = (0, react.useCallback)((target) => {
|
|
655
|
+
const aligned = mode === "week" ? (0, date_fns.startOfWeek)(target, { weekStartsOn }) : (0, date_fns.startOfDay)(target);
|
|
656
|
+
return Math.round((0, date_fns.differenceInCalendarDays)(aligned, anchor) / step) + PAGE_WINDOW;
|
|
657
|
+
}, [
|
|
658
|
+
anchor,
|
|
659
|
+
mode,
|
|
660
|
+
step,
|
|
661
|
+
weekStartsOn
|
|
662
|
+
])(date);
|
|
663
|
+
const viewedIndexRef = (0, react.useRef)(activeIndex);
|
|
664
|
+
const headerDays = (0, react.useMemo)(() => mode === "week" ? getWeekDays(date, weekStartsOn) : [date], [
|
|
665
|
+
mode,
|
|
666
|
+
date,
|
|
667
|
+
weekStartsOn
|
|
668
|
+
]);
|
|
669
|
+
const handleViewableItemsChanged = (0, react.useCallback)((info) => {
|
|
670
|
+
const settled = info.viewableItems.find((token) => token.isViewable);
|
|
671
|
+
if (settled?.index == null || settled.index === viewedIndexRef.current) return;
|
|
672
|
+
viewedIndexRef.current = settled.index;
|
|
673
|
+
if (settled.item) onChangeDate(settled.item);
|
|
674
|
+
}, [onChangeDate]);
|
|
675
|
+
(0, react.useEffect)(() => {
|
|
676
|
+
if (activeIndex === viewedIndexRef.current) return;
|
|
677
|
+
viewedIndexRef.current = activeIndex;
|
|
678
|
+
listRef.current?.scrollToIndex({
|
|
679
|
+
index: activeIndex,
|
|
680
|
+
animated: false
|
|
681
|
+
});
|
|
682
|
+
}, [activeIndex]);
|
|
683
|
+
const snapToIndices = (0, react.useMemo)(() => pageDates.map((_, index) => index), [pageDates]);
|
|
684
|
+
const keyExtractorList = (0, react.useCallback)((item) => item.toISOString(), []);
|
|
685
|
+
const getFixedItemSize = (0, react.useCallback)(() => width, [width]);
|
|
686
|
+
const renderItem = (0, react.useCallback)(({ item, index }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
687
|
+
style: {
|
|
688
|
+
width,
|
|
689
|
+
height: pageHeight
|
|
690
|
+
},
|
|
691
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimetablePage, {
|
|
692
|
+
mode,
|
|
693
|
+
date: item,
|
|
694
|
+
events,
|
|
695
|
+
cellHeight,
|
|
696
|
+
hourHeight,
|
|
697
|
+
committedCellHeight,
|
|
698
|
+
scrollY,
|
|
699
|
+
isActive: index === activeIndex,
|
|
700
|
+
scrollOffsetMinutes,
|
|
701
|
+
weekStartsOn,
|
|
702
|
+
hourColumnWidth,
|
|
703
|
+
minHour: clampedMinHour,
|
|
704
|
+
maxHour: clampedMaxHour,
|
|
705
|
+
ampm,
|
|
706
|
+
minHourHeight,
|
|
707
|
+
maxHourHeight,
|
|
708
|
+
showNowIndicator,
|
|
709
|
+
renderEvent,
|
|
710
|
+
keyExtractor,
|
|
711
|
+
onPressEvent,
|
|
712
|
+
onPressCell
|
|
713
|
+
})
|
|
714
|
+
}), [
|
|
715
|
+
width,
|
|
716
|
+
pageHeight,
|
|
717
|
+
mode,
|
|
718
|
+
events,
|
|
719
|
+
cellHeight,
|
|
720
|
+
hourHeight,
|
|
721
|
+
committedCellHeight,
|
|
722
|
+
scrollY,
|
|
723
|
+
activeIndex,
|
|
724
|
+
scrollOffsetMinutes,
|
|
725
|
+
weekStartsOn,
|
|
726
|
+
hourColumnWidth,
|
|
727
|
+
clampedMinHour,
|
|
728
|
+
clampedMaxHour,
|
|
729
|
+
ampm,
|
|
730
|
+
minHourHeight,
|
|
731
|
+
maxHourHeight,
|
|
732
|
+
showNowIndicator,
|
|
733
|
+
renderEvent,
|
|
734
|
+
keyExtractor,
|
|
735
|
+
onPressEvent,
|
|
736
|
+
onPressCell
|
|
737
|
+
]);
|
|
738
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native.View, {
|
|
739
|
+
style: styles.container,
|
|
740
|
+
children: [renderHeader ? renderHeader(headerDays) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultHeader, {
|
|
741
|
+
days: headerDays,
|
|
742
|
+
mode,
|
|
743
|
+
width,
|
|
744
|
+
hourColumnWidth,
|
|
745
|
+
locale
|
|
746
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
747
|
+
style: styles.pager,
|
|
748
|
+
onLayout: (event) => setPageHeight(event.nativeEvent.layout.height),
|
|
749
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_legendapp_list_react_native.LegendList, {
|
|
750
|
+
ref: listRef,
|
|
751
|
+
style: styles.pagerList,
|
|
752
|
+
data: pageDates,
|
|
753
|
+
horizontal: true,
|
|
754
|
+
recycleItems: false,
|
|
755
|
+
keyExtractor: keyExtractorList,
|
|
756
|
+
getFixedItemSize,
|
|
757
|
+
pagingEnabled: !freeSwipe,
|
|
758
|
+
snapToIndices: freeSwipe ? snapToIndices : void 0,
|
|
759
|
+
initialScrollIndex: activeIndex,
|
|
760
|
+
showsHorizontalScrollIndicator: false,
|
|
761
|
+
viewabilityConfig: PAGE_VIEWABILITY,
|
|
762
|
+
onViewableItemsChanged: handleViewableItemsChanged,
|
|
763
|
+
renderItem
|
|
764
|
+
}, pageHeight)
|
|
765
|
+
})]
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const TimeGrid = (0, react.memo)(TimeGridInner);
|
|
769
|
+
const DefaultHeader = ({ days, mode, width, hourColumnWidth, locale }) => {
|
|
770
|
+
const dayWidth = mode === "week" ? (width - hourColumnWidth) / days.length : width;
|
|
771
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native.View, {
|
|
772
|
+
style: styles.headerRow,
|
|
773
|
+
children: [mode === "week" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, { style: { width: hourColumnWidth } }) : null, days.map((day) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DayHeader, {
|
|
774
|
+
day,
|
|
775
|
+
mode,
|
|
776
|
+
width: dayWidth,
|
|
777
|
+
locale
|
|
778
|
+
}, day.toISOString()))]
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
const DayHeader = ({ day, mode, width, locale }) => {
|
|
782
|
+
const theme = useCalendarTheme();
|
|
783
|
+
const isToday = getIsToday(day);
|
|
784
|
+
const badgeSize = mode === "day" ? 44 : 32;
|
|
785
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_native.View, {
|
|
786
|
+
style: [styles.dayHeader, {
|
|
787
|
+
width,
|
|
788
|
+
gap: mode === "day" ? 4 : 2
|
|
789
|
+
}],
|
|
790
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.View, {
|
|
791
|
+
style: [styles.dayHeaderBadge, isToday && {
|
|
792
|
+
backgroundColor: theme.colors.todayBackground,
|
|
793
|
+
borderRadius: 999,
|
|
794
|
+
width: badgeSize,
|
|
795
|
+
height: badgeSize
|
|
796
|
+
}],
|
|
797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
798
|
+
style: [theme.text.dayNumber, { color: isToday ? theme.colors.todayText : theme.colors.text }],
|
|
799
|
+
allowFontScaling: false,
|
|
800
|
+
...isToday && { accessibilityLabel: `Today, ${day.getDate()}` },
|
|
801
|
+
children: day.getDate()
|
|
802
|
+
})
|
|
803
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.Text, {
|
|
804
|
+
style: [theme.text.weekday, { color: theme.colors.text }],
|
|
805
|
+
allowFontScaling: false,
|
|
806
|
+
children: day.toLocaleDateString(locale, { weekday: "short" })
|
|
807
|
+
})]
|
|
808
|
+
});
|
|
809
|
+
};
|
|
810
|
+
const styles = react_native.StyleSheet.create({
|
|
811
|
+
pager: { flex: 1 },
|
|
812
|
+
pagerList: { flex: 1 },
|
|
813
|
+
container: { flex: 1 },
|
|
814
|
+
headerRow: {
|
|
815
|
+
flexDirection: "row",
|
|
816
|
+
alignItems: "center",
|
|
817
|
+
paddingBottom: 8
|
|
818
|
+
},
|
|
819
|
+
dayHeader: { alignItems: "center" },
|
|
820
|
+
dayHeaderBadge: {
|
|
821
|
+
justifyContent: "center",
|
|
822
|
+
alignItems: "center"
|
|
823
|
+
},
|
|
824
|
+
content: {
|
|
825
|
+
width: "100%",
|
|
826
|
+
position: "relative"
|
|
827
|
+
},
|
|
828
|
+
cellPressLayer: {
|
|
829
|
+
position: "absolute",
|
|
830
|
+
top: 0,
|
|
831
|
+
bottom: 0,
|
|
832
|
+
right: 0
|
|
833
|
+
},
|
|
834
|
+
weekendColumn: {
|
|
835
|
+
position: "absolute",
|
|
836
|
+
top: 0
|
|
837
|
+
},
|
|
838
|
+
daySeparator: {
|
|
839
|
+
position: "absolute",
|
|
840
|
+
top: 0,
|
|
841
|
+
width: react_native.StyleSheet.hairlineWidth
|
|
842
|
+
},
|
|
843
|
+
hourRow: {
|
|
844
|
+
position: "absolute",
|
|
845
|
+
left: 0,
|
|
846
|
+
right: 0,
|
|
847
|
+
flexDirection: "row",
|
|
848
|
+
alignItems: "flex-start"
|
|
849
|
+
},
|
|
850
|
+
hourLabel: {
|
|
851
|
+
marginTop: -8,
|
|
852
|
+
textAlign: "center"
|
|
853
|
+
},
|
|
854
|
+
hourLine: {
|
|
855
|
+
flex: 1,
|
|
856
|
+
height: react_native.StyleSheet.hairlineWidth
|
|
857
|
+
},
|
|
858
|
+
eventBox: {
|
|
859
|
+
position: "absolute",
|
|
860
|
+
overflow: "hidden"
|
|
861
|
+
},
|
|
862
|
+
nowIndicator: {
|
|
863
|
+
position: "absolute",
|
|
864
|
+
right: 0,
|
|
865
|
+
height: 2
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region src/components/Calendar.tsx
|
|
870
|
+
const defaultKeyExtractor = (event) => `${event.start.toISOString()}|${event.end.toISOString()}|${event.title ?? ""}`;
|
|
871
|
+
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 }) {
|
|
872
|
+
const mergedTheme = (0, react.useMemo)(() => mergeTheme(theme), [theme]);
|
|
873
|
+
const internalCellHeight = (0, react_native_reanimated.useSharedValue)(hourHeight);
|
|
874
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarThemeProvider, {
|
|
875
|
+
value: mergedTheme,
|
|
876
|
+
children: mode === "month" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthPager, {
|
|
877
|
+
date,
|
|
878
|
+
events,
|
|
879
|
+
maxVisibleEventCount,
|
|
880
|
+
weekStartsOn,
|
|
881
|
+
renderEvent,
|
|
882
|
+
keyExtractor,
|
|
883
|
+
onPressDay,
|
|
884
|
+
onPressEvent,
|
|
885
|
+
onPressMore,
|
|
886
|
+
onChangeDate,
|
|
887
|
+
freeSwipe
|
|
888
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeGrid, {
|
|
889
|
+
mode,
|
|
890
|
+
date,
|
|
891
|
+
events,
|
|
892
|
+
cellHeight: cellHeightProp ?? internalCellHeight,
|
|
893
|
+
hourHeight,
|
|
894
|
+
weekStartsOn,
|
|
895
|
+
renderEvent,
|
|
896
|
+
keyExtractor,
|
|
897
|
+
scrollOffsetMinutes,
|
|
898
|
+
hourColumnWidth,
|
|
899
|
+
minHour,
|
|
900
|
+
maxHour,
|
|
901
|
+
ampm,
|
|
902
|
+
minHourHeight,
|
|
903
|
+
maxHourHeight,
|
|
904
|
+
showNowIndicator,
|
|
905
|
+
locale,
|
|
906
|
+
freeSwipe,
|
|
907
|
+
onPressEvent,
|
|
908
|
+
onPressCell,
|
|
909
|
+
onChangeDate,
|
|
910
|
+
renderHeader: renderTimeGridHeader
|
|
911
|
+
})
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
//#endregion
|
|
915
|
+
exports.Calendar = Calendar;
|
|
916
|
+
exports.CalendarThemeProvider = CalendarThemeProvider;
|
|
917
|
+
exports.DEFAULT_HOUR_HEIGHT = DEFAULT_HOUR_HEIGHT;
|
|
918
|
+
exports.DefaultEvent = DefaultEvent;
|
|
919
|
+
exports.MonthPager = MonthPager;
|
|
920
|
+
exports.MonthView = MonthView;
|
|
921
|
+
exports.TimeGrid = TimeGrid;
|
|
922
|
+
exports.defaultTheme = defaultTheme;
|
|
923
|
+
exports.getIsToday = getIsToday;
|
|
924
|
+
exports.getWeekDays = getWeekDays;
|
|
925
|
+
exports.isSameCalendarDay = isSameCalendarDay;
|
|
926
|
+
exports.isWeekend = isWeekend;
|
|
927
|
+
exports.layoutDayEvents = layoutDayEvents;
|
|
928
|
+
exports.mergeTheme = mergeTheme;
|
|
929
|
+
exports.minutesIntoDay = minutesIntoDay;
|
|
930
|
+
exports.useCalendarTheme = useCalendarTheme;
|