@sunsama/event-calendar 0.10.4 → 0.11.1

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.
Files changed (52) hide show
  1. package/README.md +1 -0
  2. package/lib/commonjs/components/background-hours-content.js +12 -4
  3. package/lib/commonjs/components/background-hours-content.js.map +1 -1
  4. package/lib/commonjs/components/background-hours-layout.js +17 -9
  5. package/lib/commonjs/components/background-hours-layout.js.map +1 -1
  6. package/lib/commonjs/components/timed-events.js +2 -0
  7. package/lib/commonjs/components/timed-events.js.map +1 -1
  8. package/lib/commonjs/components/zoom-provider.js +6 -74
  9. package/lib/commonjs/components/zoom-provider.js.map +1 -1
  10. package/lib/commonjs/hooks/use-long-press-new-event.js +83 -0
  11. package/lib/commonjs/hooks/use-long-press-new-event.js.map +1 -0
  12. package/lib/commonjs/index.js +3 -1
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/module/components/background-hours-content.js +12 -5
  15. package/lib/module/components/background-hours-content.js.map +1 -1
  16. package/lib/module/components/background-hours-layout.js +16 -9
  17. package/lib/module/components/background-hours-layout.js.map +1 -1
  18. package/lib/module/components/timed-events.js +2 -0
  19. package/lib/module/components/timed-events.js.map +1 -1
  20. package/lib/module/components/zoom-provider.js +7 -75
  21. package/lib/module/components/zoom-provider.js.map +1 -1
  22. package/lib/module/hooks/use-long-press-new-event.js +79 -0
  23. package/lib/module/hooks/use-long-press-new-event.js.map +1 -0
  24. package/lib/module/index.js +3 -1
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/typescript/commonjs/components/background-hours-content.d.ts +3 -1
  27. package/lib/typescript/commonjs/components/background-hours-content.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/components/background-hours-layout.d.ts +3 -1
  29. package/lib/typescript/commonjs/components/background-hours-layout.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/components/zoom-provider.d.ts +2 -2
  31. package/lib/typescript/commonjs/components/zoom-provider.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/hooks/use-long-press-new-event.d.ts +3 -0
  33. package/lib/typescript/commonjs/hooks/use-long-press-new-event.d.ts.map +1 -0
  34. package/lib/typescript/commonjs/index.d.ts +1 -0
  35. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  36. package/lib/typescript/module/components/background-hours-content.d.ts +3 -1
  37. package/lib/typescript/module/components/background-hours-content.d.ts.map +1 -1
  38. package/lib/typescript/module/components/background-hours-layout.d.ts +3 -1
  39. package/lib/typescript/module/components/background-hours-layout.d.ts.map +1 -1
  40. package/lib/typescript/module/components/zoom-provider.d.ts +2 -2
  41. package/lib/typescript/module/components/zoom-provider.d.ts.map +1 -1
  42. package/lib/typescript/module/hooks/use-long-press-new-event.d.ts +3 -0
  43. package/lib/typescript/module/hooks/use-long-press-new-event.d.ts.map +1 -0
  44. package/lib/typescript/module/index.d.ts +1 -0
  45. package/lib/typescript/module/index.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/components/background-hours-content.tsx +24 -17
  48. package/src/components/background-hours-layout.tsx +25 -18
  49. package/src/components/timed-events.tsx +2 -2
  50. package/src/components/zoom-provider.tsx +54 -154
  51. package/src/hooks/use-long-press-new-event.ts +103 -0
  52. package/src/index.tsx +5 -1
@@ -1,38 +1,45 @@
1
- import { memo, useContext } from "react";
1
+ import { memo, type RefObject, useContext } from "react";
2
2
  import Animated, { useAnimatedStyle } from "react-native-reanimated";
3
3
  import { StyleSheet, Text, View } from "react-native";
4
4
  import { ConfigProvider, TOP_MARGIN_PIXEL_OFFSET } from "../utils/globals";
5
5
  import { PrefabHour } from "../types";
6
+ import { GestureDetector } from "react-native-gesture-handler";
7
+ import useLongPressNewEvent from "src/hooks/use-long-press-new-event";
6
8
 
7
9
  type BackgroundHoursLayoutProps = {
8
10
  hours: PrefabHour[];
11
+ refNewEvent: RefObject<any>;
9
12
  };
10
13
 
11
14
  const BackgroundHoursLayout = memo(
12
- ({ hours }: BackgroundHoursLayoutProps) => {
15
+ ({ refNewEvent, hours }: BackgroundHoursLayoutProps) => {
13
16
  const { theme, zoomLevel } = useContext(ConfigProvider);
14
17
 
15
18
  const styleHourSize = useAnimatedStyle(() => {
16
19
  return { height: zoomLevel.value * 60 };
17
20
  }, []);
18
21
 
22
+ const longPressNewEvent = useLongPressNewEvent(refNewEvent);
23
+
19
24
  return (
20
- <View style={[styles.hourContainer, theme?.backgroundHoursContainer]}>
21
- {hours.map((hour) => (
22
- <Animated.View
23
- style={[
24
- styles.hourInnerContainer,
25
- theme?.backgroundHoursInnerContainer,
26
- styleHourSize,
27
- ]}
28
- key={hour.increment}
29
- >
30
- <Text style={[styles.hourText, theme?.backgroundHoursText]}>
31
- {hour.hourFormatted}
32
- </Text>
33
- </Animated.View>
34
- ))}
35
- </View>
25
+ <GestureDetector gesture={longPressNewEvent}>
26
+ <View style={[styles.hourContainer, theme?.backgroundHoursContainer]}>
27
+ {hours.map((hour) => (
28
+ <Animated.View
29
+ style={[
30
+ styles.hourInnerContainer,
31
+ theme?.backgroundHoursInnerContainer,
32
+ styleHourSize,
33
+ ]}
34
+ key={hour.increment}
35
+ >
36
+ <Text style={[styles.hourText, theme?.backgroundHoursText]}>
37
+ {hour.hourFormatted}
38
+ </Text>
39
+ </Animated.View>
40
+ ))}
41
+ </View>
42
+ </GestureDetector>
36
43
  );
37
44
  },
38
45
  () => true
@@ -33,9 +33,9 @@ const TimedEvents = ({ refNewEvent }: TimedEventsProps) => {
33
33
 
34
34
  return (
35
35
  <View style={[styles.container, theme?.timedEventsContainer]}>
36
- <BackgroundHoursLayout hours={hours} />
36
+ <BackgroundHoursLayout refNewEvent={refNewEvent} hours={hours} />
37
37
  <View style={styles.backgroundContainer}>
38
- <BackgroundHoursContent hours={hours} />
38
+ <BackgroundHoursContent refNewEvent={refNewEvent} hours={hours} />
39
39
  {layout.partDayEventsLayout.map((partDayLayout) => (
40
40
  <TimedEventContainer
41
41
  key={partDayLayout.event.id}
@@ -4,10 +4,9 @@ import Animated, {
4
4
  useSharedValue,
5
5
  } from "react-native-reanimated";
6
6
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
7
- import { forwardRef, useContext, useEffect } from "react";
8
- import { ConfigProvider, TOP_MARGIN_PIXEL_OFFSET } from "../utils/globals";
7
+ import { useContext, useEffect } from "react";
8
+ import { ConfigProvider } from "../utils/globals";
9
9
  import { StyleSheet } from "react-native";
10
- import { useIsEditing } from "../hooks/use-is-editing";
11
10
  import doubleTapGesture from "../utils/double-tap-reset-zoom-gesture";
12
11
 
13
12
  type ZoomProviderProps = {
@@ -17,160 +16,61 @@ type ZoomProviderProps = {
17
16
  // This fraction determines how quickly zoom grows
18
17
  const fraction = 0.1;
19
18
 
20
- const ZoomProvider = forwardRef<any, ZoomProviderProps>(
21
- ({ children }, refNewEvent) => {
22
- const {
23
- canCreateEvents,
24
- zoomLevel,
25
- defaultZoomLevel,
26
- createY,
27
- onCreateEvent,
28
- maxZoomLevel,
29
- minZoomLevel,
30
- maximumHour,
31
- onZoomChange,
32
- fiveMinuteInterval,
33
- } = useContext(ConfigProvider);
34
- const previewScale = useSharedValue(-1);
35
-
36
- useEffect(() => {
37
- previewScale.value = zoomLevel.get();
38
- }, [zoomLevel, previewScale]);
39
-
40
- const pinchGesture = Gesture.Pinch()
41
- .onUpdate((event) => {
42
- "worklet";
43
-
44
- const newScale =
45
- previewScale.value * (1 + fraction * (event.scale - 1));
46
-
47
- zoomLevel.value = Math.min(
48
- maxZoomLevel,
49
- Math.max(minZoomLevel, newScale)
50
- );
51
- previewScale.value = zoomLevel.value;
52
- })
53
- .onEnd(() => {
54
- if (onZoomChange) {
55
- runOnJS(onZoomChange)(zoomLevel.value);
56
- }
57
- });
58
-
59
- const yPosition = useSharedValue(-1);
60
- const { isEditing } = useIsEditing();
61
- const isDragging = useSharedValue(false);
62
-
63
- useAnimatedReaction(
64
- () => zoomLevel.value,
65
- (zoom) => {
66
- maximumHour.value = 1440 * zoom;
67
- },
68
- [maximumHour]
69
- );
70
-
71
- const longPressGesture = Gesture.LongPress()
72
- .enabled(canCreateEvents && !isEditing)
73
- .withRef(refNewEvent as any)
74
- .numberOfPointers(1)
75
- .minDuration(250)
76
- .maxDistance(10000)
77
- .onStart((event) => {
78
- "worklet";
79
-
80
- isDragging.value = true;
81
- createY.value = Math.max(
82
- 0,
83
- event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2
84
- );
85
- })
86
- .onTouchesMove((event) => {
87
- "worklet";
88
-
89
- if (!isDragging.value) {
90
- return;
91
- }
92
-
93
- if (!fiveMinuteInterval) {
94
- createY.value = Math.max(
95
- 0,
96
- event.allTouches[0].y -
97
- TOP_MARGIN_PIXEL_OFFSET -
98
- (zoomLevel.value * 60) / 2
99
- );
100
- } else {
101
- const normalizedY =
102
- event.allTouches[0].y -
103
- TOP_MARGIN_PIXEL_OFFSET -
104
- (zoomLevel.value * 60) / 2;
105
- const time = Math.floor(normalizedY / zoomLevel.value);
106
- const hour = Math.floor(time / 60);
107
- const minute = time - hour * 60;
108
- const minuteInterval = Math.floor(minute / 5) * 5;
109
-
110
- createY.value = (hour * 60 + minuteInterval) * zoomLevel.value;
111
- }
112
- })
113
- .onEnd((event, success) => {
114
- "worklet";
115
-
116
- if (!isDragging.value) {
117
- return;
118
- }
119
-
120
- // Make sure it doesn't show the new event component anymore
121
- createY.value = -1;
122
- yPosition.value = -1;
123
- isDragging.value = false;
124
-
125
- if (!success) {
126
- return;
127
- }
128
-
129
- // Determine the hour that was clicked and trigger the event creation
130
- const normalizedY =
131
- event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2;
132
- const time = Math.floor(normalizedY / zoomLevel.value);
133
- const hour = Math.floor(time / 60);
134
- const minute = time - hour * 60;
135
-
136
- if (!onCreateEvent) {
137
- return;
138
- }
139
-
140
- if (fiveMinuteInterval) {
141
- const minuteInterval = Math.floor(minute / 5) * 5;
142
-
143
- runOnJS(onCreateEvent)({
144
- hour,
145
- minute: minuteInterval,
146
- });
147
- return;
148
- }
149
-
150
- runOnJS(onCreateEvent)({
151
- hour,
152
- minute,
153
- });
154
- });
155
-
156
- const combinedGesture = Gesture.Simultaneous(
157
- pinchGesture,
158
- longPressGesture,
159
- doubleTapGesture(zoomLevel, defaultZoomLevel, onZoomChange)
160
- );
161
-
162
- return (
163
- <GestureDetector gesture={combinedGesture}>
164
- <Animated.View style={styles.container}>{children}</Animated.View>
165
- </GestureDetector>
166
- );
167
- }
168
- );
19
+ export default function ZoomProvider({ children }: ZoomProviderProps) {
20
+ const {
21
+ zoomLevel,
22
+ defaultZoomLevel,
23
+ maxZoomLevel,
24
+ minZoomLevel,
25
+ maximumHour,
26
+ onZoomChange,
27
+ } = useContext(ConfigProvider);
28
+ const previewScale = useSharedValue(-1);
29
+
30
+ useEffect(() => {
31
+ previewScale.value = zoomLevel.get();
32
+ }, [zoomLevel, previewScale]);
33
+
34
+ const pinchGesture = Gesture.Pinch()
35
+ .onUpdate((event) => {
36
+ "worklet";
37
+
38
+ const newScale = previewScale.value * (1 + fraction * (event.scale - 1));
39
+
40
+ zoomLevel.value = Math.min(
41
+ maxZoomLevel,
42
+ Math.max(minZoomLevel, newScale)
43
+ );
44
+ previewScale.value = zoomLevel.value;
45
+ })
46
+ .onEnd(() => {
47
+ if (onZoomChange) {
48
+ runOnJS(onZoomChange)(zoomLevel.value);
49
+ }
50
+ });
51
+
52
+ useAnimatedReaction(
53
+ () => zoomLevel.value,
54
+ (zoom) => {
55
+ maximumHour.value = 1440 * zoom;
56
+ },
57
+ [maximumHour]
58
+ );
59
+
60
+ const combinedGesture = Gesture.Simultaneous(
61
+ pinchGesture,
62
+ doubleTapGesture(zoomLevel, defaultZoomLevel, onZoomChange)
63
+ );
64
+
65
+ return (
66
+ <GestureDetector gesture={combinedGesture}>
67
+ <Animated.View style={styles.container}>{children}</Animated.View>
68
+ </GestureDetector>
69
+ );
70
+ }
169
71
 
170
72
  const styles = StyleSheet.create({
171
73
  container: {
172
74
  flex: 1,
173
75
  },
174
76
  });
175
-
176
- export default ZoomProvider;
@@ -0,0 +1,103 @@
1
+ import { Gesture } from "react-native-gesture-handler";
2
+ import { ConfigProvider, TOP_MARGIN_PIXEL_OFFSET } from "src/utils/globals";
3
+ import { runOnJS, useSharedValue } from "react-native-reanimated";
4
+ import { useIsEditing } from "src/hooks/use-is-editing";
5
+ import { type RefObject, useContext } from "react";
6
+
7
+ export default function useLongPressNewEvent(refNewEvent: RefObject<any>) {
8
+ const {
9
+ canCreateEvents,
10
+ zoomLevel,
11
+ createY,
12
+ onCreateEvent,
13
+ fiveMinuteInterval,
14
+ } = useContext(ConfigProvider);
15
+ const yPosition = useSharedValue(-1);
16
+ const { isEditing } = useIsEditing();
17
+ const isDragging = useSharedValue(false);
18
+
19
+ return Gesture.LongPress()
20
+ .enabled(canCreateEvents && !isEditing)
21
+ .withRef(refNewEvent as any)
22
+ .numberOfPointers(1)
23
+ .minDuration(250)
24
+ .maxDistance(10000)
25
+ .onStart((event) => {
26
+ "worklet";
27
+
28
+ isDragging.value = true;
29
+ createY.value = Math.max(
30
+ 0,
31
+ event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2
32
+ );
33
+ })
34
+ .onTouchesMove((event) => {
35
+ "worklet";
36
+
37
+ if (!isDragging.value) {
38
+ return;
39
+ }
40
+
41
+ if (!fiveMinuteInterval) {
42
+ createY.value = Math.max(
43
+ 0,
44
+ event.allTouches[0].y -
45
+ TOP_MARGIN_PIXEL_OFFSET -
46
+ (zoomLevel.value * 60) / 2
47
+ );
48
+ } else {
49
+ const normalizedY =
50
+ event.allTouches[0].y -
51
+ TOP_MARGIN_PIXEL_OFFSET -
52
+ (zoomLevel.value * 60) / 2;
53
+ const time = Math.floor(normalizedY / zoomLevel.value);
54
+ const hour = Math.floor(time / 60);
55
+ const minute = time - hour * 60;
56
+ const minuteInterval = Math.floor(minute / 5) * 5;
57
+
58
+ createY.value = (hour * 60 + minuteInterval) * zoomLevel.value;
59
+ }
60
+ })
61
+ .onEnd((event, success) => {
62
+ "worklet";
63
+
64
+ if (!isDragging.value) {
65
+ return;
66
+ }
67
+
68
+ // Make sure it doesn't show the new event component anymore
69
+ createY.value = -1;
70
+ yPosition.value = -1;
71
+ isDragging.value = false;
72
+
73
+ if (!success) {
74
+ return;
75
+ }
76
+
77
+ // Determine the hour that was clicked and trigger the event creation
78
+ const normalizedY =
79
+ event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2;
80
+ const time = Math.floor(normalizedY / zoomLevel.value);
81
+ const hour = Math.floor(time / 60);
82
+ const minute = time - hour * 60;
83
+
84
+ if (!onCreateEvent) {
85
+ return;
86
+ }
87
+
88
+ if (fiveMinuteInterval) {
89
+ const minuteInterval = Math.floor(minute / 5) * 5;
90
+
91
+ runOnJS(onCreateEvent)({
92
+ hour,
93
+ minute: minuteInterval,
94
+ });
95
+ return;
96
+ }
97
+
98
+ runOnJS(onCreateEvent)({
99
+ hour,
100
+ minute,
101
+ });
102
+ });
103
+ }
package/src/index.tsx CHANGED
@@ -92,6 +92,7 @@ export interface EventCalendarMethods {
92
92
  scrollToOffset: (y: number, animated?: boolean) => void;
93
93
  startEditMode: (eventId: string) => void;
94
94
  endEditMode: () => void;
95
+ setZoomLevel: (newZoomLevel: number) => void;
95
96
  }
96
97
 
97
98
  function EventCalendarContentInner<T extends CalendarEvent>(
@@ -167,6 +168,9 @@ function EventCalendarContentInner<T extends CalendarEvent>(
167
168
 
168
169
  refEditingProvider.current?.startEditing(layout);
169
170
  },
171
+ setZoomLevel: (newZoomLevel: number) => {
172
+ zoomLevel.value = newZoomLevel;
173
+ },
170
174
  }),
171
175
  [zoomLevel, eventsLayout]
172
176
  );
@@ -221,7 +225,7 @@ function EventCalendarContentInner<T extends CalendarEvent>(
221
225
  onScroll={onScrollFeedback}
222
226
  >
223
227
  <IsEditingProvider ref={refEditingProvider}>
224
- <ZoomProvider ref={refNewEvent}>
228
+ <ZoomProvider>
225
229
  <View style={[styles.borderContainer, theme?.borderContainer]} />
226
230
  <TimedEvents refNewEvent={refNewEvent} />
227
231
  </ZoomProvider>