react-native-calendar-resource 1.0.2 → 1.1.2

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 [Your Name]
3
+ Copyright (c) 2026 AbdelRahman Sobhy
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -14,6 +14,8 @@ A fully customizable, type-safe resource-based calendar component for React Nati
14
14
  - 🎭 **Style Customization** - Full control over colors and appearance
15
15
  - 👆 **Interactive** - Handle slot, event, and resource press events
16
16
  - 🔄 **Synchronized Scrolling** - Header and time column scroll with the grid
17
+ - 🎯 **Drag and Drop** - Long-press and drag events to reschedule (optional)
18
+ - 🔍 **Pinch to Zoom** - Zoom in/out with focal point support (optional)
17
19
 
18
20
  ---
19
21
 
@@ -30,9 +32,13 @@ yarn add react-native-calendar-resource
30
32
  Ensure you have the required peer dependencies:
31
33
 
32
34
  ```bash
33
- npm install date-fns
35
+ npm install date-fns
36
+ # or
37
+ yarn add date-fns
34
38
  ```
35
39
 
40
+ **Note:** For Expo projects, these are typically already installed. For bare React Native projects, follow the [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) and [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) installation guides.
41
+
36
42
  ---
37
43
 
38
44
  ## Quick Start
@@ -80,26 +86,33 @@ function MyCalendar() {
80
86
 
81
87
  ### Optional Props
82
88
 
83
- | Prop | Type | Default | Description |
84
- | ------------------------ | ---------------------------------- | ---------------------------------------------------- | -------------------------------------- |
85
- | `unavailableSlots` | `UnavailableSlot<T>[]` | `[]` | Slots that are unavailable |
86
- | `timeConfig` | `CalendarTimeConfig` | `{ startHour: 9, endHour: 24, timeFormat: "HH:mm" }` | Time configuration |
87
- | `dimensions` | `Partial<CalendarDimensions>` | `{ resourceWidth: 64, hourHeight: 64 }` | Grid dimensions |
88
- | `styles` | `CalendarStyles` | - | Overall calendar styling |
89
- | `eventStyles` | `CalendarEventStyles` | - | Event-specific styling |
90
- | `unavailableStyles` | `UnavailableSlotStyles` | - | Unavailable slot styling |
91
- | `onSlotPress` | `(hour, resourceId, date) => void` | - | Called when empty slot is pressed |
92
- | `onEventPress` | `(event) => void` | - | Called when event is pressed |
93
- | `onResourcePress` | `(resource) => void` | - | Called when resource header is pressed |
94
- | `renderEvent` | `(event, dimensions) => ReactNode` | - | Custom event renderer |
95
- | `renderResourceHeader` | `(resource) => ReactNode` | - | Custom resource header renderer |
96
- | `renderTimeSlot` | `(hour) => ReactNode` | - | Custom time slot renderer |
97
- | `renderUnavailableSlot` | `(slot, dimensions) => ReactNode` | - | Custom unavailable slot renderer |
98
- | `showHeader` | `boolean` | `true` | Show date header |
99
- | `showTimeColumn` | `boolean` | `true` | Show time column |
100
- | `showResourceHeaders` | `boolean` | `true` | Show resource headers |
101
- | `enableHorizontalScroll` | `boolean` | `true` | Enable horizontal scrolling |
102
- | `enableVerticalScroll` | `boolean` | `true` | Enable vertical scrolling |
89
+ | Prop | Type | Default | Description |
90
+ | ------------------------ | -------------------------------------- | ---------------------------------------------------- | ------------------------------------------- |
91
+ | `unavailableSlots` | `UnavailableSlot<T>[]` | `[]` | Slots that are unavailable |
92
+ | `timeConfig` | `CalendarTimeConfig` | `{ startHour: 9, endHour: 24, timeFormat: "HH:mm" }` | Time configuration |
93
+ | `dimensions` | `Partial<CalendarDimensions>` | `{ resourceWidth: 64, hourHeight: 64 }` | Grid dimensions |
94
+ | `styles` | `CalendarStyles` | - | Overall calendar styling |
95
+ | `eventStyles` | `CalendarEventStyles` | - | Event-specific styling |
96
+ | `unavailableStyles` | `UnavailableSlotStyles` | - | Unavailable slot styling |
97
+ | `onSlotPress` | `(hour, resourceId, date) => void` | - | Called when empty slot is pressed |
98
+ | `onEventPress` | `(event) => void` | - | Called when event is pressed |
99
+ | `onEventDrop` | `(droppedData, originalEvent) => void` | - | Called when event is dropped after dragging |
100
+ | `onResourcePress` | `(resource) => void` | - | Called when resource header is pressed |
101
+ | `renderEvent` | `(event, dimensions) => ReactNode` | - | Custom event renderer |
102
+ | `renderResourceHeader` | `(resource) => ReactNode` | - | Custom resource header renderer |
103
+ | `renderTimeSlot` | `(hour) => ReactNode` | - | Custom time slot renderer |
104
+ | `renderUnavailableSlot` | `(slot, dimensions) => ReactNode` | - | Custom unavailable slot renderer |
105
+ | `showHeader` | `boolean` | `true` | Show date header |
106
+ | `showTimeColumn` | `boolean` | `true` | Show time column |
107
+ | `showResourceHeaders` | `boolean` | `true` | Show resource headers |
108
+ | `enableHorizontalScroll` | `boolean` | `true` | Enable horizontal scrolling |
109
+ | `enableVerticalScroll` | `boolean` | `true` | Enable vertical scrolling |
110
+ | `enableDragAndDrop` | `boolean` | `false` | Enable drag and drop for events |
111
+ | `zoomEnabled` | `boolean` | `false` | Enable pinch-to-zoom gesture ⚠️ Experimental |
112
+ | `maxZoom` | `number` | `3` | Maximum zoom level (3 = 300%) |
113
+ | `initialZoom` | `number` | `1` | Initial zoom level (1 = 100%) |
114
+ | `snapBack` | `boolean` | `false` | Auto-return to initial zoom after gesture |
115
+ | `snapBackDelay` | `number` | `1000` | Delay before snap-back in milliseconds |
103
116
 
104
117
  ---
105
118
 
@@ -147,6 +160,98 @@ type CalendarDimensions = {
147
160
 
148
161
  ## Examples
149
162
 
163
+ ### Drag and Drop Events
164
+
165
+ Enable event rescheduling by long-pressing and dragging:
166
+
167
+ ```tsx
168
+ import { useState } from "react";
169
+ import { Calendar, CalendarEvent } from "react-native-calendar-resource";
170
+
171
+ function MyCalendar() {
172
+ const [events, setEvents] = useState<CalendarEvent[]>([
173
+ {
174
+ id: "evt1",
175
+ title: "Team Meeting",
176
+ resourceId: "1",
177
+ startHour: 10,
178
+ endHour: 11.5,
179
+ color: "#3b82f6",
180
+ },
181
+ ]);
182
+
183
+ return (
184
+ <Calendar
185
+ date={new Date()}
186
+ resources={resources}
187
+ events={events}
188
+ enableDragAndDrop={true}
189
+ onEventDrop={(droppedData, originalEvent) => {
190
+ // Update event with new time/resource
191
+ setEvents((prev) =>
192
+ prev.map((event) =>
193
+ event.id === droppedData.eventId
194
+ ? {
195
+ ...event,
196
+ resourceId: droppedData.newResourceId,
197
+ startHour: droppedData.newStartHour,
198
+ endHour: droppedData.newEndHour,
199
+ }
200
+ : event,
201
+ ),
202
+ );
203
+
204
+ console.log("Event moved:", {
205
+ from: {
206
+ resource: originalEvent.resourceId,
207
+ time: `${originalEvent.startHour}-${originalEvent.endHour}`,
208
+ },
209
+ to: {
210
+ resource: droppedData.newResourceId,
211
+ time: `${droppedData.newStartHour}-${droppedData.newEndHour}`,
212
+ },
213
+ });
214
+ }}
215
+ />
216
+ );
217
+ }
218
+ ```
219
+
220
+ **How it works:**
221
+
222
+ - Long-press an event for 500ms to start dragging
223
+ - Drag the event to a new resource or time slot
224
+ - Events snap to 30-minute intervals
225
+ - Visual feedback shows the event being dragged (opacity, scale, border)
226
+ - The `onEventDrop` callback receives both the new position and original event data
227
+
228
+ ### Pinch to Zoom
229
+
230
+ > **⚠️ EXPERIMENTAL FEATURE**: Pinch-to-zoom is currently experimental and may have performance or gesture detection issues. We recommend waiting for a stable release before using in production.
231
+
232
+ Enable pinch-to-zoom for better visibility of events:
233
+
234
+ ```tsx
235
+ <Calendar
236
+ date={new Date()}
237
+ resources={resources}
238
+ events={events}
239
+ zoomEnabled={true}
240
+ maxZoom={3}
241
+ initialZoom={1}
242
+ snapBack={true}
243
+ snapBackDelay={1000}
244
+ />
245
+ ```
246
+
247
+ **How it works:**
248
+
249
+ - Pinch with two fingers to zoom in/out (min: 1x, max: configurable)
250
+ - Zoom centers on the focal point between your fingers
251
+ - Scroll position adjusts automatically to keep content in view
252
+ - Optional snap-back returns to initial zoom after a delay
253
+ - Smooth animations for zoom transitions
254
+
150
255
  ### Custom Time Range
151
256
 
152
257
  ```tsx
@@ -295,12 +400,6 @@ interface BookingData {
295
400
 
296
401
  MIT
297
402
 
298
- - The tab bar remains visible while navigating deeper into Explore-related screens
299
- - Navigation context is preserved
300
- - Platform-native gestures and transitions are maintained
301
-
302
- Headers are disabled at the stack level and selectively enabled per screen to match the Figma design and avoid default iOS -Liquid Glass- system back button styling.
303
-
304
403
  ---
305
404
 
306
405
  **Author:** Abdelrahman Sobhy
@@ -1,3 +1,3 @@
1
1
  import { CalendarProps } from "../../types/calendar";
2
- export declare function Calendar<TEvent = any, TResource = any, TUnavailable = any>({ date, resources, events, unavailableSlots, timeConfig, dimensions, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onResourcePress, onDateChange, minDate, maxDate, allowPastDates, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader, showTimeColumn, showResourceHeaders, enableHorizontalScroll, enableVerticalScroll, showDateNavigation, dateFormat, accessibilityLabel, }: CalendarProps<TEvent, TResource, TUnavailable>): import("react").JSX.Element;
2
+ export declare function Calendar({ date, resources, events, unavailableSlots, timeConfig, dimensions, zoomEnabled, maxZoom, initialZoom, snapBack, snapBackDelay, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onEventDrop, onResourcePress, onDateChange, minDate, maxDate, allowPastDates, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader, showTimeColumn, showResourceHeaders, enableHorizontalScroll, enableVerticalScroll, showDateNavigation, enableDragAndDrop, dateFormat, accessibilityLabel, }: CalendarProps): import("react").JSX.Element;
3
3
  //# sourceMappingURL=Calendar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../../components/calendar/Calendar.tsx"],"names":[],"mappings":"AAOA,OAAO,EAEL,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAoB9B,wBAAgB,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,EAC1E,IAAI,EACJ,SAAS,EACT,MAAM,EACN,gBAAqB,EACrB,UAAU,EACV,UAAU,EACV,MAAM,EACN,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAsB,EACtB,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,UAAiB,EACjB,cAAqB,EACrB,mBAA0B,EAC1B,sBAA6B,EAC7B,oBAA2B,EAC3B,kBAAyB,EACzB,UAA+B,EAC/B,kBAA+B,GAChC,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,+BA0IhD"}
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../../components/calendar/Calendar.tsx"],"names":[],"mappings":"AAaA,OAAO,EAEL,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAoB9B,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,SAAS,EACT,MAAM,EACN,gBAAqB,EACrB,UAAU,EACV,UAAU,EACV,WAAmB,EACnB,OAAW,EACX,WAAe,EACf,QAAgB,EAChB,aAAoB,EACpB,MAAM,EACN,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAsB,EACtB,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,UAAiB,EACjB,cAAqB,EACrB,mBAA0B,EAC1B,sBAA6B,EAC7B,oBAA2B,EAC3B,kBAAyB,EACzB,iBAAyB,EACzB,UAA+B,EAC/B,kBAA+B,GAChC,EAAE,aAAa,+BA8Pf"}
@@ -1,5 +1,7 @@
1
- import { useMemo, useRef } from "react";
2
- import { ScrollView, View, } from "react-native";
1
+ import { useMemo, useRef, useState, useEffect } from "react";
2
+ import { View, } from "react-native";
3
+ import { Gesture, GestureDetector, ScrollView } from "react-native-gesture-handler";
4
+ import Animated, { useSharedValue, useAnimatedReaction, runOnJS, withTiming, } from "react-native-reanimated";
3
5
  import { CalendarHeader } from "./CalendarHeader";
4
6
  import { EventsLayer } from "./EventsLayer";
5
7
  import { GridBody } from "./GridBody";
@@ -16,22 +18,111 @@ const DEFAULT_TIME_CONFIG = {
16
18
  endHour: 24,
17
19
  timeFormat: "HH:mm",
18
20
  };
19
- export function Calendar({ date, resources, events, unavailableSlots = [], timeConfig, dimensions, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onResourcePress, onDateChange, minDate, maxDate, allowPastDates = false, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader = true, showTimeColumn = true, showResourceHeaders = true, enableHorizontalScroll = true, enableVerticalScroll = true, showDateNavigation = true, dateFormat = "EEE, MMM d, yyyy", accessibilityLabel = "Calendar", }) {
21
+ export function Calendar({ date, resources, events, unavailableSlots = [], timeConfig, dimensions, zoomEnabled = false, maxZoom = 3, initialZoom = 1, snapBack = false, snapBackDelay = 1000, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onEventDrop, onResourcePress, onDateChange, minDate, maxDate, allowPastDates = false, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader = true, showTimeColumn = true, showResourceHeaders = true, enableHorizontalScroll = true, enableVerticalScroll = true, showDateNavigation = true, enableDragAndDrop = false, dateFormat = "EEE, MMM d, yyyy", accessibilityLabel = "Calendar", }) {
20
22
  const headerScrollRef = useRef(null);
21
23
  const timeColumnScrollRef = useRef(null);
24
+ const horizontalScrollRef = useRef(null);
25
+ const verticalScrollRef = useRef(null);
26
+ // Zoom state
27
+ const minZoom = 1;
28
+ const zoomScale = useSharedValue(initialZoom);
29
+ const savedZoomScale = useSharedValue(initialZoom);
30
+ const [currentZoom, setCurrentZoom] = useState(initialZoom);
31
+ const [isPinching, setIsPinching] = useState(false);
32
+ const snapBackTimerRef = useRef(null);
33
+ const focalX = useRef(0);
34
+ const focalY = useRef(0);
35
+ const lastScrollX = useRef(0);
36
+ const lastScrollY = useRef(0);
37
+ // Update state when zoom changes
38
+ useAnimatedReaction(() => zoomScale.value, (zoom) => {
39
+ runOnJS(setCurrentZoom)(zoom);
40
+ });
41
+ // Adjust scroll position for focal point zooming
42
+ const prevZoom = useRef(initialZoom);
43
+ useEffect(() => {
44
+ if (currentZoom !== prevZoom.current && horizontalScrollRef.current && verticalScrollRef.current) {
45
+ const zoomRatio = currentZoom / prevZoom.current;
46
+ // Calculate new scroll position to keep focal point in place
47
+ horizontalScrollRef.current.scrollTo({
48
+ x: (lastScrollX.current + focalX.current) * zoomRatio - focalX.current,
49
+ animated: false,
50
+ });
51
+ verticalScrollRef.current.scrollTo({
52
+ y: (lastScrollY.current + focalY.current) * zoomRatio - focalY.current,
53
+ animated: false,
54
+ });
55
+ lastScrollX.current = (lastScrollX.current + focalX.current) * zoomRatio - focalX.current;
56
+ lastScrollY.current = (lastScrollY.current + focalY.current) * zoomRatio - focalY.current;
57
+ prevZoom.current = currentZoom;
58
+ }
59
+ }, [currentZoom]);
22
60
  // Merge configurations with defaults
23
- const finalDimensions = useMemo(() => ({ ...DEFAULT_DIMENSIONS, ...dimensions }), [dimensions]);
61
+ const baseDimensions = useMemo(() => ({ ...DEFAULT_DIMENSIONS, ...dimensions }), [dimensions]);
24
62
  const finalTimeConfig = useMemo(() => ({ ...DEFAULT_TIME_CONFIG, ...timeConfig }), [timeConfig]);
63
+ // Apply zoom to dimensions - recalculate on zoom change
64
+ const finalDimensions = useMemo(() => ({
65
+ resourceWidth: baseDimensions.resourceWidth * currentZoom,
66
+ hourHeight: baseDimensions.hourHeight * currentZoom,
67
+ }), [baseDimensions, currentZoom]);
25
68
  // Calculate grid dimensions
26
69
  const gridTotalWidth = resources.length * finalDimensions.resourceWidth;
27
70
  const totalHours = finalTimeConfig.endHour - finalTimeConfig.startHour;
28
71
  const gridTotalHeight = totalHours * finalDimensions.hourHeight;
72
+ // Callbacks to update focal point (avoid accessing refs in worklets)
73
+ const updateFocalPoint = (x, y) => {
74
+ focalX.current = x;
75
+ focalY.current = y;
76
+ };
77
+ // Pinch gesture for zoom with focal point
78
+ const pinchGesture = Gesture.Pinch()
79
+ .enabled(zoomEnabled)
80
+ .onBegin((e) => {
81
+ 'worklet';
82
+ // Clear any pending snap-back timer
83
+ if (snapBackTimerRef.current) {
84
+ clearTimeout(snapBackTimerRef.current);
85
+ snapBackTimerRef.current = null;
86
+ }
87
+ runOnJS(setIsPinching)(true);
88
+ runOnJS(updateFocalPoint)(e.focalX, e.focalY);
89
+ })
90
+ .onUpdate((e) => {
91
+ 'worklet';
92
+ const newScale = savedZoomScale.value * e.scale;
93
+ const clampedScale = Math.max(minZoom, Math.min(maxZoom, newScale));
94
+ // Update zoom
95
+ zoomScale.value = clampedScale;
96
+ // Store focal point
97
+ runOnJS(updateFocalPoint)(e.focalX, e.focalY);
98
+ })
99
+ .onEnd(() => {
100
+ 'worklet';
101
+ savedZoomScale.value = zoomScale.value;
102
+ runOnJS(setIsPinching)(false);
103
+ // Snap back with smooth animation
104
+ if (snapBack && zoomScale.value !== initialZoom) {
105
+ snapBackTimerRef.current = setTimeout(() => {
106
+ zoomScale.value = withTiming(initialZoom, {
107
+ duration: 400,
108
+ // Use easeOut for smoother deceleration
109
+ });
110
+ savedZoomScale.value = initialZoom;
111
+ }, snapBackDelay);
112
+ }
113
+ })
114
+ .onFinalize(() => {
115
+ 'worklet';
116
+ runOnJS(setIsPinching)(false);
117
+ });
29
118
  const handleHorizontalScroll = (scrollEvent) => {
30
119
  const horizontalOffset = scrollEvent.nativeEvent.contentOffset.x;
120
+ lastScrollX.current = horizontalOffset;
31
121
  headerScrollRef.current?.scrollTo({ x: horizontalOffset, animated: false });
32
122
  };
33
123
  const handleVerticalScroll = (scrollEvent) => {
34
124
  const verticalOffset = scrollEvent.nativeEvent.contentOffset.y;
125
+ lastScrollY.current = verticalOffset;
35
126
  timeColumnScrollRef.current?.scrollTo({
36
127
  y: verticalOffset,
37
128
  animated: false,
@@ -47,22 +138,26 @@ export function Calendar({ date, resources, events, unavailableSlots = [], timeC
47
138
  {showTimeColumn && (<TimeColumn scrollRef={timeColumnScrollRef} gridTotalHeight={gridTotalHeight} timeConfig={finalTimeConfig} dimensions={finalDimensions} renderTimeSlot={renderTimeSlot} styles={styles}/>)}
48
139
 
49
140
  <View style={{ flex: 1 }}>
50
- <ScrollView horizontal={enableHorizontalScroll} showsHorizontalScrollIndicator={enableHorizontalScroll} bounces={false} onScroll={handleHorizontalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableHorizontalScroll}>
51
- <ScrollView showsVerticalScrollIndicator={enableVerticalScroll} bounces={false} onScroll={handleVerticalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableVerticalScroll} contentContainerStyle={{
141
+ <GestureDetector gesture={pinchGesture}>
142
+ <Animated.View style={{ flex: 1 }}>
143
+ <ScrollView ref={horizontalScrollRef} horizontal={enableHorizontalScroll} showsHorizontalScrollIndicator={enableHorizontalScroll} bounces={false} onScroll={handleHorizontalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableHorizontalScroll && !isPinching}>
144
+ <ScrollView ref={verticalScrollRef} showsVerticalScrollIndicator={enableVerticalScroll} bounces={false} onScroll={handleVerticalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableVerticalScroll && !isPinching} contentContainerStyle={{
52
145
  width: gridTotalWidth,
53
146
  height: gridTotalHeight,
54
147
  }}>
55
- <View style={{
148
+ <View style={{
56
149
  position: "relative",
57
150
  width: gridTotalWidth,
58
151
  height: gridTotalHeight,
59
152
  }}>
60
- <GridBody resources={resources} events={events} unavailableSlots={unavailableSlots} onSlotPress={onSlotPress} date={date} timeConfig={finalTimeConfig} dimensions={finalDimensions} styles={styles}/>
61
- <UnavailableLayer unavailableSlots={unavailableSlots} resources={resources} timeConfig={finalTimeConfig} dimensions={finalDimensions} unavailableStyles={unavailableStyles} renderUnavailableSlot={renderUnavailableSlot}/>
62
- <EventsLayer events={events} resources={resources} onEventPress={onEventPress} timeConfig={finalTimeConfig} dimensions={finalDimensions} eventStyles={eventStyles} renderEvent={renderEvent}/>
63
- </View>
64
- </ScrollView>
65
- </ScrollView>
153
+ <GridBody resources={resources} events={events} unavailableSlots={unavailableSlots} onSlotPress={onSlotPress} date={date} timeConfig={finalTimeConfig} dimensions={finalDimensions} styles={styles}/>
154
+ <UnavailableLayer unavailableSlots={unavailableSlots} resources={resources} timeConfig={finalTimeConfig} dimensions={finalDimensions} unavailableStyles={unavailableStyles} renderUnavailableSlot={renderUnavailableSlot}/>
155
+ <EventsLayer events={events} resources={resources} onEventPress={onEventPress} onEventDrop={onEventDrop} timeConfig={finalTimeConfig} dimensions={finalDimensions} eventStyles={eventStyles} renderEvent={renderEvent} enableDragAndDrop={enableDragAndDrop}/>
156
+ </View>
157
+ </ScrollView>
158
+ </ScrollView>
159
+ </Animated.View>
160
+ </GestureDetector>
66
161
  </View>
67
162
  </View>
68
163
  </View>);
@@ -1 +1 @@
1
- {"version":3,"file":"CalendarHeader.d.ts","sourceRoot":"","sources":["../../../components/calendar/CalendarHeader.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,kBAAyB,EACzB,UAA+B,EAC/B,OAAO,EACP,OAAO,EACP,cAAqB,GACtB,EAAE,mBAAmB,+BA4IrB"}
1
+ {"version":3,"file":"CalendarHeader.d.ts","sourceRoot":"","sources":["../../../components/calendar/CalendarHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,kBAAyB,EACzB,UAA+B,EAC/B,OAAO,EACP,OAAO,EACP,cAAqB,GACtB,EAAE,mBAAmB,+BAoIrB"}
@@ -1,4 +1,3 @@
1
- import { MaterialIcons } from "@expo/vector-icons";
2
1
  import { addDays, format, subDays } from "date-fns";
3
2
  import { Pressable, Text, View } from "react-native";
4
3
  export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigation = true, dateFormat = "EEE, MMM d, yyyy", minDate, maxDate, allowPastDates = true, }) {
@@ -90,7 +89,7 @@ export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigat
90
89
  justifyContent: "center",
91
90
  backgroundColor: "rgba(38, 38, 38, 0.75)",
92
91
  }}>
93
- <MaterialIcons name="chevron-left" size={20} color={prevDisabled ? "#626262" : "#fefefe"}/>
92
+ <Text style={{ color: prevDisabled ? "#626262" : "#fefefe", fontSize: 24, fontWeight: "bold" }}>‹</Text>
94
93
  </Pressable>
95
94
  <Text style={{ color: "#fefefe", fontSize: 16, fontWeight: "600" }}>
96
95
  {format(date, dateFormat)}
@@ -103,7 +102,7 @@ export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigat
103
102
  alignItems: "center",
104
103
  justifyContent: "center",
105
104
  }}>
106
- <MaterialIcons name="chevron-right" size={20} color={nextDisabled ? "#626262" : "#fefefe"}/>
105
+ <Text style={{ color: nextDisabled ? "#626262" : "#fefefe", fontSize: 24, fontWeight: "bold" }}>›</Text>
107
106
  </Pressable>
108
107
  </>) : (<Text style={{ color: '#fefefe', fontSize: 16, fontWeight: '600' }}>
109
108
  {format(date, dateFormat)}
@@ -1,13 +1,15 @@
1
- import { CalendarDimensions, CalendarEvent, CalendarEventStyles, CalendarTimeConfig, RenderEventFunction, Resource } from "../../types/calendar";
2
- type EventsLayerProps<TEvent = any, TResource = any> = {
3
- events: CalendarEvent<TEvent>[];
4
- resources: Resource<TResource>[];
5
- onEventPress?: (event: CalendarEvent<TEvent>) => void;
1
+ import { CalendarDimensions, CalendarEvent, CalendarEventStyles, CalendarTimeConfig, DroppedEventData, RenderEventFunction, Resource } from "../../types/calendar";
2
+ type EventsLayerProps = {
3
+ events: CalendarEvent[];
4
+ resources: Resource[];
5
+ onEventPress?: (event: CalendarEvent) => void;
6
+ onEventDrop?: (droppedData: DroppedEventData, originalEvent: CalendarEvent) => void;
6
7
  timeConfig: CalendarTimeConfig;
7
8
  dimensions: CalendarDimensions;
8
9
  eventStyles?: CalendarEventStyles;
9
- renderEvent?: RenderEventFunction<TEvent>;
10
+ renderEvent?: RenderEventFunction;
11
+ enableDragAndDrop?: boolean;
10
12
  };
11
- export declare function EventsLayer<TEvent = any, TResource = any>({ events, resources, onEventPress, timeConfig, dimensions, eventStyles, renderEvent, }: EventsLayerProps<TEvent, TResource>): import("react").JSX.Element;
13
+ export declare function EventsLayer({ events, resources, onEventPress, onEventDrop, timeConfig, dimensions, eventStyles, renderEvent, enableDragAndDrop, }: EventsLayerProps): import("react").JSX.Element;
12
14
  export {};
13
15
  //# sourceMappingURL=EventsLayer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EventsLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/EventsLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,gBAAgB,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,IAAI;IACrD,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACtD,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,WAAW,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAgB,WAAW,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,EACzD,MAAM,EACN,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,GACZ,EAAE,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,+BA+GrC"}
1
+ {"version":3,"file":"EventsLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/EventsLayer.tsx"],"names":[],"mappings":"AASA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,WAAW,CAAC,EAAE,CACZ,WAAW,EAAE,gBAAgB,EAC7B,aAAa,EAAE,aAAa,KACzB,IAAI,CAAC;IACV,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,iBAAyB,GAC1B,EAAE,gBAAgB,+BA6RlB"}
@@ -1,6 +1,10 @@
1
- import { useMemo } from "react";
1
+ import { useMemo, useState, useEffect } from "react";
2
2
  import { Pressable, Text, View } from "react-native";
3
- export function EventsLayer({ events, resources, onEventPress, timeConfig, dimensions, eventStyles, renderEvent, }) {
3
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
+ import Animated, { useAnimatedStyle, useSharedValue, withSpring, runOnJS, } from "react-native-reanimated";
5
+ export function EventsLayer({ events, resources, onEventPress, onEventDrop, timeConfig, dimensions, eventStyles, renderEvent, enableDragAndDrop = false, }) {
6
+ const [draggedEventId, setDraggedEventId] = useState(null);
7
+ const [dropPreview, setDropPreview] = useState(null);
4
8
  const positionedEvents = useMemo(() => {
5
9
  return events
6
10
  .map((event) => {
@@ -25,7 +29,97 @@ export function EventsLayer({ events, resources, onEventPress, timeConfig, dimen
25
29
  const gridTotalWidth = resources.length * dimensions.resourceWidth;
26
30
  const gridBodyHeight = (timeConfig.endHour - timeConfig.startHour - 1) * dimensions.hourHeight;
27
31
  const handleEventPress = (event) => {
28
- onEventPress?.(event);
32
+ if (!draggedEventId) {
33
+ onEventPress?.(event);
34
+ }
35
+ };
36
+ const updateDropPreview = (event, currentX, currentY, blockWidth, blockHeight, eventColor) => {
37
+ "worklet";
38
+ // Calculate snapped position
39
+ const newResourceIndex = Math.max(0, Math.min(Math.floor(currentX / dimensions.resourceWidth), resources.length - 1));
40
+ const hourOffset = currentY / dimensions.hourHeight;
41
+ const eventDuration = event.endHour - event.startHour;
42
+ let newStartHour = timeConfig.startHour + hourOffset;
43
+ // Snap to whole hour cells
44
+ newStartHour = Math.round(newStartHour);
45
+ // Clamp to valid range
46
+ newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
47
+ const snappedTop = (newStartHour - timeConfig.startHour - 1) * dimensions.hourHeight;
48
+ const snappedLeft = newResourceIndex * dimensions.resourceWidth;
49
+ runOnJS(setDropPreview)({
50
+ top: snappedTop,
51
+ left: snappedLeft,
52
+ width: blockWidth,
53
+ height: blockHeight,
54
+ color: eventColor,
55
+ });
56
+ };
57
+ const handleEventDrop = (event, finalX, finalY) => {
58
+ "worklet";
59
+ // Calculate new resource and time
60
+ const newResourceIndex = Math.max(0, Math.min(Math.floor(finalX / dimensions.resourceWidth), resources.length - 1));
61
+ const newResource = resources[newResourceIndex];
62
+ const hourOffset = finalY / dimensions.hourHeight;
63
+ const eventDuration = event.endHour - event.startHour;
64
+ let newStartHour = timeConfig.startHour + hourOffset;
65
+ // Snap to whole hour cells
66
+ newStartHour = Math.round(newStartHour);
67
+ // Clamp to valid range
68
+ newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
69
+ const newEndHour = newStartHour + eventDuration;
70
+ // Only trigger callback if position changed significantly
71
+ if (newResource.id !== event.resourceId ||
72
+ Math.abs(newStartHour - event.startHour) > 0.1) {
73
+ runOnJS(onEventDrop)({
74
+ eventId: event.id,
75
+ newResourceId: newResource.id,
76
+ newStartHour,
77
+ newEndHour,
78
+ }, event);
79
+ }
80
+ runOnJS(setDraggedEventId)(null);
81
+ runOnJS(setDropPreview)(null);
82
+ };
83
+ const createGesture = (event, layoutX, layoutY, translateX, translateY, blockWidth, blockHeight, eventColor) => {
84
+ const longPress = Gesture.LongPress()
85
+ .minDuration(500)
86
+ .onStart(() => {
87
+ if (enableDragAndDrop) {
88
+ runOnJS(setDraggedEventId)(event.id);
89
+ }
90
+ });
91
+ const pan = Gesture.Pan()
92
+ .enabled(enableDragAndDrop)
93
+ .activeOffsetX([-5, 5])
94
+ .activeOffsetY([-5, 5])
95
+ .onUpdate((e) => {
96
+ translateX.value = e.translationX;
97
+ translateY.value = e.translationY;
98
+ // Update preview position
99
+ const currentX = layoutX + e.translationX;
100
+ const currentY = layoutY + e.translationY;
101
+ updateDropPreview(event, currentX, currentY, blockWidth, blockHeight, eventColor);
102
+ })
103
+ .onEnd(() => {
104
+ const finalX = layoutX + translateX.value;
105
+ const finalY = layoutY + translateY.value;
106
+ // Calculate snapped position
107
+ const newResourceIndex = Math.max(0, Math.min(Math.floor(finalX / dimensions.resourceWidth), resources.length - 1));
108
+ const hourOffset = finalY / dimensions.hourHeight;
109
+ const eventDuration = event.endHour - event.startHour;
110
+ let newStartHour = timeConfig.startHour + hourOffset;
111
+ newStartHour = Math.round(newStartHour);
112
+ newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
113
+ const snappedTop = (newStartHour - timeConfig.startHour - 1) * dimensions.hourHeight;
114
+ const snappedLeft = newResourceIndex * dimensions.resourceWidth;
115
+ // Snap instantly to final position and KEEP it there
116
+ translateX.value = snappedLeft - layoutX;
117
+ translateY.value = snappedTop - layoutY;
118
+ // Trigger the drop callback - React will re-render with new position
119
+ handleEventDrop(event, finalX, finalY);
120
+ // Don't reset - let React take over when it re-renders with updated event data
121
+ });
122
+ return Gesture.Simultaneous(longPress, pan);
29
123
  };
30
124
  const defaultColor = eventStyles?.defaultColor || "#3b82f6";
31
125
  const borderRadius = eventStyles?.borderRadius || 8;
@@ -38,42 +132,96 @@ export function EventsLayer({ events, resources, onEventPress, timeConfig, dimen
38
132
  width: gridTotalWidth,
39
133
  height: gridBodyHeight,
40
134
  }} pointerEvents="box-none">
135
+ {/* Drop preview ghost */}
136
+ {dropPreview && (<View style={{
137
+ position: "absolute",
138
+ top: dropPreview.top,
139
+ left: dropPreview.left,
140
+ width: dropPreview.width,
141
+ height: dropPreview.height,
142
+ backgroundColor: dropPreview.color,
143
+ opacity: 0.3,
144
+ borderRadius,
145
+ borderWidth: 2,
146
+ borderColor: "#ffffff",
147
+ borderStyle: "dashed",
148
+ zIndex: 5,
149
+ }}/>)}
150
+
41
151
  {positionedEvents.map((item) => {
42
152
  const eventColor = item.event.color || defaultColor;
43
- return (<View key={item.event.id} style={{
44
- position: "absolute",
45
- justifyContent: "center",
46
- alignItems: "center",
47
- backgroundColor: "#1a1a1a",
48
- top: item.topPosition,
49
- left: item.leftPosition,
50
- height: item.blockHeight,
51
- width: item.blockWidth,
52
- }}>
53
- <Pressable onPress={() => handleEventPress(item.event)} style={{
54
- height: item.blockHeight - 2,
55
- width: item.blockWidth - 2,
56
- backgroundColor: eventColor,
57
- borderRadius,
58
- opacity,
59
- justifyContent: "center",
60
- alignItems: "center",
61
- padding: 8,
62
- overflow: "hidden",
63
- }}>
64
- {renderEvent ? (renderEvent(item.event, {
65
- width: item.blockWidth - 2,
66
- height: item.blockHeight - 2,
67
- })) : (<Text style={{
68
- color: "#ffffff",
69
- fontSize: 14,
70
- fontWeight: "600",
71
- textAlign: "center",
72
- }} numberOfLines={2}>
73
- {item.event.title}
74
- </Text>)}
75
- </Pressable>
76
- </View>);
153
+ const isDragging = draggedEventId === item.event.id;
154
+ return (<EventBlock key={item.event.id} event={item.event} topPosition={item.topPosition} leftPosition={item.leftPosition} blockHeight={item.blockHeight} blockWidth={item.blockWidth} eventColor={eventColor} isDragging={isDragging} borderRadius={borderRadius} opacity={opacity} enableDragAndDrop={enableDragAndDrop} onPress={handleEventPress} createGesture={createGesture} renderEvent={renderEvent} updateDropPreview={updateDropPreview}/>);
77
155
  })}
78
156
  </View>);
79
157
  }
158
+ function EventBlock({ event, topPosition, leftPosition, blockHeight, blockWidth, eventColor, isDragging, borderRadius, opacity, enableDragAndDrop, onPress, createGesture, renderEvent, updateDropPreview, }) {
159
+ const translateX = useSharedValue(0);
160
+ const translateY = useSharedValue(0);
161
+ const scale = useSharedValue(1);
162
+ // Reset translation when position changes (event data updated)
163
+ // Use the event's position as a key to detect changes
164
+ const positionKey = `${topPosition}-${leftPosition}`;
165
+ const prevPositionKey = useSharedValue(positionKey);
166
+ useEffect(() => {
167
+ if (prevPositionKey.value !== positionKey && !isDragging) {
168
+ translateX.value = 0;
169
+ translateY.value = 0;
170
+ prevPositionKey.value = positionKey;
171
+ }
172
+ }, [positionKey, isDragging, prevPositionKey, translateX, translateY]);
173
+ const gesture = enableDragAndDrop
174
+ ? createGesture(event, leftPosition, topPosition, translateX, translateY, blockWidth, blockHeight, eventColor)
175
+ : undefined;
176
+ const animatedStyle = useAnimatedStyle(() => ({
177
+ transform: [
178
+ { translateX: translateX.value },
179
+ { translateY: translateY.value },
180
+ { scale: isDragging ? withSpring(1.05) : withSpring(1) },
181
+ ],
182
+ }));
183
+ return (<Animated.View style={[
184
+ {
185
+ position: "absolute",
186
+ justifyContent: "center",
187
+ alignItems: "center",
188
+ backgroundColor: "#1a1a1a",
189
+ top: topPosition,
190
+ left: leftPosition,
191
+ height: blockHeight,
192
+ width: blockWidth,
193
+ zIndex: isDragging ? 1000 : 10,
194
+ },
195
+ animatedStyle,
196
+ ]}>
197
+ <GestureDetector gesture={gesture || Gesture.Tap()}>
198
+ <Animated.View style={{ width: "100%", height: "100%" }}>
199
+ <Pressable onPress={() => onPress(event)} style={{
200
+ height: blockHeight - 2,
201
+ width: blockWidth - 2,
202
+ backgroundColor: eventColor,
203
+ borderRadius,
204
+ opacity: isDragging ? 0.7 : opacity,
205
+ justifyContent: "center",
206
+ alignItems: "center",
207
+ padding: 8,
208
+ overflow: "hidden",
209
+ borderWidth: isDragging ? 2 : 0,
210
+ borderColor: "#ffffff",
211
+ }}>
212
+ {renderEvent ? (renderEvent(event, {
213
+ width: blockWidth - 2,
214
+ height: blockHeight - 2,
215
+ })) : (<Text style={{
216
+ color: "#ffffff",
217
+ fontSize: 14,
218
+ fontWeight: "600",
219
+ textAlign: "center",
220
+ }} numberOfLines={2}>
221
+ {event.title}
222
+ </Text>)}
223
+ </Pressable>
224
+ </Animated.View>
225
+ </GestureDetector>
226
+ </Animated.View>);
227
+ }
@@ -1,14 +1,14 @@
1
1
  import { CalendarDimensions, CalendarEvent, CalendarStyles, CalendarTimeConfig, Resource, UnavailableSlot } from "../../types/calendar";
2
- type GridBodyProps<TEvent = any, TResource = any, TUnavailable = any> = {
3
- resources: Resource<TResource>[];
4
- events: CalendarEvent<TEvent>[];
5
- unavailableSlots: UnavailableSlot<TUnavailable>[];
2
+ type GridBodyProps = {
3
+ resources: Resource[];
4
+ events: CalendarEvent[];
5
+ unavailableSlots: UnavailableSlot[];
6
6
  onSlotPress?: (hour: number, resourceId: string, date: Date) => void;
7
7
  date: Date;
8
8
  timeConfig: CalendarTimeConfig;
9
9
  dimensions: CalendarDimensions;
10
10
  styles?: CalendarStyles;
11
11
  };
12
- export declare function GridBody<TEvent = any, TResource = any, TUnavailable = any>({ resources, events, unavailableSlots, onSlotPress, date, timeConfig, dimensions, styles, }: GridBodyProps<TEvent, TResource, TUnavailable>): import("react").JSX.Element;
12
+ export declare function GridBody({ resources, events, unavailableSlots, onSlotPress, date, timeConfig, dimensions, styles, }: GridBodyProps): import("react").JSX.Element;
13
13
  export {};
14
14
  //# sourceMappingURL=GridBody.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GridBody.d.ts","sourceRoot":"","sources":["../../../components/calendar/GridBody.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,IAAI;IACtE,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IACjC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,gBAAgB,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;IAClD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,EAC1E,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,IAAI,EACJ,UAAU,EACV,UAAU,EACV,MAAM,GACP,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,+BAgGhD"}
1
+ {"version":3,"file":"GridBody.d.ts","sourceRoot":"","sources":["../../../components/calendar/GridBody.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,IAAI,EACJ,UAAU,EACV,UAAU,EACV,MAAM,GACP,EAAE,aAAa,+BAmGf"}
@@ -1,6 +1,5 @@
1
- import { MaterialIcons } from "@expo/vector-icons";
2
1
  import { useMemo } from "react";
3
- import { Pressable, View } from "react-native";
2
+ import { Pressable, Text, View } from "react-native";
4
3
  export function GridBody({ resources, events, unavailableSlots, onSlotPress, date, timeConfig, dimensions, styles, }) {
5
4
  const hoursArray = useMemo(() => {
6
5
  const result = [];
@@ -62,7 +61,9 @@ export function GridBody({ resources, events, unavailableSlots, onSlotPress, dat
62
61
  justifyContent: "center",
63
62
  alignItems: "center",
64
63
  }}>
65
- <MaterialIcons name="add" size={14} color="#474747"/>
64
+ <Text style={{ color: "#474747", fontSize: 20, fontWeight: "300" }}>
65
+ +
66
+ </Text>
66
67
  </Pressable>);
67
68
  })}
68
69
  </View>))}
@@ -1,13 +1,13 @@
1
1
  import React from "react";
2
2
  import { ScrollView } from "react-native";
3
3
  import { CalendarDimensions, RenderResourceHeaderFunction, Resource } from "../../types/calendar";
4
- type ResourceHeadersProps<T = any> = {
5
- resources: Resource<T>[];
4
+ type ResourceHeadersProps = {
5
+ resources: Resource[];
6
6
  scrollRef: React.RefObject<ScrollView | null>;
7
7
  dimensions: CalendarDimensions;
8
- renderResourceHeader?: RenderResourceHeaderFunction<T>;
9
- onResourcePress?: (resource: Resource<T>) => void;
8
+ renderResourceHeader?: RenderResourceHeaderFunction;
9
+ onResourcePress?: (resource: Resource) => void;
10
10
  };
11
- export declare function ResourceHeaders<T = any>({ resources, scrollRef, dimensions, renderResourceHeader, onResourcePress, }: ResourceHeadersProps<T>): React.JSX.Element;
11
+ export declare function ResourceHeaders({ resources, scrollRef, dimensions, renderResourceHeader, onResourcePress, }: ResourceHeadersProps): React.JSX.Element;
12
12
  export {};
13
13
  //# sourceMappingURL=ResourceHeaders.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceHeaders.d.ts","sourceRoot":"","sources":["../../../components/calendar/ResourceHeaders.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAa,UAAU,EAAc,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,oBAAoB,CAAC,CAAC,GAAG,GAAG,IAAI;IACnC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACzB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC9C,UAAU,EAAE,kBAAkB,CAAC;IAC/B,oBAAoB,CAAC,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,GAAG,GAAG,EAAE,EACvC,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,eAAe,GAChB,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAiDzB"}
1
+ {"version":3,"file":"ResourceHeaders.d.ts","sourceRoot":"","sources":["../../../components/calendar/ResourceHeaders.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAa,UAAU,EAAc,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC9C,UAAU,EAAE,kBAAkB,CAAC;IAC/B,oBAAoB,CAAC,EAAE,4BAA4B,CAAC;IACpD,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,eAAe,GAChB,EAAE,oBAAoB,qBAiDtB"}
@@ -1,12 +1,12 @@
1
1
  import { CalendarDimensions, CalendarTimeConfig, RenderUnavailableSlotFunction, Resource, UnavailableSlot, UnavailableSlotStyles } from "../../types/calendar";
2
- type UnavailableLayerProps<TResource = any, TUnavailable = any> = {
3
- unavailableSlots: UnavailableSlot<TUnavailable>[];
4
- resources: Resource<TResource>[];
2
+ type UnavailableLayerProps = {
3
+ unavailableSlots: UnavailableSlot[];
4
+ resources: Resource[];
5
5
  timeConfig: CalendarTimeConfig;
6
6
  dimensions: CalendarDimensions;
7
7
  unavailableStyles?: UnavailableSlotStyles;
8
- renderUnavailableSlot?: RenderUnavailableSlotFunction<TUnavailable>;
8
+ renderUnavailableSlot?: RenderUnavailableSlotFunction;
9
9
  };
10
- export declare function UnavailableLayer<TResource = any, TUnavailable = any>({ unavailableSlots, resources, timeConfig, dimensions, unavailableStyles, renderUnavailableSlot, }: UnavailableLayerProps<TResource, TUnavailable>): import("react").JSX.Element;
10
+ export declare function UnavailableLayer<TResource = unknown, TUnavailable = unknown>({ unavailableSlots, resources, timeConfig, dimensions, unavailableStyles, renderUnavailableSlot, }: UnavailableLayerProps): import("react").JSX.Element;
11
11
  export {};
12
12
  //# sourceMappingURL=UnavailableLayer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"UnavailableLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/UnavailableLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,QAAQ,EACR,eAAe,EACf,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,qBAAqB,CAAC,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,IAAI;IAChE,gBAAgB,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;IAClD,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IACjC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,6BAA6B,CAAC,YAAY,CAAC,CAAC;CACrE,CAAC;AAuDF,wBAAgB,gBAAgB,CAAC,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,EACpE,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,GACtB,EAAE,qBAAqB,CAAC,SAAS,EAAE,YAAY,CAAC,+BAkIhD"}
1
+ {"version":3,"file":"UnavailableLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/UnavailableLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,QAAQ,EACR,eAAe,EACf,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,qBAAqB,GAAG;IAC3B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;CACvD,CAAC;AAuDF,wBAAgB,gBAAgB,CAAC,SAAS,GAAG,OAAO,EAAE,YAAY,GAAG,OAAO,EAAE,EAC5E,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,GACtB,EAAE,qBAAqB,+BAkIvB"}
@@ -0,0 +1,10 @@
1
+ interface ZoomControlsProps {
2
+ currentZoom: number;
3
+ minZoom: number;
4
+ maxZoom: number;
5
+ onZoomIn: () => void;
6
+ onZoomOut: () => void;
7
+ }
8
+ export declare function ZoomControls({ currentZoom, minZoom, maxZoom, onZoomIn, onZoomOut, }: ZoomControlsProps): import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=ZoomControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZoomControls.d.ts","sourceRoot":"","sources":["../../../components/calendar/ZoomControls.tsx"],"names":[],"mappings":"AAEA,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BA6BnB"}
@@ -0,0 +1,63 @@
1
+ import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
2
+ export function ZoomControls({ currentZoom, minZoom, maxZoom, onZoomIn, onZoomOut, }) {
3
+ const canZoomIn = currentZoom < maxZoom;
4
+ const canZoomOut = currentZoom > minZoom;
5
+ return (<View style={styles.container}>
6
+ <TouchableOpacity style={[styles.button, !canZoomOut && styles.buttonDisabled]} onPress={onZoomOut} disabled={!canZoomOut} accessibilityLabel="Zoom out" accessibilityRole="button">
7
+ <Text style={[styles.buttonText, !canZoomOut && styles.textDisabled]}>
8
+
9
+ </Text>
10
+ </TouchableOpacity>
11
+ <Text style={styles.zoomText}>{Math.round(currentZoom * 100)}%</Text>
12
+ <TouchableOpacity style={[styles.button, !canZoomIn && styles.buttonDisabled]} onPress={onZoomIn} disabled={!canZoomIn} accessibilityLabel="Zoom in" accessibilityRole="button">
13
+ <Text style={[styles.buttonText, !canZoomIn && styles.textDisabled]}>
14
+ +
15
+ </Text>
16
+ </TouchableOpacity>
17
+ </View>);
18
+ }
19
+ const styles = StyleSheet.create({
20
+ container: {
21
+ position: "absolute",
22
+ bottom: 20,
23
+ right: 20,
24
+ flexDirection: "row",
25
+ alignItems: "center",
26
+ backgroundColor: "#2a2a2a",
27
+ borderRadius: 8,
28
+ padding: 4,
29
+ elevation: 4,
30
+ shadowColor: "#000",
31
+ shadowOffset: { width: 0, height: 2 },
32
+ shadowOpacity: 0.3,
33
+ shadowRadius: 4,
34
+ zIndex: 1000,
35
+ },
36
+ button: {
37
+ width: 36,
38
+ height: 36,
39
+ justifyContent: "center",
40
+ alignItems: "center",
41
+ backgroundColor: "#3a3a3a",
42
+ borderRadius: 6,
43
+ },
44
+ buttonDisabled: {
45
+ opacity: 0.3,
46
+ },
47
+ buttonText: {
48
+ color: "#fff",
49
+ fontSize: 24,
50
+ fontWeight: "600",
51
+ },
52
+ textDisabled: {
53
+ color: "#666",
54
+ },
55
+ zoomText: {
56
+ color: "#fff",
57
+ fontSize: 14,
58
+ fontWeight: "500",
59
+ marginHorizontal: 12,
60
+ minWidth: 45,
61
+ textAlign: "center",
62
+ },
63
+ });
@@ -1,26 +1,29 @@
1
1
  import { ReactNode } from "react";
2
- export type Resource<T = any> = {
2
+ export type Resource = {
3
3
  id: string;
4
4
  label: string;
5
- data?: T;
6
5
  };
7
- export type CalendarEvent<T = any> = {
6
+ export type CalendarEvent = {
8
7
  id: string;
9
8
  title: string;
10
9
  resourceId: string;
11
10
  startHour: number;
12
11
  endHour: number;
13
12
  color?: string;
14
- data?: T;
13
+ };
14
+ export type DroppedEventData = {
15
+ eventId: string;
16
+ newResourceId: string;
17
+ newStartHour: number;
18
+ newEndHour: number;
15
19
  };
16
20
  export type UnavailableType = "reserved" | "offHours" | "disabled";
17
- export type UnavailableSlot<T = any> = {
21
+ export type UnavailableSlot = {
18
22
  id: string;
19
23
  resourceId: string;
20
24
  startHour: number;
21
25
  endHour: number;
22
26
  type: UnavailableType;
23
- data?: T;
24
27
  };
25
28
  export type CalendarDimensions = {
26
29
  /** Width of each resource column in pixels */
@@ -75,43 +78,50 @@ export type UnavailableSlotStyles = {
75
78
  opacity?: number;
76
79
  };
77
80
  };
78
- export type RenderEventFunction<T = any> = (event: CalendarEvent<T>, dimensions: {
81
+ export type RenderEventFunction = (event: CalendarEvent, dimensions: {
79
82
  width: number;
80
83
  height: number;
81
84
  }) => ReactNode;
82
- export type RenderResourceHeaderFunction<T = any> = (resource: Resource<T>) => ReactNode;
85
+ export type RenderResourceHeaderFunction = (resource: Resource) => ReactNode;
83
86
  export type RenderTimeSlotFunction = (hour: number) => ReactNode;
84
- export type RenderUnavailableSlotFunction<T = any> = (slot: UnavailableSlot<T>, dimensions: {
87
+ export type RenderUnavailableSlotFunction = (slot: UnavailableSlot, dimensions: {
85
88
  width: number;
86
89
  height: number;
87
90
  }) => ReactNode;
88
- export type CalendarProps<TEvent = any, TResource = any, TUnavailable = any> = {
91
+ export type CalendarProps = {
89
92
  date: Date;
90
- resources: Resource<TResource>[];
91
- events: CalendarEvent<TEvent>[];
92
- unavailableSlots?: UnavailableSlot<TUnavailable>[];
93
+ resources: Resource[];
94
+ events: CalendarEvent[];
95
+ unavailableSlots?: UnavailableSlot[];
93
96
  timeConfig?: CalendarTimeConfig;
94
97
  dimensions?: Partial<CalendarDimensions>;
95
98
  styles?: CalendarStyles;
96
99
  eventStyles?: CalendarEventStyles;
97
100
  unavailableStyles?: UnavailableSlotStyles;
101
+ zoomEnabled?: boolean;
102
+ maxZoom?: number;
103
+ initialZoom?: number;
104
+ snapBack?: boolean;
105
+ snapBackDelay?: number;
98
106
  onSlotPress?: (hour: number, resourceId: string, date: Date) => void;
99
- onEventPress?: (event: CalendarEvent<TEvent>) => void;
100
- onResourcePress?: (resource: Resource<TResource>) => void;
107
+ onEventPress?: (event: CalendarEvent) => void;
108
+ onResourcePress?: (resource: Resource) => void;
101
109
  onDateChange?: (date: Date) => void;
110
+ onEventDrop?: (droppedData: DroppedEventData, originalEvent: CalendarEvent) => void;
102
111
  minDate?: Date;
103
112
  maxDate?: Date;
104
113
  allowPastDates?: boolean;
105
- renderEvent?: RenderEventFunction<TEvent>;
106
- renderResourceHeader?: RenderResourceHeaderFunction<TResource>;
114
+ renderEvent?: RenderEventFunction;
115
+ renderResourceHeader?: RenderResourceHeaderFunction;
107
116
  renderTimeSlot?: RenderTimeSlotFunction;
108
- renderUnavailableSlot?: RenderUnavailableSlotFunction<TUnavailable>;
117
+ renderUnavailableSlot?: RenderUnavailableSlotFunction;
109
118
  showHeader?: boolean;
110
119
  showTimeColumn?: boolean;
111
120
  showResourceHeaders?: boolean;
112
121
  enableHorizontalScroll?: boolean;
113
122
  enableVerticalScroll?: boolean;
114
123
  showDateNavigation?: boolean;
124
+ enableDragAndDrop?: boolean;
115
125
  dateFormat?: string;
116
126
  accessibilityLabel?: string;
117
127
  };
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../types/calendar.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../types/calendar.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "react-native-calendar-resource",
3
- "version": "1.0.2",
3
+ "version": "1.1.2",
4
4
  "description": "A fully customizable, type-safe resource-based calendar component for React Native",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
- "build": "tsc",
8
+ "build": "tsc -p tsconfig.package.json",
9
+ "watch": "tsc -p tsconfig.package.json --watch",
9
10
  "prepublishOnly": "npm run build",
10
11
  "test": "echo \"No tests yet\" && exit 0"
11
12
  },
@@ -24,15 +25,29 @@
24
25
  "type": "git",
25
26
  "url": "https://github.com/abdosobhy1/react-native-calendar-resource"
26
27
  },
28
+ "homepage": "https://github.com/abdosobhy1/react-native-calendar-resource#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/abdosobhy1/react-native-calendar-resource/issues"
31
+ },
27
32
  "peerDependencies": {
28
33
  "react": ">=18.0.0",
29
34
  "react-native": ">=0.70.0",
30
- "date-fns": ">=3.0.0"
35
+ "date-fns": ">=3.0.0",
36
+ "react-native-gesture-handler": ">=2.9.0",
37
+ "react-native-reanimated": ">=2.14.0"
31
38
  },
32
39
  "devDependencies": {
33
40
  "@types/react": "^19.1.0",
34
41
  "@types/react-native": "^0.73.0",
42
+ "react": "^18.2.0",
43
+ "react-native": "^0.73.0",
44
+ "react-native-gesture-handler": "^2.14.0",
45
+ "react-native-reanimated": "^3.6.0",
35
46
  "typescript": "^5.9.2"
36
47
  },
37
- "files": ["dist", "README.md", "LICENSE"]
48
+ "files": [
49
+ "dist",
50
+ "README.md",
51
+ "LICENSE"
52
+ ]
38
53
  }
@@ -1,37 +0,0 @@
1
- /**
2
- * Native date utility functions to replace date-fns
3
- * Zero external dependencies
4
- */
5
- /**
6
- * Add days to a date
7
- */
8
- export declare function addDays(date: Date, days: number): Date;
9
- /**
10
- * Subtract days from a date
11
- */
12
- export declare function subDays(date: Date, days: number): Date;
13
- /**
14
- * Format a date according to a format string
15
- * Supports common tokens:
16
- * - EEE: Short weekday name (Mon, Tue, etc.)
17
- * - EEEE: Full weekday name (Monday, Tuesday, etc.)
18
- * - MMM: Short month name (Jan, Feb, etc.)
19
- * - MMMM: Full month name (January, February, etc.)
20
- * - MM: Month as 2 digits (01-12)
21
- * - M: Month as 1-2 digits (1-12)
22
- * - dd: Day as 2 digits (01-31)
23
- * - d: Day as 1-2 digits (1-31)
24
- * - yyyy: Full year (2026)
25
- * - yy: 2-digit year (26)
26
- * - HH: Hour in 24h format with leading zero (00-23)
27
- * - H: Hour in 24h format (0-23)
28
- * - hh: Hour in 12h format with leading zero (01-12)
29
- * - h: Hour in 12h format (1-12)
30
- * - mm: Minutes with leading zero (00-59)
31
- * - m: Minutes (0-59)
32
- * - ss: Seconds with leading zero (00-59)
33
- * - s: Seconds (0-59)
34
- * - a: AM/PM
35
- */
36
- export declare function formatDate(date: Date, formatString: string): string;
37
- //# sourceMappingURL=dateUtils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dateUtils.d.ts","sourceRoot":"","sources":["../../../components/calendar/dateUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAItD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAItD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CA+FnE"}
@@ -1,128 +0,0 @@
1
- /**
2
- * Native date utility functions to replace date-fns
3
- * Zero external dependencies
4
- */
5
- /**
6
- * Add days to a date
7
- */
8
- export function addDays(date, days) {
9
- const result = new Date(date);
10
- result.setDate(result.getDate() + days);
11
- return result;
12
- }
13
- /**
14
- * Subtract days from a date
15
- */
16
- export function subDays(date, days) {
17
- const result = new Date(date);
18
- result.setDate(result.getDate() - days);
19
- return result;
20
- }
21
- /**
22
- * Format a date according to a format string
23
- * Supports common tokens:
24
- * - EEE: Short weekday name (Mon, Tue, etc.)
25
- * - EEEE: Full weekday name (Monday, Tuesday, etc.)
26
- * - MMM: Short month name (Jan, Feb, etc.)
27
- * - MMMM: Full month name (January, February, etc.)
28
- * - MM: Month as 2 digits (01-12)
29
- * - M: Month as 1-2 digits (1-12)
30
- * - dd: Day as 2 digits (01-31)
31
- * - d: Day as 1-2 digits (1-31)
32
- * - yyyy: Full year (2026)
33
- * - yy: 2-digit year (26)
34
- * - HH: Hour in 24h format with leading zero (00-23)
35
- * - H: Hour in 24h format (0-23)
36
- * - hh: Hour in 12h format with leading zero (01-12)
37
- * - h: Hour in 12h format (1-12)
38
- * - mm: Minutes with leading zero (00-59)
39
- * - m: Minutes (0-59)
40
- * - ss: Seconds with leading zero (00-59)
41
- * - s: Seconds (0-59)
42
- * - a: AM/PM
43
- */
44
- export function formatDate(date, formatString) {
45
- const weekdaysShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
46
- const weekdaysFull = [
47
- "Sunday",
48
- "Monday",
49
- "Tuesday",
50
- "Wednesday",
51
- "Thursday",
52
- "Friday",
53
- "Saturday",
54
- ];
55
- const monthsShort = [
56
- "Jan",
57
- "Feb",
58
- "Mar",
59
- "Apr",
60
- "May",
61
- "Jun",
62
- "Jul",
63
- "Aug",
64
- "Sep",
65
- "Oct",
66
- "Nov",
67
- "Dec",
68
- ];
69
- const monthsFull = [
70
- "January",
71
- "February",
72
- "March",
73
- "April",
74
- "May",
75
- "June",
76
- "July",
77
- "August",
78
- "September",
79
- "October",
80
- "November",
81
- "December",
82
- ];
83
- const day = date.getDate();
84
- const month = date.getMonth();
85
- const year = date.getFullYear();
86
- const weekday = date.getDay();
87
- const hours = date.getHours();
88
- const minutes = date.getMinutes();
89
- const seconds = date.getSeconds();
90
- const pad = (num, size = 2) => {
91
- return num.toString().padStart(size, "0");
92
- };
93
- const tokens = {
94
- EEEE: weekdaysFull[weekday],
95
- EEE: weekdaysShort[weekday],
96
- MMMM: monthsFull[month],
97
- MMM: monthsShort[month],
98
- MM: pad(month + 1),
99
- M: (month + 1).toString(),
100
- dd: pad(day),
101
- d: day.toString(),
102
- yyyy: year.toString(),
103
- yy: year.toString().slice(-2),
104
- HH: pad(hours),
105
- H: hours.toString(),
106
- hh: pad(hours % 12 || 12),
107
- h: (hours % 12 || 12).toString(),
108
- mm: pad(minutes),
109
- m: minutes.toString(),
110
- ss: pad(seconds),
111
- s: seconds.toString(),
112
- a: hours >= 12 ? "PM" : "AM",
113
- };
114
- let result = formatString;
115
- // Sort by length descending to replace longer tokens first (e.g., MMMM before MMM)
116
- const sortedTokens = Object.keys(tokens).sort((a, b) => b.length - a.length);
117
- for (const token of sortedTokens) {
118
- // Use a unique placeholder to avoid re-replacement issues
119
- const placeholder = `\x00${token}\x00`;
120
- result = result.replace(new RegExp(token, "g"), placeholder);
121
- }
122
- // Replace placeholders with actual values
123
- for (const token of sortedTokens) {
124
- const placeholder = `\x00${token}\x00`;
125
- result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), tokens[token]);
126
- }
127
- return result;
128
- }