react-native-resource-calendar 1.0.18 β†’ 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,8 +42,7 @@ react-native-gesture-handler \
42
42
  react-native-reanimated \
43
43
  react-native-svg \
44
44
  @shopify/flash-list \
45
- @shopify/react-native-skia \
46
- expo-haptics
45
+ @shopify/react-native-skia
47
46
  ```
48
47
 
49
48
  If you’re using bare React Native (not Expo), install them manually:
@@ -54,8 +53,18 @@ react-native-gesture-handler \
54
53
  react-native-reanimated \
55
54
  react-native-svg \
56
55
  @shopify/flash-list \
57
- @shopify/react-native-skia \
58
- expo-haptics
56
+ @shopify/react-native-skia
57
+ ```
58
+
59
+ 🟦 Optional: Haptics Support (Expo Only)
60
+
61
+ Haptic feedback is optional.
62
+ If you want to enable vibration feedback when interacting with components, install the Expo Haptics package and set enableHapticFeedback to true in your component config.
63
+
64
+ πŸ“¦ Install (Expo)
65
+
66
+ ```bash
67
+ npx expo install expo-haptics
59
68
  ```
60
69
 
61
70
  ---
@@ -264,12 +273,14 @@ The `Calendar` component accepts a flexible set of props for customizing layout,
264
273
  | **`snapIntervalInMinutes`** | `number` | `5` | Drag/resize snapping granularity (in minutes). |
265
274
  | **`overLappingLayoutMode`** | `LayoutMode` (`'stacked' \| 'columns'`) | `'stacked'` | Strategy to lay out overlapping events inside a column. |
266
275
  | **`theme`** | `CalendarTheme` | β€” | Typography & palette overrides. |
276
+ | **`enableHapticFeedback`** | `boolean` | `false` | Enable haptic feedback. |
267
277
  | **`eventSlots`** | `EventSlots` | β€” | Slot renderers to customize event content (e.g. `{ Body, TopRight }`). |
268
278
  | **`eventStyleOverrides`** | `StyleOverrides \| ((event: Event) => StyleOverrides \| undefined)` | β€” | Per-event style override (object or function). |
269
279
  | **`isEventSelected`** | `(event: Event) => boolean` | `() => false` | Marks which events are currently selected. |
270
280
  | **`isEventDisabled`** | `(event: Event) => boolean` | `() => false` | Marks events as disabled (non-interactive). |
271
281
  | **`onResourcePress`** | `(resource: Resource) => void` | β€” | Invoked when a resource header is pressed. |
272
282
  | **`onBlockLongPress`** | `(resource: Resource, date: Date) => void` | β€” | Long-press on an empty block (grid). |
283
+ | **`onBlockTap`** | `(resource: Resource, date: Date) => void` | β€” | Tap on an empty block (grid). |
273
284
  | **`onDisabledBlockPress`** | `(block: DisabledBlock) => void` | β€” | Tap on a disabled block (e.g., lunch). |
274
285
  | **`onEventPress`** | `(event: Event) => void` | β€” | Tap on an event. |
275
286
  | **`onEventLongPress`** | `(event: Event) => void` | β€” | Long-press on an event. The calendar also preps internal drag state here. |
package/dist/index.d.mts CHANGED
@@ -86,9 +86,11 @@ interface CalendarProps {
86
86
  hourHeight?: number;
87
87
  onResourcePress?: (resource: Resource) => void;
88
88
  onBlockLongPress?: (resource: Resource, date: Date) => void;
89
+ onBlockTap?: (resource: Resource, date: Date) => void;
89
90
  onDisabledBlockPress?: (block: DisabledBlock) => void;
90
91
  onEventPress?: (event: Event) => void;
91
92
  onEventLongPress?: (event: Event) => void;
93
+ enableHapticFeedback?: boolean;
92
94
  eventSlots?: EventSlots;
93
95
  eventStyleOverrides?: StyleOverrides | ((event: Event) => StyleOverrides | undefined);
94
96
  isEventSelected?: FlagFn;
package/dist/index.d.ts CHANGED
@@ -86,9 +86,11 @@ interface CalendarProps {
86
86
  hourHeight?: number;
87
87
  onResourcePress?: (resource: Resource) => void;
88
88
  onBlockLongPress?: (resource: Resource, date: Date) => void;
89
+ onBlockTap?: (resource: Resource, date: Date) => void;
89
90
  onDisabledBlockPress?: (block: DisabledBlock) => void;
90
91
  onEventPress?: (event: Event) => void;
91
92
  onEventLongPress?: (event: Event) => void;
93
+ enableHapticFeedback?: boolean;
92
94
  eventSlots?: EventSlots;
93
95
  eventStyleOverrides?: StyleOverrides | ((event: Event) => StyleOverrides | undefined);
94
96
  isEventSelected?: FlagFn;
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ var reactNativeGestureHandler = require('react-native-gesture-handler');
5
5
  var Animated2 = require('react-native-reanimated');
6
6
  var reactNative = require('react-native');
7
7
  var flashList = require('@shopify/flash-list');
8
- var Haptics = require('expo-haptics');
9
8
  var dateFnsTz = require('date-fns-tz');
10
9
  var dateFns = require('date-fns');
11
10
  var lodash = require('lodash');
@@ -39,7 +38,6 @@ function _interopNamespace(e) {
39
38
 
40
39
  var React19__namespace = /*#__PURE__*/_interopNamespace(React19);
41
40
  var Animated2__default = /*#__PURE__*/_interopDefault(Animated2);
42
- var Haptics__namespace = /*#__PURE__*/_interopNamespace(Haptics);
43
41
  var Svg__default = /*#__PURE__*/_interopDefault(Svg);
44
42
 
45
43
  // src/components/Calendar.tsx
@@ -747,6 +745,7 @@ function StaffAvatar({
747
745
  }
748
746
  var EventGridBlocksSkia = ({
749
747
  handleBlockPress,
748
+ handleBlockLongPress,
750
749
  hourHeight,
751
750
  APPOINTMENT_BLOCK_WIDTH
752
751
  }) => {
@@ -788,6 +787,16 @@ var EventGridBlocksSkia = ({
788
787
  },
789
788
  [handleBlockPress, timeLabels]
790
789
  );
790
+ const onSlotLongPress = React19__namespace.useCallback(
791
+ (row) => {
792
+ setPressedRow(null);
793
+ const slot = timeLabels[row];
794
+ if (slot) {
795
+ handleBlockLongPress(slot);
796
+ }
797
+ },
798
+ [timeLabels, handleBlockLongPress]
799
+ );
791
800
  const onPressBegin = React19__namespace.useCallback((row) => {
792
801
  setPressedRow(row);
793
802
  }, []);
@@ -797,14 +806,31 @@ var EventGridBlocksSkia = ({
797
806
  const longPressGesture = reactNativeGestureHandler.Gesture.LongPress().onBegin((e) => {
798
807
  "worklet";
799
808
  Animated2.runOnJS(onPressBegin)(Math.floor(e.y / rowHeight));
809
+ }).onTouchesUp(() => {
810
+ "worklet";
811
+ Animated2.runOnJS(onTouchesUp)();
812
+ }).onEnd((e) => {
813
+ "worklet";
814
+ Animated2.runOnJS(onSlotLongPress)(Math.floor(e.y / rowHeight));
815
+ }).onFinalize(() => {
816
+ "worklet";
817
+ Animated2.runOnJS(onTouchesUp)();
818
+ });
819
+ const tapGesture = reactNativeGestureHandler.Gesture.Tap().onBegin((e) => {
820
+ "worklet";
821
+ Animated2.runOnJS(onPressBegin)(Math.floor(e.y / rowHeight));
800
822
  }).onEnd((e) => {
801
823
  "worklet";
802
824
  Animated2.runOnJS(onSlotPress)(Math.floor(e.y / rowHeight));
825
+ }).onTouchesUp(() => {
826
+ "worklet";
827
+ Animated2.runOnJS(onTouchesUp)();
803
828
  }).onFinalize(() => {
804
829
  "worklet";
805
830
  Animated2.runOnJS(onTouchesUp)();
806
831
  });
807
- return /* @__PURE__ */ React19__namespace.createElement(reactNativeGestureHandler.GestureDetector, { gesture: longPressGesture }, /* @__PURE__ */ React19__namespace.createElement(reactNative.View, null, /* @__PURE__ */ React19__namespace.createElement(reactNativeSkia.Canvas, { style: { width: APPOINTMENT_BLOCK_WIDTH, height: segmentHeight } }, firstRects.map(({ x, y, width: w, height: h, row }, idx) => /* @__PURE__ */ React19__namespace.createElement(React19__namespace.Fragment, { key: idx }, /* @__PURE__ */ React19__namespace.createElement(
832
+ const composedGesture = reactNativeGestureHandler.Gesture.Race(longPressGesture, tapGesture);
833
+ return /* @__PURE__ */ React19__namespace.createElement(reactNativeGestureHandler.GestureDetector, { gesture: composedGesture }, /* @__PURE__ */ React19__namespace.createElement(reactNative.View, null, /* @__PURE__ */ React19__namespace.createElement(reactNativeSkia.Canvas, { style: { width: APPOINTMENT_BLOCK_WIDTH, height: segmentHeight } }, firstRects.map(({ x, y, width: w, height: h, row }, idx) => /* @__PURE__ */ React19__namespace.createElement(React19__namespace.Fragment, { key: idx }, /* @__PURE__ */ React19__namespace.createElement(
808
834
  reactNativeSkia.Rect,
809
835
  {
810
836
  x,
@@ -1294,7 +1320,7 @@ var DaysComponent = ({ onResourcePress, activeResourceId, mode, date, APPOINTMEN
1294
1320
  width: APPOINTMENT_BLOCK_WIDTH
1295
1321
  },
1296
1322
  space: 4,
1297
- key: i
1323
+ key: d.toString()
1298
1324
  },
1299
1325
  /* @__PURE__ */ React19__namespace.createElement(Center_default, { style: {
1300
1326
  backgroundColor: selected ? "#4d959c" : void 0,
@@ -1346,9 +1372,11 @@ var CalendarInner = (props) => {
1346
1372
  resources,
1347
1373
  onResourcePress,
1348
1374
  onBlockLongPress,
1375
+ onBlockTap,
1349
1376
  onEventPress,
1350
1377
  onEventLongPress,
1351
1378
  onDisabledBlockPress,
1379
+ enableHapticFeedback = false,
1352
1380
  eventSlots,
1353
1381
  eventStyleOverrides,
1354
1382
  overLappingLayoutMode = "stacked",
@@ -1437,8 +1465,9 @@ var CalendarInner = (props) => {
1437
1465
  React19.useEffect(() => {
1438
1466
  if (!selectedEvent) {
1439
1467
  setDraggedEventDraft(null);
1468
+ setDragReady(false);
1440
1469
  }
1441
- }, [selectedEvent]);
1470
+ }, [selectedEvent, setSelectedEvent, setDraggedEventDraft]);
1442
1471
  React19.useEffect(() => {
1443
1472
  scrollX.value = 0;
1444
1473
  }, [mode]);
@@ -1447,6 +1476,7 @@ var CalendarInner = (props) => {
1447
1476
  const flashListRef = React19.useRef(null);
1448
1477
  const prevResourceIdsRef = React19.useRef([]);
1449
1478
  const [layout, setLayout] = React19.useState(null);
1479
+ const [dragReady, setDragReady] = React19.useState(false);
1450
1480
  const dateRef = React19.useRef(date);
1451
1481
  const eventStartedTop = Animated2.useSharedValue(0);
1452
1482
  const eventHeight = Animated2.useSharedValue(0);
@@ -1463,9 +1493,19 @@ var CalendarInner = (props) => {
1463
1493
  const startedX = Animated2.useSharedValue(0);
1464
1494
  const startedY = Animated2.useSharedValue(0);
1465
1495
  const touchY = Animated2.useSharedValue(0);
1466
- const triggerHaptic = React19.useCallback(() => {
1467
- Haptics__namespace.impactAsync(Haptics__namespace.ImpactFeedbackStyle.Light);
1468
- }, []);
1496
+ const triggerHaptic = React19.useCallback(
1497
+ async (style = "Light") => {
1498
+ try {
1499
+ const Haptics = await import('expo-haptics');
1500
+ const feedbackStyle = Haptics.ImpactFeedbackStyle[style];
1501
+ if (enableHapticFeedback)
1502
+ await Haptics.impactAsync(feedbackStyle);
1503
+ } catch (e) {
1504
+ console.log("Haptics not available, skipping...");
1505
+ }
1506
+ },
1507
+ [enableHapticFeedback]
1508
+ );
1469
1509
  const resourceIds = React19.useMemo(() => {
1470
1510
  const ids = resources?.map((item) => item?.id) || [];
1471
1511
  if (JSON.stringify(prevResourceIdsRef.current) !== JSON.stringify(ids)) {
@@ -1626,7 +1666,7 @@ var CalendarInner = (props) => {
1626
1666
  const increment = APPOINTMENT_BLOCK_WIDTH * Math.sign(autoScrollXSpeed.value);
1627
1667
  const newScrollX = scrollX.value + increment;
1628
1668
  Animated2.runOnJS(scrollListTo)(newScrollX);
1629
- Animated2.runOnJS(Haptics__namespace.impactAsync)(Haptics__namespace.ImpactFeedbackStyle.Medium);
1669
+ Animated2.runOnJS(triggerHaptic)("Medium");
1630
1670
  }
1631
1671
  });
1632
1672
  Animated2.useFrameCallback(() => {
@@ -1660,7 +1700,7 @@ var CalendarInner = (props) => {
1660
1700
  const scrollDiff = Math.abs(newScrollY - lastHapticScrollY.value);
1661
1701
  if (scrollDiff >= snapInterval) {
1662
1702
  lastHapticScrollY.value = newScrollY;
1663
- Animated2.runOnJS(Haptics__namespace.impactAsync)(Haptics__namespace.ImpactFeedbackStyle.Medium);
1703
+ Animated2.runOnJS(triggerHaptic)("Medium");
1664
1704
  }
1665
1705
  });
1666
1706
  React19.useEffect(() => {
@@ -1693,7 +1733,8 @@ var CalendarInner = (props) => {
1693
1733
  lastHapticScrollY.value = scrollY.value;
1694
1734
  eventHeight.value = initialHeight;
1695
1735
  setSelectedEvent(event);
1696
- Haptics__namespace.impactAsync(Haptics__namespace.ImpactFeedbackStyle.Medium);
1736
+ requestAnimationFrame(() => setDragReady(true));
1737
+ Animated2.runOnJS(triggerHaptic)("Medium");
1697
1738
  };
1698
1739
  }, []);
1699
1740
  const internalStableOnLongPress = React19.useCallback((e) => {
@@ -1716,22 +1757,30 @@ var CalendarInner = (props) => {
1716
1757
  }
1717
1758
  }
1718
1759
  });
1719
- const handleBlockPress = React19.useCallback((resourceId, time) => {
1720
- Haptics__namespace.impactAsync(Haptics__namespace.ImpactFeedbackStyle.Medium);
1760
+ const handleBlockLongPress = React19.useCallback((resourceId, time) => {
1761
+ Animated2.runOnJS(triggerHaptic)("Medium");
1721
1762
  const resource = resources.find((r) => r.id === resourceId);
1722
1763
  if (onBlockLongPress)
1723
1764
  onBlockLongPress(resource, new Date(time));
1724
1765
  }, [resources, onBlockLongPress]);
1766
+ const handleBlockPress = React19.useCallback((resourceId, time) => {
1767
+ Animated2.runOnJS(triggerHaptic)("Medium");
1768
+ const resource = resources.find((r) => r.id === resourceId);
1769
+ if (onBlockTap)
1770
+ onBlockTap(resource, new Date(time));
1771
+ }, [resources, onBlockTap]);
1725
1772
  React19.useEffect(() => {
1726
1773
  const handleOrientationChange = () => {
1727
- if (selectedEvent)
1774
+ if (selectedEvent) {
1728
1775
  setSelectedEvent(null);
1776
+ setDragReady(false);
1777
+ }
1729
1778
  };
1730
1779
  const subscription = reactNative.Dimensions.addEventListener("change", handleOrientationChange);
1731
1780
  return () => {
1732
1781
  subscription.remove();
1733
1782
  };
1734
- }, [setSelectedEvent, selectedEvent]);
1783
+ }, [setSelectedEvent, selectedEvent, setDragReady]);
1735
1784
  React19.useEffect(() => {
1736
1785
  dateRef.current = date;
1737
1786
  }, [date]);
@@ -1743,7 +1792,8 @@ var CalendarInner = (props) => {
1743
1792
  {
1744
1793
  hourHeight,
1745
1794
  APPOINTMENT_BLOCK_WIDTH,
1746
- handleBlockPress: (time) => handleBlockPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time))
1795
+ handleBlockPress: (time) => handleBlockPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time)),
1796
+ handleBlockLongPress: (time) => handleBlockLongPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time))
1747
1797
  }
1748
1798
  ), /* @__PURE__ */ React19__namespace.default.createElement(
1749
1799
  DisabledIntervals_default,
@@ -1890,7 +1940,7 @@ var CalendarInner = (props) => {
1890
1940
  }
1891
1941
  )
1892
1942
  ),
1893
- selectedEvent && /* @__PURE__ */ React19__namespace.default.createElement(
1943
+ selectedEvent && dragReady && /* @__PURE__ */ React19__namespace.default.createElement(
1894
1944
  DraggableEvent,
1895
1945
  {
1896
1946
  selectedEvent,