@sunsama/event-calendar 0.13.4 → 0.13.6
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/lib/commonjs/components/edit-event-container.js +14 -10
- package/lib/commonjs/components/edit-event-container.js.map +1 -1
- package/lib/commonjs/components/timed-event-container.js +13 -8
- package/lib/commonjs/components/timed-event-container.js.map +1 -1
- package/lib/commonjs/components/zoom-provider.js +4 -1
- package/lib/commonjs/components/zoom-provider.js.map +1 -1
- package/lib/commonjs/utils/date-utils.js +19 -1
- package/lib/commonjs/utils/date-utils.js.map +1 -1
- package/lib/commonjs/utils/generate-event-layouts.js +4 -0
- package/lib/commonjs/utils/generate-event-layouts.js.map +1 -1
- package/lib/commonjs/utils/pan-edit-event-gesture.js +36 -33
- package/lib/commonjs/utils/pan-edit-event-gesture.js.map +1 -1
- package/lib/module/components/edit-event-container.js +14 -10
- package/lib/module/components/edit-event-container.js.map +1 -1
- package/lib/module/components/timed-event-container.js +13 -8
- package/lib/module/components/timed-event-container.js.map +1 -1
- package/lib/module/components/zoom-provider.js +4 -1
- package/lib/module/components/zoom-provider.js.map +1 -1
- package/lib/module/utils/date-utils.js +17 -0
- package/lib/module/utils/date-utils.js.map +1 -1
- package/lib/module/utils/generate-event-layouts.js +5 -1
- package/lib/module/utils/generate-event-layouts.js.map +1 -1
- package/lib/module/utils/pan-edit-event-gesture.js +36 -33
- package/lib/module/utils/pan-edit-event-gesture.js.map +1 -1
- package/lib/typescript/commonjs/components/edit-event-container.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/timed-event-container.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/zoom-provider.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/date-utils.d.ts +1 -0
- package/lib/typescript/commonjs/utils/date-utils.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/generate-event-layouts.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/pan-edit-event-gesture.d.ts.map +1 -1
- package/lib/typescript/module/components/edit-event-container.d.ts.map +1 -1
- package/lib/typescript/module/components/timed-event-container.d.ts.map +1 -1
- package/lib/typescript/module/components/zoom-provider.d.ts.map +1 -1
- package/lib/typescript/module/utils/date-utils.d.ts +1 -0
- package/lib/typescript/module/utils/date-utils.d.ts.map +1 -1
- package/lib/typescript/module/utils/generate-event-layouts.d.ts.map +1 -1
- package/lib/typescript/module/utils/pan-edit-event-gesture.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/edit-event-container.tsx +19 -11
- package/src/components/timed-event-container.tsx +14 -8
- package/src/components/zoom-provider.tsx +4 -1
- package/src/utils/__tests___/date-utils-extended.test.ts +34 -0
- package/src/utils/__tests___/generate-event-layout.test.ts +29 -0
- package/src/utils/date-utils.ts +18 -0
- package/src/utils/generate-event-layouts.ts +5 -0
- package/src/utils/pan-edit-event-gesture.ts +6 -4
|
@@ -18,7 +18,7 @@ import Animated, {
|
|
|
18
18
|
useSharedValue,
|
|
19
19
|
} from "react-native-reanimated";
|
|
20
20
|
import useAutoScroll from "../hooks/use-auto-scroll";
|
|
21
|
-
import {
|
|
21
|
+
import { EventExtend } from "../types";
|
|
22
22
|
import gesturePan from "../utils/pan-edit-event-gesture";
|
|
23
23
|
import DragBar from "../components/drag-bar";
|
|
24
24
|
import { StyleSheet } from "react-native";
|
|
@@ -77,9 +77,9 @@ const EditEventContainer = memo(
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
const calculateHeight = useCallback(
|
|
80
|
-
(
|
|
81
|
-
const start = new Date(
|
|
82
|
-
const end = new Date(
|
|
80
|
+
(startStr: string, endStr: string, zoom: number) => {
|
|
81
|
+
const start = new Date(startStr);
|
|
82
|
+
const end = new Date(endStr);
|
|
83
83
|
|
|
84
84
|
// We can't use the position.height as that has a minimum time of 30 minutes, which might not be relevant to
|
|
85
85
|
// our actual duration, meaning we'll have to calculate the height based on the start and end times
|
|
@@ -93,23 +93,31 @@ const EditEventContainer = memo(
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
calculateHeight(
|
|
96
|
+
calculateHeight(
|
|
97
|
+
editingEvent.event.start,
|
|
98
|
+
editingEvent.event.end,
|
|
99
|
+
zoomLevel.value
|
|
100
|
+
);
|
|
97
101
|
currentY.value = editingEvent.position.top * zoomLevel.value;
|
|
98
102
|
}, [height, editingEvent, currentY, zoomLevel, timezone, calculateHeight]);
|
|
99
103
|
|
|
104
|
+
const eventStart = editingEvent?.event.start;
|
|
105
|
+
const eventEnd = editingEvent?.event.end;
|
|
106
|
+
const eventTop = editingEvent?.position.top;
|
|
100
107
|
useAnimatedReaction(
|
|
101
108
|
() => zoomLevel.value,
|
|
102
109
|
(zoom) => {
|
|
103
|
-
if (
|
|
104
|
-
runOnJS(calculateHeight)(
|
|
105
|
-
currentY.value =
|
|
110
|
+
if (eventStart && eventEnd) {
|
|
111
|
+
runOnJS(calculateHeight)(eventStart, eventEnd, zoom);
|
|
112
|
+
currentY.value = (eventTop || 0) * zoom;
|
|
106
113
|
}
|
|
107
114
|
},
|
|
108
|
-
[height, currentY, zoomLevel,
|
|
115
|
+
[height, currentY, zoomLevel, eventStart, eventEnd, eventTop]
|
|
109
116
|
);
|
|
110
117
|
|
|
118
|
+
const hasEditingEvent = !!editingEvent;
|
|
111
119
|
const styleAnimatedPosition = useAnimatedStyle(() => {
|
|
112
|
-
if (!
|
|
120
|
+
if (!hasEditingEvent) {
|
|
113
121
|
return { opacity: 0 };
|
|
114
122
|
}
|
|
115
123
|
|
|
@@ -121,7 +129,7 @@ const EditEventContainer = memo(
|
|
|
121
129
|
opacity: 1,
|
|
122
130
|
width: "100%",
|
|
123
131
|
};
|
|
124
|
-
}, [
|
|
132
|
+
}, [hasEditingEvent, height, currentY]);
|
|
125
133
|
|
|
126
134
|
const startY = useSharedValue(0);
|
|
127
135
|
|
|
@@ -103,16 +103,18 @@ const TimedEventContainerInner = <T extends CalendarEvent>({
|
|
|
103
103
|
).activateAfterLongPress(500)
|
|
104
104
|
);
|
|
105
105
|
|
|
106
|
+
const eventStart = layout.event.start;
|
|
107
|
+
const eventEnd = layout.event.end;
|
|
106
108
|
useAnimatedReaction(
|
|
107
109
|
() => zoomLevel.value,
|
|
108
110
|
(newZoomLevel) => {
|
|
109
|
-
const start = new Date(
|
|
110
|
-
const end = new Date(
|
|
111
|
+
const start = new Date(eventStart);
|
|
112
|
+
const end = new Date(eventEnd);
|
|
111
113
|
const diff = (end.valueOf() - start.valueOf()) / 60_000;
|
|
112
114
|
|
|
113
115
|
height.value = Math.max(MIN_EVENT_HEIGHT_PX, newZoomLevel * diff);
|
|
114
116
|
},
|
|
115
|
-
[
|
|
117
|
+
[eventStart, eventEnd]
|
|
116
118
|
);
|
|
117
119
|
|
|
118
120
|
useAnimatedReaction(
|
|
@@ -128,25 +130,29 @@ const TimedEventContainerInner = <T extends CalendarEvent>({
|
|
|
128
130
|
[height, layout.event, renderEvent]
|
|
129
131
|
);
|
|
130
132
|
|
|
133
|
+
const eventId = layout.event.id;
|
|
134
|
+
const layoutPosition = layout.position;
|
|
135
|
+
const isEditingEventId = isEditing?.event.id;
|
|
136
|
+
|
|
131
137
|
const stylePosition = useAnimatedStyle(() => {
|
|
132
138
|
const basePosition: any = {
|
|
133
139
|
position: "absolute",
|
|
134
140
|
height: height.value,
|
|
135
141
|
top: top.value,
|
|
136
142
|
opacity: 1,
|
|
137
|
-
width:
|
|
138
|
-
marginLeft:
|
|
143
|
+
width: layoutPosition.width,
|
|
144
|
+
marginLeft: layoutPosition.marginLeft,
|
|
139
145
|
|
|
140
146
|
// This is to prevent the event from being clickable while editing
|
|
141
|
-
pointerEvents:
|
|
147
|
+
pointerEvents: isEditingEventId ? "none" : "auto",
|
|
142
148
|
};
|
|
143
149
|
|
|
144
|
-
if (
|
|
150
|
+
if (isEditingEventId === eventId) {
|
|
145
151
|
basePosition.opacity = 0.5;
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
return basePosition;
|
|
149
|
-
}, [
|
|
155
|
+
}, [layoutPosition, isEditingEventId, eventId]);
|
|
150
156
|
|
|
151
157
|
return (
|
|
152
158
|
<GestureDetector gesture={gestures}>
|
|
@@ -38,7 +38,10 @@ export default function ZoomProvider({
|
|
|
38
38
|
maximumHour,
|
|
39
39
|
onZoomChange,
|
|
40
40
|
} = useContext(ConfigProvider);
|
|
41
|
-
|
|
41
|
+
// Always overwritten in onStart before onUpdate reads it, so the initial
|
|
42
|
+
// value here is never actually used — avoids reading zoomLevel.value
|
|
43
|
+
// during render, which Reanimated strict mode warns about.
|
|
44
|
+
const previewScale = useSharedValue(0);
|
|
42
45
|
const focalTime = useSharedValue(0);
|
|
43
46
|
const screenFocalY = useSharedValue(0);
|
|
44
47
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
daysInRange,
|
|
8
8
|
startOfUserWeek,
|
|
9
9
|
computeCalendarDateRange,
|
|
10
|
+
normalizeEventDates,
|
|
10
11
|
} from "../date-utils";
|
|
11
12
|
import { CalendarEvent } from "../../types";
|
|
12
13
|
|
|
@@ -301,6 +302,39 @@ describe("startOfUserWeek", () => {
|
|
|
301
302
|
});
|
|
302
303
|
});
|
|
303
304
|
|
|
305
|
+
describe("normalizeEventDates", () => {
|
|
306
|
+
it("should convert Date start/end to ISO strings", () => {
|
|
307
|
+
const evt = {
|
|
308
|
+
...event("", ""),
|
|
309
|
+
start: new Date("2023-01-01T08:00:00.000Z"),
|
|
310
|
+
end: new Date("2023-01-01T09:00:00.000Z"),
|
|
311
|
+
} as unknown as CalendarEvent;
|
|
312
|
+
|
|
313
|
+
const normalized = normalizeEventDates(evt);
|
|
314
|
+
|
|
315
|
+
expect(normalized.start).toBe("2023-01-01T08:00:00.000Z");
|
|
316
|
+
expect(normalized.end).toBe("2023-01-01T09:00:00.000Z");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should leave string start/end untouched and return the same reference", () => {
|
|
320
|
+
const evt = event("2023-01-01T08:00:00Z", "2023-01-01T09:00:00Z");
|
|
321
|
+
|
|
322
|
+
expect(normalizeEventDates(evt)).toBe(evt);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should normalize only the fields that are Date instances", () => {
|
|
326
|
+
const evt = {
|
|
327
|
+
...event("2023-01-01T08:00:00.000Z", ""),
|
|
328
|
+
end: new Date("2023-01-01T09:00:00.000Z"),
|
|
329
|
+
} as unknown as CalendarEvent;
|
|
330
|
+
|
|
331
|
+
const normalized = normalizeEventDates(evt);
|
|
332
|
+
|
|
333
|
+
expect(normalized.start).toBe("2023-01-01T08:00:00.000Z");
|
|
334
|
+
expect(normalized.end).toBe("2023-01-01T09:00:00.000Z");
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
304
338
|
describe("computeCalendarDateRange", () => {
|
|
305
339
|
it("should return single day for 1day view", () => {
|
|
306
340
|
const result = computeCalendarDateRange("2023-10-10", "UTC", "1day", 0);
|
|
@@ -38,6 +38,35 @@ describe("generateEventLayouts", () => {
|
|
|
38
38
|
expect(dayLayout.partDayEventsLayout[0].event.id).toBe("2");
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it("should normalize Date start/end to strings in the resulting layout", () => {
|
|
42
|
+
// Consumers may pass native Date objects even though CalendarEvent types
|
|
43
|
+
// start/end as string; layouts flow into Reanimated worklet closures that
|
|
44
|
+
// can't clone a Date, so generateEventLayouts must normalize them first.
|
|
45
|
+
const events = [
|
|
46
|
+
{
|
|
47
|
+
id: "1",
|
|
48
|
+
calendarId: "primary-calendar",
|
|
49
|
+
title: "Timed Event",
|
|
50
|
+
start: new Date("2023-10-10T08:30:00Z"),
|
|
51
|
+
end: new Date("2023-10-10T09:30:00Z"),
|
|
52
|
+
},
|
|
53
|
+
] as unknown as CalendarEvent[];
|
|
54
|
+
|
|
55
|
+
const layouts = generateEventLayouts({
|
|
56
|
+
startCalendarDate: "2023-10-10",
|
|
57
|
+
endCalendarDate: "2023-10-10",
|
|
58
|
+
events,
|
|
59
|
+
timezone: "UTC",
|
|
60
|
+
userCalendarId: "primary-calendar",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const { event } = layouts["2023-10-10"]!.partDayEventsLayout[0]!;
|
|
64
|
+
expect(typeof event.start).toBe("string");
|
|
65
|
+
expect(typeof event.end).toBe("string");
|
|
66
|
+
expect(event.start).toBe("2023-10-10T08:30:00.000Z");
|
|
67
|
+
expect(event.end).toBe("2023-10-10T09:30:00.000Z");
|
|
68
|
+
});
|
|
69
|
+
|
|
41
70
|
it("should handle events with overlapping times", () => {
|
|
42
71
|
const events: CalendarEvent[] = [
|
|
43
72
|
{
|
package/src/utils/date-utils.ts
CHANGED
|
@@ -24,6 +24,24 @@ function toDate(date: Date | string): Date {
|
|
|
24
24
|
return typeof date === "string" ? new Date(date) : date;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Event layouts flow into Reanimated worklet closures (useAnimatedReaction,
|
|
28
|
+
// useAnimatedStyle, gesture handlers), which can't clone a `Date` instance
|
|
29
|
+
// across the JS/UI thread boundary ("[Worklets] Cannot copy value of type
|
|
30
|
+
// 'Date'."). Consumers may pass `start`/`end` as `Date` objects even though
|
|
31
|
+
// `CalendarEvent` types them as `string`, so normalize to ISO strings as
|
|
32
|
+
// soon as events enter the layout pipeline.
|
|
33
|
+
export const normalizeEventDates = <T extends CalendarEvent>(event: T): T => {
|
|
34
|
+
if (!isDate(event.start) && !isDate(event.end)) {
|
|
35
|
+
return event;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...event,
|
|
40
|
+
start: isDate(event.start) ? event.start.toISOString() : event.start,
|
|
41
|
+
end: isDate(event.end) ? event.end.toISOString() : event.end,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
27
45
|
export const generatePrefabHours = (
|
|
28
46
|
timeFormat: string = "HH:mm"
|
|
29
47
|
): PrefabHour[] => {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getDuration,
|
|
11
11
|
getDurationInDays,
|
|
12
12
|
isAllDayOrSpansMidnight,
|
|
13
|
+
normalizeEventDates,
|
|
13
14
|
startOfUserWeek,
|
|
14
15
|
} from "../utils/date-utils";
|
|
15
16
|
import computePositioning from "../utils/compute-positioning";
|
|
@@ -42,6 +43,10 @@ export const generateEventLayouts = <T extends CalendarEvent>({
|
|
|
42
43
|
startDayOfWeekOffset = 0,
|
|
43
44
|
timezone,
|
|
44
45
|
}: GenerateEventLayouts<T>) => {
|
|
46
|
+
// Normalize start/end to strings before events flow into any layout object,
|
|
47
|
+
// since layouts get closed over by Reanimated worklets that can't clone Dates.
|
|
48
|
+
events = events.map(normalizeEventDates);
|
|
49
|
+
|
|
45
50
|
// Calculate spacial layout for CalendarViewDay events
|
|
46
51
|
// in month view, midnight-spanning part-day events are rendered as all-day multi-day
|
|
47
52
|
const [allDayEvents, partDayEvents] = _partition([...events], (event) =>
|
|
@@ -16,16 +16,17 @@ const gesturePan = <T extends CalendarEvent>(
|
|
|
16
16
|
startEditing?: () => void,
|
|
17
17
|
isDragging?: SharedValue<boolean>,
|
|
18
18
|
autoScrollOffset?: SharedValue<number>
|
|
19
|
-
) =>
|
|
20
|
-
|
|
19
|
+
) => {
|
|
20
|
+
const hasIsEditing = !!isEditing;
|
|
21
|
+
return Gesture.Pan()
|
|
21
22
|
.blocksExternalGesture(refNewEvent)
|
|
22
23
|
.onStart(() => {
|
|
23
24
|
if (startEditing) {
|
|
24
|
-
if (
|
|
25
|
+
if (hasIsEditing) {
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
if (!
|
|
29
|
+
if (!hasIsEditing) {
|
|
29
30
|
runOnJS(startEditing)();
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -69,5 +70,6 @@ const gesturePan = <T extends CalendarEvent>(
|
|
|
69
70
|
.onFinalize(() => {
|
|
70
71
|
if (isDragging) isDragging.value = false;
|
|
71
72
|
});
|
|
73
|
+
};
|
|
72
74
|
|
|
73
75
|
export default gesturePan;
|