react-native-resource-calendar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1711 @@
1
+ import * as React18 from 'react';
2
+ import React18__default, { createContext, useState, useEffect, useMemo, useContext, useRef, useCallback } from 'react';
3
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
+ import Animated2, { useAnimatedRef, useSharedValue, runOnJS, withSpring, useFrameCallback, scrollTo, useAnimatedScrollHandler, useAnimatedStyle, useAnimatedProps } from 'react-native-reanimated';
5
+ import { InteractionManager, View, StyleSheet, Text, TouchableOpacity, TextInput, useWindowDimensions, Platform, Dimensions, Image } from 'react-native';
6
+ import { FlashList } from '@shopify/flash-list';
7
+ import * as Haptics from 'expo-haptics';
8
+ import { toZonedTime } from 'date-fns-tz';
9
+ import { isSameDay, format, getHours, getMinutes, set, setSeconds, setMinutes, setHours } from 'date-fns';
10
+ import { isUndefined } from 'lodash';
11
+ import { createStore } from 'zustand';
12
+ import { shallow } from 'zustand/shallow';
13
+ import { useStoreWithEqualityFn } from 'zustand/traditional';
14
+ import { Canvas, Rect as Rect$1, Line as Line$1 } from '@shopify/react-native-skia';
15
+ import Svg, { Defs, Pattern, Line } from 'react-native-svg';
16
+ import { Rect } from 'react-content-loader/native';
17
+ import { MaterialIcons } from '@expo/vector-icons';
18
+
19
+ // src/components/Calendar.tsx
20
+ var TIME_LABEL_WIDTH = 50;
21
+ var groupEventsByOverlap = (events) => {
22
+ return events.reduce((clusters, appointment) => {
23
+ const cluster = clusters.find((c) => c.some((e) => isOverlapping(e, appointment)));
24
+ if (cluster) {
25
+ cluster.push(appointment);
26
+ } else {
27
+ clusters.push([appointment]);
28
+ }
29
+ return clusters;
30
+ }, []);
31
+ };
32
+ function computeDisabledBlockColumns(disabledBlocks) {
33
+ const groups = groupDisabledBlocksByOverlap(disabledBlocks);
34
+ const res = /* @__PURE__ */ new Map();
35
+ for (const group of groups) {
36
+ const byStart = [...group].sort((a, b) => a.from - b.from);
37
+ const columns = [];
38
+ for (const evt of byStart) {
39
+ let placed = false;
40
+ for (const col of columns) {
41
+ const last = col[col.length - 1];
42
+ if (!isOverlappingDisabledBlock(last, evt)) {
43
+ col.push(evt);
44
+ placed = true;
45
+ break;
46
+ }
47
+ }
48
+ if (!placed) columns.push([evt]);
49
+ }
50
+ const colIndexByEvent = /* @__PURE__ */ new Map();
51
+ columns.forEach((col, idx) => col.forEach((e) => colIndexByEvent.set(e, idx)));
52
+ const groupCols = columns.length;
53
+ for (const evt of group) {
54
+ const myCol = colIndexByEvent.get(evt);
55
+ let span = 1;
56
+ for (let c = myCol + 1; c < groupCols; c++) {
57
+ const blocked = columns[c].some(
58
+ (e) => e !== evt && isOverlappingDisabledBlock(e, evt)
59
+ );
60
+ if (blocked) break;
61
+ span++;
62
+ }
63
+ const key = evt.id;
64
+ res.set(key, {
65
+ leftIndex: myCol,
66
+ renderColumnCount: groupCols,
67
+ spanColumns: span
68
+ });
69
+ }
70
+ }
71
+ return res;
72
+ }
73
+ var groupDisabledBlocksByOverlap = (disabledBlocks) => {
74
+ return disabledBlocks.reduce((clusters, disabledBlock) => {
75
+ const cluster = clusters.find((c) => c.some((e) => isOverlappingDisabledBlock(e, disabledBlock)));
76
+ if (cluster) {
77
+ cluster.push(disabledBlock);
78
+ } else {
79
+ clusters.push([disabledBlock]);
80
+ }
81
+ return clusters;
82
+ }, []);
83
+ };
84
+ var isOverlappingDisabledBlock = (disabledBlockA, disabledBlockB) => {
85
+ return !(disabledBlockA.to <= disabledBlockB.from || disabledBlockA.from >= disabledBlockB.to);
86
+ };
87
+ function computeEventColumns(events) {
88
+ const groups = groupEventsByOverlap(events);
89
+ const out = /* @__PURE__ */ new Map();
90
+ for (const group of groups) {
91
+ const byStart = [...group].sort(
92
+ (a, b) => a.from - b.from
93
+ );
94
+ const columns = [];
95
+ for (const evt of byStart) {
96
+ let placed = false;
97
+ for (const col of columns) {
98
+ const last = col[col.length - 1];
99
+ if (!isOverlapping(last, evt)) {
100
+ col.push(evt);
101
+ placed = true;
102
+ break;
103
+ }
104
+ }
105
+ if (!placed) columns.push([evt]);
106
+ }
107
+ const colIndex = /* @__PURE__ */ new Map();
108
+ columns.forEach((col, i) => col.forEach((e) => colIndex.set(e, i)));
109
+ const groupCols = columns.length;
110
+ for (const evt of group) {
111
+ const myCol = colIndex.get(evt);
112
+ let span = 1;
113
+ for (let c = myCol + 1; c < groupCols; c++) {
114
+ const blocked = columns[c].some(
115
+ (e) => (
116
+ // exclude self; block if ANY event in col c overlaps me
117
+ e !== evt && e.id !== evt.id && isOverlapping(e, evt)
118
+ )
119
+ );
120
+ if (blocked) break;
121
+ span++;
122
+ }
123
+ out.set(evt.id, {
124
+ leftIndex: myCol,
125
+ renderColumnCount: groupCols,
126
+ spanColumns: span
127
+ });
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ var getTextSize = (size) => {
133
+ switch (size) {
134
+ case 60:
135
+ return 10;
136
+ case 80:
137
+ return 12;
138
+ case 100:
139
+ return 12;
140
+ default:
141
+ return 12;
142
+ }
143
+ };
144
+ var MINUTES_IN_DAY = 1440;
145
+ var normalizeOvernight = (startMin, endMin) => {
146
+ if (endMin < startMin) endMin += MINUTES_IN_DAY;
147
+ return { startMin, endMin };
148
+ };
149
+ var intersects = (A, B) => Math.max(A.startMin, B.startMin) < Math.min(A.endMin, B.endMin);
150
+ var isOverlapping = (eventA, eventB) => {
151
+ const aStart0 = eventA.from;
152
+ const aEnd0 = eventA.to;
153
+ const bStart0 = eventB.from;
154
+ const bEnd0 = eventB.to;
155
+ if (aStart0 === aEnd0 || bStart0 === bEnd0) return false;
156
+ const A = normalizeOvernight(aStart0, aEnd0);
157
+ const B = normalizeOvernight(bStart0, bEnd0);
158
+ return intersects(A, B);
159
+ };
160
+ var getCurrentTimeInMinutes = (timezone) => {
161
+ const now = toZonedTime(/* @__PURE__ */ new Date(), timezone);
162
+ const hours = getHours(now);
163
+ const minutes = getMinutes(now);
164
+ return hours * 60 + minutes;
165
+ };
166
+ var timeToYPosition = (minutes, TIME_LABEL_HEIGHT) => minutes * (TIME_LABEL_HEIGHT / 60);
167
+ var scalePosition = (position, hourHeight) => {
168
+ return position * (hourHeight / 60);
169
+ };
170
+ var positionToMinutes = (position, TIME_LABEL_HEIGHT) => {
171
+ "worklet";
172
+ return position / (TIME_LABEL_HEIGHT / 60);
173
+ };
174
+ var combineDateAndTime = (date, time) => {
175
+ const [hours, minutes, seconds] = time.split(":").map(Number);
176
+ const combinedDate = setSeconds(setMinutes(setHours(date, hours), minutes), seconds);
177
+ return format(combinedDate, "yyyy-MM-dd HH:mm:ss");
178
+ };
179
+ var indexToDate = (index) => {
180
+ const dateWithHour = set(/* @__PURE__ */ new Date(), { hours: index, minutes: 0, seconds: 0, milliseconds: 0 });
181
+ return format(dateWithHour, "h:mm a");
182
+ };
183
+ var minutesToTime = (totalMinutes) => {
184
+ "worklet";
185
+ const safeTotalMinutes = Math.max(0, Math.round(totalMinutes));
186
+ const hours24 = Math.floor(safeTotalMinutes / 60);
187
+ const minutes = safeTotalMinutes % 60;
188
+ const paddedMins = minutes < 10 ? "0" + minutes : String(minutes);
189
+ const hours12 = hours24 % 12 || 12;
190
+ return `${hours12}:${paddedMins}`;
191
+ };
192
+ function computeStackedEventLayout(events, containerWidthPx, {
193
+ indentPx = 12,
194
+ // how much to nudge each overlap to the right
195
+ rightPadPx = 0,
196
+ // visual breathing room on the right
197
+ minWidthPx = 30,
198
+ // never let an event become thinner than this
199
+ capIndentLevels = 4
200
+ // after N levels, stop indenting (just stack via z-index)
201
+ } = {}) {
202
+ const groups = groupEventsByOverlap(events);
203
+ const out = /* @__PURE__ */ new Map();
204
+ for (const group of groups) {
205
+ const byStart = [...group].sort((a, b) => a.from - b.from);
206
+ const active = [];
207
+ const removeFinished = (currentFrom) => {
208
+ for (let i = active.length - 1; i >= 0; i--) {
209
+ if (!isOverlapping(active[i].e, { ...active[i].e, from: currentFrom, to: currentFrom })) {
210
+ if (active[i].e.to <= currentFrom) active.splice(i, 1);
211
+ }
212
+ }
213
+ };
214
+ const findLowestFreeLevel = () => {
215
+ const used = new Set(active.map((a) => a.level));
216
+ let lvl = 0;
217
+ while (used.has(lvl)) lvl++;
218
+ return lvl;
219
+ };
220
+ for (const e of byStart) {
221
+ removeFinished(e.from);
222
+ let level = findLowestFreeLevel();
223
+ const visualLevel = Math.min(level, capIndentLevels);
224
+ active.push({ e, level });
225
+ const leftPx = visualLevel * indentPx;
226
+ const available = containerWidthPx - leftPx - rightPadPx;
227
+ const widthPx = Math.max(minWidthPx, available);
228
+ const zIndex = 9999 + e.from * 10 + level;
229
+ out.set(e.id, { leftPx, widthPx, zIndex });
230
+ }
231
+ }
232
+ return out;
233
+ }
234
+ function columnsToPixels(columnMap, containerWidthPx, {
235
+ gutterPx = 2,
236
+ // spacing between columns
237
+ padLeftPx = 0,
238
+ padRightPx = 0
239
+ } = {}) {
240
+ const out = /* @__PURE__ */ new Map();
241
+ for (const [id, c] of columnMap) {
242
+ const totalGutters = (c.renderColumnCount - 1) * gutterPx;
243
+ const innerWidth = containerWidthPx - padLeftPx - padRightPx - totalGutters;
244
+ const colWidth = innerWidth / c.renderColumnCount;
245
+ const left = padLeftPx + c.leftIndex * (colWidth + gutterPx);
246
+ const width = colWidth * c.spanColumns + gutterPx * (c.spanColumns - 1);
247
+ out.set(id, {
248
+ leftPx: left,
249
+ widthPx: Math.max(0, width),
250
+ // later columns on top slightly, but mostly rely on time-based z
251
+ zIndex: 1e3 + c.leftIndex
252
+ });
253
+ }
254
+ return out;
255
+ }
256
+ function computeEventFrames(events, containerWidthPx, mode, options) {
257
+ if (mode === "columns") {
258
+ const columnLayouts = computeEventColumns(events);
259
+ return columnsToPixels(columnLayouts, containerWidthPx, {
260
+ gutterPx: options?.gutterPx,
261
+ padLeftPx: options?.padLeftPx,
262
+ padRightPx: options?.padRightPx
263
+ });
264
+ } else {
265
+ return computeStackedEventLayout(events, containerWidthPx, {
266
+ indentPx: options?.indentPx,
267
+ rightPadPx: options?.rightPadPx,
268
+ minWidthPx: options?.minWidthPx,
269
+ capIndentLevels: options?.capIndentLevels
270
+ });
271
+ }
272
+ }
273
+ var Col = ({ children, divider, space, style }) => {
274
+ return /* @__PURE__ */ React18__default.createElement(View, { style: [{ flexDirection: "column" }, style] }, React18__default.Children.toArray(children).map((child, index) => /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, { key: index }, child, index !== React18__default.Children.toArray(children).length - 1 && divider, index !== React18__default.Children.toArray(children).length - 1 && /* @__PURE__ */ React18__default.createElement(View, { style: { height: space, width: "100%" } }))));
275
+ };
276
+ var Col_default = Col;
277
+
278
+ // src/theme/resolveFont.ts
279
+ var WEIGHT_NAME_MAP = {
280
+ "100": "Thin",
281
+ "200": "ExtraLight",
282
+ "300": "Light",
283
+ "400": "Regular",
284
+ "500": "Medium",
285
+ "600": "SemiBold",
286
+ "700": "Bold",
287
+ "800": "ExtraBold",
288
+ "900": "Black"
289
+ };
290
+ function resolveFont({ family = "System", weight = "400", italic = false }) {
291
+ if (family === "System" || family.includes("_")) return family;
292
+ const weightName = WEIGHT_NAME_MAP[weight] ?? "Regular";
293
+ const base = `${family}_${weight}${weightName}`;
294
+ return italic ? `${base}_Italic` : base;
295
+ }
296
+
297
+ // src/theme/ThemeContext.tsx
298
+ var defaultTheme = {
299
+ typography: {
300
+ fontFamily: "System"
301
+ }
302
+ };
303
+ var ThemeCtx = createContext(defaultTheme);
304
+ var useCalendarTheme = () => useContext(ThemeCtx);
305
+ var useResolvedFont = (overrides) => {
306
+ const { typography } = useCalendarTheme();
307
+ const family = overrides?.fontFamily ?? typography?.fontFamily ?? "System";
308
+ const weight = overrides?.fontWeight ?? "400";
309
+ const italic = overrides?.italic ?? false;
310
+ return resolveFont({ family, weight, italic });
311
+ };
312
+ var CalendarThemeProvider = ({ theme, children }) => {
313
+ const mergedTheme = {
314
+ ...defaultTheme,
315
+ ...theme,
316
+ typography: { ...defaultTheme.typography, ...theme?.typography }
317
+ };
318
+ return /* @__PURE__ */ React18__default.createElement(ThemeCtx.Provider, { value: mergedTheme }, children);
319
+ };
320
+
321
+ // src/components/TimeLabels.tsx
322
+ var TimeLabels = React18.forwardRef(({
323
+ timezone,
324
+ hourHeight = 120,
325
+ startMinutes = 0,
326
+ totalTimelineWidth,
327
+ date,
328
+ layout
329
+ }, ref) => {
330
+ const isToday = isSameDay(/* @__PURE__ */ new Date(), date);
331
+ const [currentTimeYPosition, setCurrentTimeYPosition] = useState(timeToYPosition(getCurrentTimeInMinutes(timezone), hourHeight));
332
+ const [currentTime, setCurrentTime] = useState(format(toZonedTime(/* @__PURE__ */ new Date(), timezone), "h:mm"));
333
+ const APPOINTMENT_BLOCK_HEIGHT = hourHeight / 4;
334
+ const updateCurrentTimeYPosition = () => {
335
+ setCurrentTimeYPosition(timeToYPosition(getCurrentTimeInMinutes(timezone), hourHeight));
336
+ };
337
+ const updateCurrentTime = () => {
338
+ setCurrentTime(format(toZonedTime(/* @__PURE__ */ new Date(), timezone), "h:mm"));
339
+ };
340
+ const titleFace = useResolvedFont({ fontWeight: "700" });
341
+ useEffect(() => {
342
+ const update = () => {
343
+ updateCurrentTime();
344
+ updateCurrentTimeYPosition();
345
+ };
346
+ update();
347
+ const intervalId = setInterval(update, 300);
348
+ return () => clearInterval(intervalId);
349
+ }, [timezone]);
350
+ useEffect(() => {
351
+ InteractionManager.runAfterInteractions(() => {
352
+ let pos = isToday ? currentTimeYPosition - 240 : timeToYPosition(startMinutes, hourHeight);
353
+ if (ref.current) {
354
+ ref.current.scrollTo({
355
+ y: Math.round(pos / APPOINTMENT_BLOCK_HEIGHT) * APPOINTMENT_BLOCK_HEIGHT,
356
+ // Offset by 240px to give a little margin above the red line
357
+ animated: true
358
+ });
359
+ }
360
+ });
361
+ }, [date, isToday, APPOINTMENT_BLOCK_HEIGHT, startMinutes, hourHeight]);
362
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(Col_default, null, Array.from({ length: 24 }).map((_, index) => /* @__PURE__ */ React18.createElement(View, { key: index, style: [styles.timeLabel, { height: hourHeight }] }, /* @__PURE__ */ React18.createElement(
363
+ Text,
364
+ {
365
+ allowFontScaling: false,
366
+ style: { textAlign: "center", fontFamily: titleFace, fontSize: getTextSize(hourHeight) }
367
+ },
368
+ indexToDate(index).split(" ")[0]
369
+ ), /* @__PURE__ */ React18.createElement(
370
+ Text,
371
+ {
372
+ allowFontScaling: false,
373
+ style: { textAlign: "center", fontFamily: titleFace, fontSize: getTextSize(hourHeight) }
374
+ },
375
+ indexToDate(index).split(" ")[1]
376
+ ))), isToday && /* @__PURE__ */ React18.createElement(View, { style: [styles.currentTime, {
377
+ top: currentTimeYPosition - 13,
378
+ width: TIME_LABEL_WIDTH
379
+ }] }, /* @__PURE__ */ React18.createElement(
380
+ Text,
381
+ {
382
+ allowFontScaling: false,
383
+ style: {
384
+ textAlign: "center",
385
+ fontFamily: titleFace,
386
+ fontSize: getTextSize(hourHeight),
387
+ color: "red"
388
+ }
389
+ },
390
+ currentTime
391
+ ))), isToday && /* @__PURE__ */ React18.createElement(View, { style: [styles.currentTimeLine, {
392
+ pointerEvents: "none",
393
+ top: currentTimeYPosition,
394
+ width: totalTimelineWidth,
395
+ left: TIME_LABEL_WIDTH
396
+ }] }));
397
+ });
398
+ var styles = StyleSheet.create({
399
+ timeLabel: {
400
+ width: TIME_LABEL_WIDTH
401
+ },
402
+ currentTimeLine: {
403
+ position: "absolute",
404
+ height: 2,
405
+ // Thickness of the line
406
+ backgroundColor: "red",
407
+ zIndex: 1e4
408
+ // Ensure it's on top of all other elements
409
+ },
410
+ currentTime: {
411
+ backgroundColor: "#fff",
412
+ borderColor: "red",
413
+ alignItems: "center",
414
+ justifyContent: "center",
415
+ borderWidth: 2,
416
+ borderRadius: 20,
417
+ height: 26,
418
+ position: "absolute",
419
+ zIndex: 1e4
420
+ // Ensure it's on top of all other elements
421
+ }
422
+ });
423
+ var Hidden = ({ isHidden, children }) => {
424
+ if (isHidden) {
425
+ return null;
426
+ }
427
+ return /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, null, children);
428
+ };
429
+ var Hidden_default = Hidden;
430
+ var Center = ({ children, style }) => {
431
+ return /* @__PURE__ */ React18__default.createElement(
432
+ View,
433
+ {
434
+ style: [{
435
+ justifyContent: "center",
436
+ alignItems: "center"
437
+ }, style]
438
+ },
439
+ children
440
+ );
441
+ };
442
+ var Center_default = Center;
443
+ var Badge = ({
444
+ style,
445
+ value = "",
446
+ children,
447
+ fontSize,
448
+ color = "red",
449
+ textColor = "white"
450
+ }) => {
451
+ const titleFace = useResolvedFont({ fontWeight: "600" });
452
+ return /* @__PURE__ */ React18__default.createElement(View, { style: [styles2.badge, { backgroundColor: color }, style] }, children ? children : /* @__PURE__ */ React18__default.createElement(
453
+ Text,
454
+ {
455
+ allowFontScaling: false,
456
+ style: {
457
+ color: textColor,
458
+ fontSize,
459
+ fontFamily: titleFace
460
+ }
461
+ },
462
+ value
463
+ ));
464
+ };
465
+ var styles2 = StyleSheet.create({
466
+ badge: {
467
+ justifyContent: "center",
468
+ alignItems: "center",
469
+ borderRadius: 999,
470
+ paddingHorizontal: 6
471
+ }
472
+ });
473
+ var Badge_default = Badge;
474
+ var createCalendarStore = () => createStore((set2) => ({
475
+ resourcesById: {},
476
+ eventsByResource: {},
477
+ disabledBlocksByResource: {},
478
+ disabledIntervalsByResource: {},
479
+ selectedEvent: null,
480
+ draggedEventDraft: null,
481
+ setSelectedEvent: (evt) => set2({ selectedEvent: evt }),
482
+ upsertResources: (rs) => set2((s) => {
483
+ const next = { ...s.resourcesById };
484
+ let changed = false;
485
+ for (const r of rs) {
486
+ const prev = next[r.id];
487
+ if (!prev || prev.name !== r.name || prev.avatar !== r.avatar) {
488
+ next[r.id] = { id: r.id, name: r.name, avatar: r.avatar };
489
+ changed = true;
490
+ }
491
+ }
492
+ return changed ? { resourcesById: next } : {};
493
+ }),
494
+ setDayData: ({ events, disabledBlocks, disableIntervals }) => set2((s) => ({
495
+ eventsByResource: events ?? s.eventsByResource,
496
+ disabledBlocksByResource: disabledBlocks ?? s.disabledBlocksByResource,
497
+ disabledIntervalsByResource: disableIntervals ?? s.disabledIntervalsByResource
498
+ })),
499
+ setDraggedEventDraft: (draft) => set2({ draggedEventDraft: draft }),
500
+ clearDay: () => set2({
501
+ eventsByResource: {},
502
+ disabledBlocksByResource: {},
503
+ disabledIntervalsByResource: {}
504
+ })
505
+ }));
506
+ var StoreContext = createContext(null);
507
+ var Provider = ({ children }) => {
508
+ const ref = useRef(void 0);
509
+ if (!ref.current) ref.current = createCalendarStore();
510
+ return /* @__PURE__ */ React18__default.createElement(StoreContext.Provider, { value: ref.current }, children);
511
+ };
512
+ var useBound = (selector, eq) => {
513
+ const store = useContext(StoreContext);
514
+ if (!store) throw new Error("Calendar store used outside of Provider");
515
+ return useStoreWithEqualityFn(store, selector, eq);
516
+ };
517
+ var useResourceById = (id) => useBound((s) => s.resourcesById[id]);
518
+ var useGetSelectedEvent = () => useBound((s) => s.selectedEvent);
519
+ var useSetSelectedEvent = () => useBound((s) => s.setSelectedEvent);
520
+ var useEventsFor = (resourceId) => useBound((s) => s.eventsByResource[resourceId] ?? [], shallow);
521
+ var useGetDraggedEventDraft = () => useBound((s) => s.draggedEventDraft);
522
+ var useDisabledBlocksFor = (resourceId) => useBound((s) => s.disabledBlocksByResource[resourceId] ?? [], shallow);
523
+ var useDisabledIntervalsFor = (resourceId) => useBound((s) => s.disabledIntervalsByResource[resourceId] ?? [], shallow);
524
+ var useUpsertResources = () => useBound((s) => s.upsertResources);
525
+ var useSetDayData = () => useBound((s) => s.setDayData);
526
+ var useSetDraggedEventDraft = () => useBound((s) => s.setDraggedEventDraft);
527
+ var zustandBinding = {
528
+ Provider,
529
+ useResourceById,
530
+ useEventsFor,
531
+ useDisabledBlocksFor,
532
+ useDisabledIntervalsFor,
533
+ useUpsertResources,
534
+ useSetDayData,
535
+ useGetSelectedEvent,
536
+ useSetSelectedEvent,
537
+ useGetDraggedEventDraft,
538
+ useSetDraggedEventDraft
539
+ };
540
+
541
+ // src/store/bindings/BindingProvider.tsx
542
+ var BindingCtx = createContext(null);
543
+ var useCalendarBinding = () => {
544
+ const ctx = useContext(BindingCtx);
545
+ if (!ctx) throw new Error("useCalendarBinding must be used within <CalendarBindingProvider>");
546
+ return ctx;
547
+ };
548
+ var CalendarBindingProvider = ({ binding, children }) => {
549
+ const active = binding ?? zustandBinding;
550
+ const StoreProvider = active.Provider;
551
+ return /* @__PURE__ */ React18__default.createElement(BindingCtx.Provider, { value: active }, /* @__PURE__ */ React18__default.createElement(StoreProvider, null, children));
552
+ };
553
+
554
+ // src/components/ResourcesComponent.tsx
555
+ var ResourceComponent = ({ id, onResourcePress, APPOINTMENT_BLOCK_WIDTH }) => {
556
+ const { useResourceById: useResourceById2, useEventsFor: useEventsFor2 } = useCalendarBinding();
557
+ const resource = useResourceById2(id);
558
+ const events = useEventsFor2(id);
559
+ const titleFace = useResolvedFont({ fontWeight: "700" });
560
+ return /* @__PURE__ */ React18.createElement(Col_default, { style: [{
561
+ alignItems: "center",
562
+ width: APPOINTMENT_BLOCK_WIDTH
563
+ }] }, /* @__PURE__ */ React18.createElement(View, { style: { position: "relative" } }, /* @__PURE__ */ React18.createElement(
564
+ StaffAvatar,
565
+ {
566
+ onPress: () => {
567
+ if (onResourcePress)
568
+ onResourcePress(resource);
569
+ },
570
+ name: resource?.name,
571
+ circleSize: 40,
572
+ fontSize: 16,
573
+ badge: events?.length,
574
+ image: resource?.avatar
575
+ }
576
+ )), /* @__PURE__ */ React18.createElement(
577
+ Text,
578
+ {
579
+ style: {
580
+ fontSize: 14,
581
+ fontFamily: titleFace
582
+ },
583
+ numberOfLines: 1,
584
+ allowFontScaling: false
585
+ },
586
+ resource?.name
587
+ ));
588
+ };
589
+ var ResourcesComponent = ({ resourceIds, onResourcePress, APPOINTMENT_BLOCK_WIDTH }) => {
590
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, resourceIds?.map((id) => {
591
+ return /* @__PURE__ */ React18.createElement(
592
+ ResourceComponent,
593
+ {
594
+ key: id,
595
+ id,
596
+ APPOINTMENT_BLOCK_WIDTH,
597
+ onResourcePress
598
+ }
599
+ );
600
+ }));
601
+ };
602
+ function StaffAvatar({
603
+ name,
604
+ circleSize = 60,
605
+ fontSize = 36,
606
+ image,
607
+ badge,
608
+ badgeStyle,
609
+ onPress,
610
+ containerStyle,
611
+ ringColor = "#DAEEE7",
612
+ avatarColor,
613
+ textColor
614
+ }) {
615
+ const titleFace = useResolvedFont({ fontWeight: "700" });
616
+ return /* @__PURE__ */ React18.createElement(
617
+ TouchableOpacity,
618
+ {
619
+ disabled: isUndefined(onPress),
620
+ onPress,
621
+ style: containerStyle
622
+ },
623
+ /* @__PURE__ */ React18.createElement(Center_default, { style: {
624
+ borderRadius: 9999,
625
+ backgroundColor: ringColor
626
+ } }, /* @__PURE__ */ React18.createElement(Hidden_default, { isHidden: isUndefined(badge) || Number(badge) == 0 }, /* @__PURE__ */ React18.createElement(
627
+ View,
628
+ {
629
+ style: [{
630
+ zIndex: 1,
631
+ position: "absolute",
632
+ right: -4,
633
+ top: -6,
634
+ borderRadius: 999,
635
+ backgroundColor: "#fff",
636
+ padding: 4
637
+ }, badgeStyle]
638
+ },
639
+ /* @__PURE__ */ React18.createElement(
640
+ Badge_default,
641
+ {
642
+ fontSize: 12,
643
+ value: badge + "",
644
+ color: "#4d959c"
645
+ }
646
+ )
647
+ )), /* @__PURE__ */ React18.createElement(Center_default, { style: {
648
+ margin: 2,
649
+ borderRadius: 9999,
650
+ backgroundColor: "white"
651
+ } }, /* @__PURE__ */ React18.createElement(Center_default, { style: {
652
+ margin: 2,
653
+ borderRadius: 9999,
654
+ height: circleSize,
655
+ width: circleSize,
656
+ backgroundColor: avatarColor || "#C9E5E8",
657
+ overflow: "hidden"
658
+ } }, image ? /* @__PURE__ */ React18.createElement(
659
+ Image,
660
+ {
661
+ resizeMode: "cover",
662
+ source: { uri: image },
663
+ style: {
664
+ height: "100%",
665
+ borderRadius: 6,
666
+ ...StyleSheet.absoluteFillObject
667
+ }
668
+ }
669
+ ) : /* @__PURE__ */ React18.createElement(
670
+ Text,
671
+ {
672
+ allowFontScaling: false,
673
+ style: {
674
+ fontFamily: titleFace,
675
+ fontSize,
676
+ color: textColor || "#4d959c"
677
+ }
678
+ },
679
+ name ? name.split(" ").map((n) => n[0]).join("") : ""
680
+ ))))
681
+ );
682
+ }
683
+ var EventGridBlocksSkia = ({
684
+ dateRef,
685
+ handleBlockPress,
686
+ hourHeight,
687
+ APPOINTMENT_BLOCK_WIDTH
688
+ }) => {
689
+ const rowHeight = hourHeight / 4;
690
+ const [pressedRow, setPressedRow] = React18.useState(null);
691
+ const timeLabels = useMemo(() => {
692
+ const out = [];
693
+ for (let h = 0; h < 24; h++) {
694
+ for (let q = 0; q < 4; q++) {
695
+ const m = q * 15;
696
+ const hh = String(h).padStart(2, "0");
697
+ const mm = String(m).padStart(2, "0");
698
+ out.push(`${hh}:${mm}:00`);
699
+ }
700
+ }
701
+ return out;
702
+ }, []);
703
+ const rects = useMemo(
704
+ () => timeLabels.map((_, row) => ({
705
+ x: 0,
706
+ y: row * rowHeight,
707
+ width: APPOINTMENT_BLOCK_WIDTH,
708
+ height: rowHeight,
709
+ row
710
+ })),
711
+ [timeLabels, rowHeight, APPOINTMENT_BLOCK_WIDTH]
712
+ );
713
+ const midIndex = Math.ceil(rects.length / 2);
714
+ const firstRects = rects.slice(0, midIndex);
715
+ const secondRects = rects.slice(midIndex);
716
+ const segmentHeight = rowHeight * firstRects.length;
717
+ const onSlotPress = React18.useCallback(
718
+ (row) => {
719
+ setPressedRow(null);
720
+ const slot = timeLabels[row];
721
+ if (slot) {
722
+ const timestamp = combineDateAndTime(dateRef.current, slot);
723
+ handleBlockPress(timestamp);
724
+ }
725
+ },
726
+ [dateRef, handleBlockPress, timeLabels]
727
+ );
728
+ const onPressBegin = React18.useCallback((row) => {
729
+ setPressedRow(row);
730
+ }, []);
731
+ const onTouchesUp = React18.useCallback(() => {
732
+ setPressedRow(null);
733
+ }, []);
734
+ const longPressGesture = Gesture.LongPress().onBegin((e) => runOnJS(onPressBegin)(Math.floor(e.y / rowHeight))).onEnd((e) => runOnJS(onSlotPress)(Math.floor(e.y / rowHeight))).onFinalize(() => runOnJS(onTouchesUp)());
735
+ return /* @__PURE__ */ React18.createElement(GestureDetector, { gesture: longPressGesture }, /* @__PURE__ */ React18.createElement(View, null, /* @__PURE__ */ React18.createElement(Canvas, { style: { width: APPOINTMENT_BLOCK_WIDTH, height: segmentHeight } }, firstRects.map(({ x, y, width: w, height: h, row }, idx) => /* @__PURE__ */ React18.createElement(React18.Fragment, { key: idx }, /* @__PURE__ */ React18.createElement(
736
+ Rect$1,
737
+ {
738
+ x,
739
+ y,
740
+ width: w,
741
+ height: h,
742
+ color: pressedRow === row ? "rgba(240,240,240,0.3)" : "rgba(240,240,240,0.6)",
743
+ style: "fill"
744
+ }
745
+ ), /* @__PURE__ */ React18.createElement(Line$1, { p1: { x, y: y + h }, p2: { x: x + w, y: y + h }, color: "#ddd", strokeWidth: 1 })))), /* @__PURE__ */ React18.createElement(Canvas, { style: { width: APPOINTMENT_BLOCK_WIDTH, height: segmentHeight } }, secondRects.map(({ x, y, width: w, height: h, row }, idx) => /* @__PURE__ */ React18.createElement(React18.Fragment, { key: idx }, /* @__PURE__ */ React18.createElement(
746
+ Rect$1,
747
+ {
748
+ x,
749
+ y: y - segmentHeight,
750
+ width: w,
751
+ height: h,
752
+ color: pressedRow === row ? "rgba(240,240,240,0.3)" : "rgba(240,240,240,0.6)",
753
+ style: "fill"
754
+ }
755
+ ), /* @__PURE__ */ React18.createElement(
756
+ Line$1,
757
+ {
758
+ p1: { x, y: y - segmentHeight + h },
759
+ p2: { x: x + w, y: y - segmentHeight + h },
760
+ color: "#ddd",
761
+ strokeWidth: 1
762
+ }
763
+ ))))));
764
+ };
765
+ var StoreFeeder = ({ store, resources }) => {
766
+ const upsertResources = store.useUpsertResources();
767
+ const setDayData = store.useSetDayData();
768
+ useEffect(() => {
769
+ upsertResources(resources.map((r) => ({ id: r.id, name: r.name, avatar: r.avatar })));
770
+ const eventsByResource = {};
771
+ const blocksByResource = {};
772
+ const intervalsByResource = {};
773
+ for (const r of resources) {
774
+ if (r.events?.length) eventsByResource[r.id] = r.events;
775
+ if (r.disabledBlocks?.length) blocksByResource[r.id] = r.disabledBlocks;
776
+ if (r.disableIntervals?.length) intervalsByResource[r.id] = r.disableIntervals;
777
+ }
778
+ setDayData({
779
+ events: eventsByResource,
780
+ disabledBlocks: blocksByResource,
781
+ disableIntervals: intervalsByResource
782
+ });
783
+ }, [resources, upsertResources, setDayData]);
784
+ return null;
785
+ };
786
+ var DisabledInterval = ({ width, top, height }) => {
787
+ return /* @__PURE__ */ React18__default.createElement(View, { style: [styles3.disabledBlock, { width, top, height }] }, /* @__PURE__ */ React18__default.createElement(Svg, { width, height: "100%" }, /* @__PURE__ */ React18__default.createElement(Defs, null, /* @__PURE__ */ React18__default.createElement(Pattern, { id: "diagonalHatch", patternUnits: "userSpaceOnUse", width: "10", height: "10" }, /* @__PURE__ */ React18__default.createElement(Line, { x1: "0", y1: "0", x2: "10", y2: "10", stroke: "rgba(150, 150, 150, 0.8)", strokeWidth: "1" }))), /* @__PURE__ */ React18__default.createElement(Rect, { width, height: "100%", fill: "url(#diagonalHatch)" })));
788
+ };
789
+ var DisabledIntervals = React18__default.memo(({ id, APPOINTMENT_BLOCK_WIDTH, hourHeight }) => {
790
+ const { useDisabledIntervalsFor: useDisabledIntervalsFor2 } = useCalendarBinding();
791
+ const disabledIntervals = useDisabledIntervalsFor2(id);
792
+ return /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, null, disabledIntervals.map(
793
+ (disabledInterval, index) => {
794
+ return /* @__PURE__ */ React18__default.createElement(
795
+ DisabledInterval,
796
+ {
797
+ key: `${index}-${disabledInterval.from}-${disabledInterval.to}`,
798
+ width: APPOINTMENT_BLOCK_WIDTH,
799
+ top: scalePosition(disabledInterval.from, hourHeight),
800
+ height: scalePosition(disabledInterval.to - disabledInterval.from, hourHeight)
801
+ }
802
+ );
803
+ }
804
+ ));
805
+ });
806
+ var styles3 = StyleSheet.create({
807
+ disabledBlock: {
808
+ position: "absolute",
809
+ zIndex: -10
810
+ }
811
+ });
812
+ var DisabledIntervals_default = DisabledIntervals;
813
+ var Row = ({ children, divider, space, style, ...props }) => {
814
+ return /* @__PURE__ */ React18__default.createElement(View, { style: [{ flexDirection: "row" }, style], ...props }, React18__default.Children.toArray(children).map((child, index) => /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, { key: index }, child, index !== React18__default.Children.toArray(children).length - 1 && divider, index !== React18__default.Children.toArray(children).length - 1 && /* @__PURE__ */ React18__default.createElement(View, { style: { width: space, height: "100%" } }))));
815
+ };
816
+ var Row_default = Row;
817
+ var DisabledBlockComponent = ({
818
+ top,
819
+ height,
820
+ layout,
821
+ disabledBlock,
822
+ hourHeight,
823
+ onDisabledBlockPress
824
+ }) => {
825
+ const dynamicStyle = {
826
+ backgroundColor: "#d3d3d3",
827
+ top: top + 2,
828
+ height: height < hourHeight / 4 ? height : height - 4,
829
+ width: layout.widthPx,
830
+ left: layout.leftPx,
831
+ borderWidth: 1,
832
+ borderColor: "rgba(0,0,0,0.12)"
833
+ };
834
+ const titleFace = useResolvedFont({ fontWeight: "600" });
835
+ return /* @__PURE__ */ React18__default.createElement(
836
+ TouchableOpacity,
837
+ {
838
+ style: [styles4.event, dynamicStyle],
839
+ onPress: () => {
840
+ onDisabledBlockPress && onDisabledBlockPress(disabledBlock);
841
+ }
842
+ },
843
+ /* @__PURE__ */ React18__default.createElement(Col_default, { style: { position: "relative" } }, /* @__PURE__ */ React18__default.createElement(Row_default, { style: { height: 18 } }, /* @__PURE__ */ React18__default.createElement(
844
+ Text,
845
+ {
846
+ allowFontScaling: false,
847
+ style: {
848
+ fontFamily: titleFace,
849
+ fontSize: getTextSize(hourHeight)
850
+ }
851
+ },
852
+ minutesToTime(disabledBlock?.from),
853
+ " - ",
854
+ minutesToTime(disabledBlock?.to)
855
+ )), /* @__PURE__ */ React18__default.createElement(
856
+ Text,
857
+ {
858
+ allowFontScaling: false,
859
+ style: {
860
+ fontFamily: titleFace,
861
+ fontSize: getTextSize(hourHeight)
862
+ }
863
+ },
864
+ disabledBlock?.title
865
+ ))
866
+ );
867
+ };
868
+ var DisabledBlocks = React18__default.memo(({
869
+ id,
870
+ APPOINTMENT_BLOCK_WIDTH,
871
+ hourHeight,
872
+ onDisabledBlockPress
873
+ }) => {
874
+ const { useDisabledBlocksFor: useDisabledBlocksFor2 } = useCalendarBinding();
875
+ const disabledBlocks = useDisabledBlocksFor2(id);
876
+ const layoutMap = useMemo(() => {
877
+ return columnsToPixels(computeDisabledBlockColumns(disabledBlocks), APPOINTMENT_BLOCK_WIDTH);
878
+ }, [disabledBlocks]);
879
+ return /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, null, disabledBlocks.map(
880
+ (disabledBlock, index) => {
881
+ const key = disabledBlock.id;
882
+ return /* @__PURE__ */ React18__default.createElement(
883
+ DisabledBlockComponent,
884
+ {
885
+ hourHeight,
886
+ disabledBlock,
887
+ key: `${index}-${disabledBlock.from}-${disabledBlock.to}`,
888
+ top: scalePosition(disabledBlock.from, hourHeight),
889
+ height: scalePosition(disabledBlock.to - disabledBlock.from, hourHeight),
890
+ layout: layoutMap.get(key),
891
+ onDisabledBlockPress
892
+ }
893
+ );
894
+ }
895
+ ));
896
+ });
897
+ var styles4 = StyleSheet.create({
898
+ event: {
899
+ position: "absolute",
900
+ borderRadius: 5,
901
+ padding: 2,
902
+ overflow: "hidden",
903
+ zIndex: 999
904
+ // Ensure events stay above the background blocks
905
+ }
906
+ });
907
+ var DisabledBlocks_default = DisabledBlocks;
908
+ var EventBlock = React18__default.memo(({
909
+ event,
910
+ onLongPress,
911
+ onPress,
912
+ disabled,
913
+ selected,
914
+ hourHeight,
915
+ slots,
916
+ frame,
917
+ styleOverrides
918
+ }) => {
919
+ const { useGetSelectedEvent: useGetSelectedEvent2 } = useCalendarBinding();
920
+ const selectedAppointment = useGetSelectedEvent2();
921
+ const eventTop = scalePosition(event.from, hourHeight);
922
+ const eventHeight = scalePosition(event.to - event.from, hourHeight);
923
+ const start = minutesToTime(event.from);
924
+ const end = minutesToTime(event.to);
925
+ const dynamicStyle = {
926
+ top: eventTop + 2,
927
+ height: eventHeight < hourHeight / 4 ? eventHeight : eventHeight - 4,
928
+ left: frame.leftPx,
929
+ width: frame.widthPx,
930
+ zIndex: frame.zIndex,
931
+ opacity: selectedAppointment ? 0.5 : 1,
932
+ borderWidth: selected ? 2 : 1,
933
+ borderColor: selected ? "#4d959c" : "rgba(0,0,0,0.12)"
934
+ };
935
+ const resolved = typeof styleOverrides === "function" ? styleOverrides(event) ?? {} : styleOverrides ?? {};
936
+ if (eventHeight == 0)
937
+ return null;
938
+ const TopRight = slots?.TopRight;
939
+ const Body = slots?.Body;
940
+ const titleFace = useResolvedFont({ fontWeight: "700" });
941
+ const timeFace = useResolvedFont({ fontWeight: "600" });
942
+ return /* @__PURE__ */ React18__default.createElement(
943
+ TouchableOpacity,
944
+ {
945
+ style: [styles5.event, resolved?.container, dynamicStyle],
946
+ disabled,
947
+ onPress: () => {
948
+ onPress && onPress(event);
949
+ },
950
+ onLongPress: () => {
951
+ onLongPress && onLongPress(event);
952
+ }
953
+ },
954
+ /* @__PURE__ */ React18__default.createElement(Hidden_default, { isHidden: !disabled }, /* @__PURE__ */ React18__default.createElement(View, { style: {
955
+ position: "absolute",
956
+ top: 0,
957
+ width: "150%",
958
+ height: "150%",
959
+ zIndex: 1,
960
+ backgroundColor: "rgba(255, 255, 255, 0.5)"
961
+ } })),
962
+ /* @__PURE__ */ React18__default.createElement(Col_default, { style: [{ position: "relative" }, resolved?.content] }, /* @__PURE__ */ React18__default.createElement(
963
+ TextInput,
964
+ {
965
+ editable: false,
966
+ allowFontScaling: false,
967
+ underlineColorAndroid: "transparent",
968
+ style: {
969
+ width: "100%",
970
+ fontFamily: timeFace,
971
+ fontSize: getTextSize(hourHeight),
972
+ pointerEvents: "none",
973
+ padding: 0,
974
+ margin: 0
975
+ },
976
+ defaultValue: `${start} - ${end}`
977
+ }
978
+ ), Body ? /* @__PURE__ */ React18__default.createElement(Body, { event, ctx: { hourHeight } }) : /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, null, /* @__PURE__ */ React18__default.createElement(Row_default, { style: { alignItems: "center", height: 18 } }, /* @__PURE__ */ React18__default.createElement(
979
+ Text,
980
+ {
981
+ allowFontScaling: false,
982
+ style: [{
983
+ fontFamily: titleFace,
984
+ fontSize: getTextSize(hourHeight),
985
+ fontWeight: "700"
986
+ }, resolved?.title]
987
+ },
988
+ event?.title
989
+ )), /* @__PURE__ */ React18__default.createElement(
990
+ Text,
991
+ {
992
+ allowFontScaling: false,
993
+ style: [{
994
+ fontFamily: timeFace,
995
+ fontSize: getTextSize(hourHeight),
996
+ fontWeight: "600"
997
+ }, resolved?.desc]
998
+ },
999
+ event?.description
1000
+ )), /* @__PURE__ */ React18__default.createElement(Row_default, { style: {
1001
+ position: "absolute",
1002
+ right: 2
1003
+ }, space: 2 }, TopRight ? /* @__PURE__ */ React18__default.createElement(TopRight, { event, ctx: { hourHeight } }) : null))
1004
+ );
1005
+ });
1006
+ var styles5 = StyleSheet.create({
1007
+ event: {
1008
+ backgroundColor: "#4d959c",
1009
+ position: "absolute",
1010
+ borderRadius: 5,
1011
+ padding: 2,
1012
+ overflow: "hidden",
1013
+ zIndex: 9999
1014
+ // Ensure events stay above the background blocks
1015
+ }
1016
+ });
1017
+ var EventBlock_default = EventBlock;
1018
+ var AnimatedTextInput = Animated2.createAnimatedComponent(TextInput);
1019
+ var DraggableEvent = ({
1020
+ selectedEvent,
1021
+ eventStartedTop,
1022
+ panYAbs,
1023
+ panXAbs,
1024
+ APPOINTMENT_BLOCK_WIDTH,
1025
+ hourHeight,
1026
+ eventHeight,
1027
+ styleOverrides,
1028
+ slots
1029
+ }) => {
1030
+ const dynamicStyle = useAnimatedStyle(() => {
1031
+ return {
1032
+ height: eventHeight.value < hourHeight / 4 ? eventHeight.value : eventHeight.value - 4,
1033
+ width: APPOINTMENT_BLOCK_WIDTH,
1034
+ borderWidth: 1,
1035
+ borderColor: "rgba(0,0,0,0.12)"
1036
+ };
1037
+ });
1038
+ const draggingAnimatedStyle = useAnimatedStyle(() => {
1039
+ if (!selectedEvent) {
1040
+ return {
1041
+ opacity: 0,
1042
+ transform: [
1043
+ {
1044
+ translateY: 0
1045
+ },
1046
+ {
1047
+ translateX: 0
1048
+ }
1049
+ ]
1050
+ };
1051
+ }
1052
+ return {
1053
+ opacity: 1,
1054
+ transform: [
1055
+ {
1056
+ translateY: panYAbs.value - eventHeight.value / 2 + 2
1057
+ },
1058
+ {
1059
+ translateX: panXAbs.value - APPOINTMENT_BLOCK_WIDTH / 2
1060
+ }
1061
+ ]
1062
+ };
1063
+ }, [selectedEvent, eventHeight, APPOINTMENT_BLOCK_WIDTH]);
1064
+ const initialDisplayTime = useMemo(() => {
1065
+ const start = minutesToTime(positionToMinutes(eventStartedTop.value, hourHeight));
1066
+ const end = minutesToTime(positionToMinutes(eventStartedTop.value + eventHeight.value, hourHeight));
1067
+ return `${start} - ${end}`;
1068
+ }, [eventHeight.value, hourHeight, eventStartedTop.value]);
1069
+ const animatedTimeProps = useAnimatedProps(() => {
1070
+ const start = minutesToTime(positionToMinutes(eventStartedTop.value, hourHeight));
1071
+ const end = minutesToTime(positionToMinutes(eventStartedTop.value + eventHeight.value, hourHeight));
1072
+ return {
1073
+ text: `${start} - ${end}`
1074
+ };
1075
+ }, []);
1076
+ const resolved = typeof styleOverrides === "function" ? styleOverrides(selectedEvent) ?? {} : styleOverrides ?? {};
1077
+ const TopRight = slots?.TopRight;
1078
+ const Body = slots?.Body;
1079
+ const titleFace = useResolvedFont({ fontWeight: "700" });
1080
+ const timeFace = useResolvedFont({ fontWeight: "600" });
1081
+ return /* @__PURE__ */ React18.createElement(Animated2.View, { style: [styles6.event, dynamicStyle, draggingAnimatedStyle, resolved?.container] }, /* @__PURE__ */ React18.createElement(Col_default, { style: [{ position: "relative" }, resolved?.content] }, /* @__PURE__ */ React18.createElement(
1082
+ AnimatedTextInput,
1083
+ {
1084
+ editable: false,
1085
+ allowFontScaling: false,
1086
+ underlineColorAndroid: "transparent",
1087
+ style: {
1088
+ width: "100%",
1089
+ fontFamily: timeFace,
1090
+ fontSize: getTextSize(hourHeight),
1091
+ pointerEvents: "none",
1092
+ padding: 0,
1093
+ margin: 0
1094
+ },
1095
+ defaultValue: initialDisplayTime,
1096
+ animatedProps: animatedTimeProps
1097
+ }
1098
+ ), Body ? /* @__PURE__ */ React18.createElement(Body, { event: selectedEvent, ctx: { hourHeight } }) : /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(Row_default, { style: { alignItems: "center", height: 18 } }, /* @__PURE__ */ React18.createElement(
1099
+ Text,
1100
+ {
1101
+ allowFontScaling: false,
1102
+ style: [{
1103
+ fontFamily: titleFace,
1104
+ fontSize: getTextSize(hourHeight)
1105
+ }, resolved?.title]
1106
+ },
1107
+ selectedEvent?.title
1108
+ )), /* @__PURE__ */ React18.createElement(
1109
+ Text,
1110
+ {
1111
+ allowFontScaling: false,
1112
+ style: [{
1113
+ fontFamily: timeFace,
1114
+ fontSize: getTextSize(hourHeight)
1115
+ }, resolved?.desc]
1116
+ },
1117
+ selectedEvent?.description
1118
+ )), /* @__PURE__ */ React18.createElement(Row_default, { style: {
1119
+ position: "absolute",
1120
+ right: 2
1121
+ }, space: 2 }, TopRight ? /* @__PURE__ */ React18.createElement(TopRight, { event: selectedEvent, ctx: { hourHeight } }) : null)), /* @__PURE__ */ React18.createElement(Row_default, { style: {
1122
+ position: "absolute",
1123
+ alignSelf: "center",
1124
+ bottom: 0
1125
+ } }, /* @__PURE__ */ React18.createElement(MaterialIcons, { name: "drag-handle", size: 12, color: "black" })));
1126
+ };
1127
+ var styles6 = StyleSheet.create({
1128
+ event: {
1129
+ backgroundColor: "#4d959c",
1130
+ position: "absolute",
1131
+ borderRadius: 5,
1132
+ padding: 2,
1133
+ overflow: "hidden",
1134
+ zIndex: 99
1135
+ // Ensure events stay above the background blocks
1136
+ }
1137
+ });
1138
+ var EventBlocks = React18__default.memo(({
1139
+ id,
1140
+ onLongPress,
1141
+ onPress,
1142
+ hourHeight,
1143
+ EVENT_BLOCK_WIDTH,
1144
+ eventRenderer,
1145
+ isEventDisabled,
1146
+ isEventSelected,
1147
+ mode
1148
+ }) => {
1149
+ const { useEventsFor: useEventsFor2 } = useCalendarBinding();
1150
+ const events = useEventsFor2(id);
1151
+ const frameMap = useMemo(
1152
+ () => computeEventFrames(events, EVENT_BLOCK_WIDTH, mode),
1153
+ [events, mode, EVENT_BLOCK_WIDTH]
1154
+ );
1155
+ const Renderer = eventRenderer;
1156
+ return events?.map(
1157
+ (evt, index) => {
1158
+ const selected = isEventSelected?.(evt) ?? false;
1159
+ const disabled = isEventDisabled?.(evt) ?? false;
1160
+ return /* @__PURE__ */ React18__default.createElement(
1161
+ Renderer,
1162
+ {
1163
+ key: `${evt.from}-${evt.to}-${index}`,
1164
+ event: evt,
1165
+ onLongPress: (evt2) => onLongPress(evt2),
1166
+ onPress: (evt2) => onPress(evt2),
1167
+ hourHeight,
1168
+ frame: frameMap.get(evt.id),
1169
+ selected,
1170
+ disabled
1171
+ }
1172
+ );
1173
+ }
1174
+ );
1175
+ });
1176
+ var EventBlocks_default = EventBlocks;
1177
+
1178
+ // src/components/Calendar.tsx
1179
+ var AnimatedFlashList = Animated2.createAnimatedComponent(FlashList);
1180
+ var CalendarInner = (props) => {
1181
+ const { width } = useWindowDimensions();
1182
+ const isIOS = Platform.OS === "ios";
1183
+ const binding = useCalendarBinding();
1184
+ const {
1185
+ date,
1186
+ numberOfColumns = 3,
1187
+ startMinutes,
1188
+ hourHeight = 120,
1189
+ snapIntervalInMinutes = 5,
1190
+ timezone = Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,
1191
+ resources,
1192
+ onResourcePress,
1193
+ onBlockLongPress,
1194
+ onEventPress,
1195
+ onEventLongPress,
1196
+ onDisabledBlockPress,
1197
+ eventSlots,
1198
+ eventStyleOverrides,
1199
+ overLappingLayoutMode = "stacked"
1200
+ } = props;
1201
+ const snapInterval = hourHeight / 60 * snapIntervalInMinutes;
1202
+ const onPressRef = React18__default.useRef(onEventPress);
1203
+ const onLongPressRef = React18__default.useRef(onEventLongPress);
1204
+ const internalOnLongPress = useRef(null);
1205
+ const onDisabledBlockPressRef = React18__default.useRef(onDisabledBlockPress);
1206
+ const selectedRef = useRef(props.isEventSelected);
1207
+ const disabledRef = useRef(props.isEventDisabled);
1208
+ const effectiveRenderer = useMemo(() => {
1209
+ return (p) => /* @__PURE__ */ React18__default.createElement(
1210
+ EventBlock_default,
1211
+ {
1212
+ ...p,
1213
+ slots: props.eventSlots,
1214
+ styleOverrides: props.eventStyleOverrides
1215
+ }
1216
+ );
1217
+ }, [eventSlots, eventStyleOverrides]);
1218
+ const isEventSelectedStable = useCallback(
1219
+ (ev) => selectedRef.current ? selectedRef.current(ev) : false,
1220
+ []
1221
+ );
1222
+ const isEventDisabledStable = useCallback(
1223
+ (ev) => disabledRef.current ? disabledRef.current(ev) : false,
1224
+ []
1225
+ );
1226
+ useEffect(() => {
1227
+ onPressRef.current = onEventPress;
1228
+ }, [onEventPress]);
1229
+ useEffect(() => {
1230
+ onLongPressRef.current = onEventLongPress;
1231
+ }, [onEventLongPress]);
1232
+ useEffect(() => {
1233
+ onDisabledBlockPressRef.current = onDisabledBlockPress;
1234
+ }, [onDisabledBlockPress]);
1235
+ useEffect(() => {
1236
+ rendererRef.current = effectiveRenderer;
1237
+ }, [effectiveRenderer]);
1238
+ useEffect(() => {
1239
+ selectedRef.current = props.isEventSelected;
1240
+ }, [props.isEventSelected]);
1241
+ useEffect(() => {
1242
+ disabledRef.current = props.isEventDisabled;
1243
+ }, [props.isEventDisabled]);
1244
+ const rendererRef = useRef(effectiveRenderer);
1245
+ const stableRenderer = useCallback((p) => rendererRef.current(p), []);
1246
+ const stableOnPress = React18__default.useCallback((e) => onPressRef.current?.(e), []);
1247
+ const stableOnDisabledBlockPress = React18__default.useCallback((b) => onDisabledBlockPressRef.current?.(b), []);
1248
+ const { useGetSelectedEvent: useGetSelectedEvent2, useSetSelectedEvent: useSetSelectedEvent2, useSetDraggedEventDraft: useSetDraggedEventDraft2, useGetDraggedEventDraft: useGetDraggedEventDraft2 } = useCalendarBinding();
1249
+ const selectedEvent = useGetSelectedEvent2();
1250
+ const setSelectedEvent = useSetSelectedEvent2();
1251
+ const setDraggedEventDraft = useSetDraggedEventDraft2();
1252
+ const APPOINTMENT_BLOCK_WIDTH = (width - TIME_LABEL_WIDTH) / numberOfColumns;
1253
+ const hourHeightRef = useRef(hourHeight);
1254
+ const resourcesRef = useRef(resources);
1255
+ const apptWidthRef = useRef(APPOINTMENT_BLOCK_WIDTH);
1256
+ useEffect(() => {
1257
+ hourHeightRef.current = hourHeight;
1258
+ }, [hourHeight]);
1259
+ useEffect(() => {
1260
+ resourcesRef.current = resources;
1261
+ }, [resources]);
1262
+ useEffect(() => {
1263
+ apptWidthRef.current = APPOINTMENT_BLOCK_WIDTH;
1264
+ }, [APPOINTMENT_BLOCK_WIDTH]);
1265
+ useEffect(() => {
1266
+ if (!selectedEvent) {
1267
+ setDraggedEventDraft(null);
1268
+ }
1269
+ }, [selectedEvent]);
1270
+ const verticalScrollViewRef = useAnimatedRef();
1271
+ const headerScrollViewRef = useAnimatedRef();
1272
+ const flashListRef = useRef(null);
1273
+ const prevResourceIdsRef = useRef([]);
1274
+ const [layout, setLayout] = useState(null);
1275
+ const dateRef = useRef(date);
1276
+ const eventStartedTop = useSharedValue(0);
1277
+ const eventHeight = useSharedValue(0);
1278
+ const panXAbs = useSharedValue(0);
1279
+ const panYAbs = useSharedValue(0);
1280
+ const isPulling = useSharedValue(false);
1281
+ const isDragging = useSharedValue(false);
1282
+ const scrollX = useSharedValue(0);
1283
+ const scrollY = useSharedValue(0);
1284
+ const autoScrollSpeed = useSharedValue(0);
1285
+ const autoScrollXSpeed = useSharedValue(0);
1286
+ const lastHapticScrollY = useSharedValue(0);
1287
+ const lastXScrollTime = useSharedValue(0);
1288
+ const startedX = useSharedValue(0);
1289
+ const startedY = useSharedValue(0);
1290
+ const touchY = useSharedValue(0);
1291
+ const triggerHaptic = useCallback(() => {
1292
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
1293
+ }, []);
1294
+ const panGesture = Gesture.Pan().manualActivation(!isIOS).enabled(layout !== null).shouldCancelWhenOutside(false).onTouchesMove((_evt, stateManager) => {
1295
+ if (isIOS) return;
1296
+ if (selectedEvent)
1297
+ stateManager.activate();
1298
+ else stateManager.end();
1299
+ }).onUpdate((evt) => {
1300
+ if (!evt || evt.y == null || evt.x == null) return;
1301
+ let draggable = false;
1302
+ let pullable = false;
1303
+ const draggableMinY = panYAbs.value - eventHeight.value / 2;
1304
+ const draggableMaxY = panYAbs.value + eventHeight.value / 2 - (eventHeight.value <= snapInterval * 3 * 2 ? snapInterval : snapInterval * 3);
1305
+ const pullableMaxY = panYAbs.value + eventHeight.value / 2;
1306
+ const blockMinX = panXAbs.value - APPOINTMENT_BLOCK_WIDTH / 2;
1307
+ const blockMaxX = panXAbs.value + APPOINTMENT_BLOCK_WIDTH / 2;
1308
+ touchY.value = evt.y;
1309
+ if (evt.x >= blockMinX && evt.x <= blockMaxX) {
1310
+ draggable = evt.y >= draggableMinY && evt.y <= draggableMaxY;
1311
+ pullable = evt.y > draggableMaxY && evt.y <= pullableMaxY + snapInterval * 3;
1312
+ }
1313
+ if (pullable && !isDragging.value || isPulling.value) {
1314
+ isPulling.value = true;
1315
+ const onScreenTop = eventStartedTop.value - scrollY.value;
1316
+ const newHeight = evt.y - onScreenTop;
1317
+ const snappedHeight = Math.round(newHeight / snapInterval) * snapInterval;
1318
+ let finalHeight = Math.max(hourHeight / 4, snappedHeight);
1319
+ const totalDayHeight = 24 * hourHeight;
1320
+ const maxAllowedHeight = totalDayHeight - eventStartedTop.value;
1321
+ finalHeight = Math.min(finalHeight, maxAllowedHeight);
1322
+ if (finalHeight !== eventHeight.value) {
1323
+ eventHeight.value = finalHeight;
1324
+ panYAbs.value = onScreenTop + finalHeight / 2;
1325
+ runOnJS(triggerHaptic)();
1326
+ }
1327
+ if (layout) {
1328
+ const AUTO_SCROLL_BUFFER = 30;
1329
+ if (evt.y > layout.height - AUTO_SCROLL_BUFFER) {
1330
+ autoScrollSpeed.value = 1;
1331
+ } else if (evt.y < AUTO_SCROLL_BUFFER && newHeight > hourHeight / 4) {
1332
+ autoScrollSpeed.value = -1;
1333
+ } else {
1334
+ autoScrollSpeed.value = 0;
1335
+ }
1336
+ } else {
1337
+ autoScrollSpeed.value = 0;
1338
+ }
1339
+ }
1340
+ if (draggable && !isPulling.value || isDragging.value) {
1341
+ isDragging.value = true;
1342
+ const translatedY = Math.round(evt.translationY / snapInterval) * snapInterval;
1343
+ const proposedAbsoluteTop = startedY.value - eventHeight.value / 2 + translatedY + scrollY.value;
1344
+ let snappedAbsoluteTop = Math.round(proposedAbsoluteTop / snapInterval) * snapInterval;
1345
+ snappedAbsoluteTop = Math.max(0, snappedAbsoluteTop);
1346
+ if (layout) {
1347
+ const maxAbsoluteTop = layout.height + scrollY.value - snapInterval;
1348
+ snappedAbsoluteTop = Math.min(snappedAbsoluteTop, maxAbsoluteTop);
1349
+ }
1350
+ if (snappedAbsoluteTop !== eventStartedTop.value) {
1351
+ runOnJS(triggerHaptic)();
1352
+ eventStartedTop.value = snappedAbsoluteTop;
1353
+ }
1354
+ panYAbs.value = snappedAbsoluteTop - scrollY.value + eventHeight.value / 2;
1355
+ let panXAbsValue = Math.max(
1356
+ APPOINTMENT_BLOCK_WIDTH / 2 + TIME_LABEL_WIDTH,
1357
+ startedX.value + evt.translationX
1358
+ );
1359
+ if (layout?.width) {
1360
+ panXAbsValue = Math.min(
1361
+ layout.width - APPOINTMENT_BLOCK_WIDTH / 2,
1362
+ panXAbsValue
1363
+ );
1364
+ }
1365
+ panXAbs.value = panXAbsValue;
1366
+ if (layout) {
1367
+ const AUTO_SCROLL_BUFFER = 30;
1368
+ if (evt.y > layout.height - AUTO_SCROLL_BUFFER) {
1369
+ autoScrollSpeed.value = 1;
1370
+ } else if (evt.y < AUTO_SCROLL_BUFFER) {
1371
+ autoScrollSpeed.value = -1;
1372
+ } else {
1373
+ autoScrollSpeed.value = 0;
1374
+ }
1375
+ if (panXAbs.value >= layout.width - APPOINTMENT_BLOCK_WIDTH / 2) {
1376
+ autoScrollXSpeed.value = 1;
1377
+ } else if (panXAbs.value <= APPOINTMENT_BLOCK_WIDTH / 2 + TIME_LABEL_WIDTH) {
1378
+ autoScrollXSpeed.value = -1;
1379
+ } else {
1380
+ autoScrollXSpeed.value = 0;
1381
+ }
1382
+ } else {
1383
+ autoScrollSpeed.value = 0;
1384
+ autoScrollXSpeed.value = 0;
1385
+ }
1386
+ }
1387
+ }).onEnd(() => {
1388
+ autoScrollSpeed.value = 0;
1389
+ autoScrollXSpeed.value = 0;
1390
+ lastXScrollTime.value = 0;
1391
+ const finalEventTop = panYAbs.value - eventHeight.value / 2 + scrollY.value;
1392
+ let adjustedFinalEventTop = Math.round(finalEventTop / snapInterval) * snapInterval;
1393
+ adjustedFinalEventTop = Math.max(0, adjustedFinalEventTop);
1394
+ const finalPanYValue = adjustedFinalEventTop - scrollY.value + eventHeight.value / 2;
1395
+ const finalXOnScreen = panXAbs.value;
1396
+ const absoluteX = finalXOnScreen + scrollX.value;
1397
+ const newStaffIndex = Math.floor((absoluteX - TIME_LABEL_WIDTH) / APPOINTMENT_BLOCK_WIDTH);
1398
+ const clampedStaffIndex = Math.max(0, Math.min(newStaffIndex, resources.length - 1));
1399
+ const endedResource = resources[clampedStaffIndex];
1400
+ const finalPanXValue = TIME_LABEL_WIDTH + clampedStaffIndex * APPOINTMENT_BLOCK_WIDTH - scrollX.value + APPOINTMENT_BLOCK_WIDTH / 2;
1401
+ panYAbs.value = withSpring(finalPanYValue);
1402
+ panXAbs.value = withSpring(finalPanXValue);
1403
+ if (!isPulling.value) {
1404
+ eventStartedTop.value = adjustedFinalEventTop;
1405
+ }
1406
+ startedY.value = finalPanYValue;
1407
+ startedX.value = finalPanXValue;
1408
+ isPulling.value = false;
1409
+ isDragging.value = false;
1410
+ runOnJS(setDraggedEventDraft)({
1411
+ event: selectedEvent,
1412
+ from: positionToMinutes(adjustedFinalEventTop, hourHeight),
1413
+ to: positionToMinutes(adjustedFinalEventTop + eventHeight.value, hourHeight),
1414
+ resourceId: endedResource?.id
1415
+ });
1416
+ });
1417
+ const scrollListTo = (x) => {
1418
+ flashListRef.current?.scrollToOffset({ offset: x, animated: false });
1419
+ };
1420
+ useFrameCallback((frameInfo) => {
1421
+ if (autoScrollXSpeed.value === 0) {
1422
+ return;
1423
+ }
1424
+ const now = frameInfo.timeSinceFirstFrame;
1425
+ const scrollInterval = 500;
1426
+ if (now - lastXScrollTime.value > scrollInterval) {
1427
+ lastXScrollTime.value = now;
1428
+ const increment = APPOINTMENT_BLOCK_WIDTH * Math.sign(autoScrollXSpeed.value);
1429
+ const newScrollX = scrollX.value + increment;
1430
+ runOnJS(scrollListTo)(newScrollX);
1431
+ runOnJS(Haptics.impactAsync)(Haptics.ImpactFeedbackStyle.Medium);
1432
+ }
1433
+ });
1434
+ useFrameCallback(() => {
1435
+ if (autoScrollSpeed.value === 0) {
1436
+ return;
1437
+ }
1438
+ const increment = snapInterval / 5 * Math.sign(autoScrollSpeed.value);
1439
+ const newScrollY = scrollY.value + increment;
1440
+ scrollTo(verticalScrollViewRef, 0, newScrollY, false);
1441
+ if (isDragging.value) {
1442
+ let currentEventTop = panYAbs.value - eventHeight.value / 2 + newScrollY;
1443
+ currentEventTop = Math.round(currentEventTop / snapInterval) * snapInterval;
1444
+ eventStartedTop.value = Math.max(0, currentEventTop);
1445
+ }
1446
+ if (isPulling.value) {
1447
+ const onScreenTop = eventStartedTop.value - newScrollY;
1448
+ const newHeight = touchY.value - onScreenTop;
1449
+ const snappedHeight = Math.round(newHeight / snapInterval) * snapInterval;
1450
+ let finalHeight = Math.max(hourHeight / 4, snappedHeight);
1451
+ const totalDayHeight = 24 * hourHeight;
1452
+ const maxAllowedHeight = totalDayHeight - eventStartedTop.value;
1453
+ finalHeight = Math.min(finalHeight, maxAllowedHeight);
1454
+ if (finalHeight !== eventHeight.value) {
1455
+ eventHeight.value = finalHeight;
1456
+ panYAbs.value = onScreenTop + finalHeight / 2;
1457
+ }
1458
+ if (hourHeight / 4 == finalHeight) {
1459
+ autoScrollSpeed.value = 0;
1460
+ }
1461
+ }
1462
+ const scrollDiff = Math.abs(newScrollY - lastHapticScrollY.value);
1463
+ if (scrollDiff >= snapInterval) {
1464
+ lastHapticScrollY.value = newScrollY;
1465
+ runOnJS(Haptics.impactAsync)(Haptics.ImpactFeedbackStyle.Medium);
1466
+ }
1467
+ });
1468
+ useEffect(() => {
1469
+ internalOnLongPress.current = (event) => {
1470
+ onLongPressRef.current?.(event);
1471
+ const hh = hourHeightRef.current;
1472
+ const eventTop = scalePosition(event.from, hh);
1473
+ const eventTo = event.to < event.from ? event.to + 1440 : event.to;
1474
+ const initialHeight = scalePosition(eventTo - event.from, hh);
1475
+ const panAbsValue = eventTop - scrollY.value + initialHeight / 2;
1476
+ panYAbs.value = panAbsValue;
1477
+ startedY.value = panAbsValue;
1478
+ eventStartedTop.value = eventTop;
1479
+ const resources2 = resourcesRef.current;
1480
+ const APPOINTMENT_BLOCK_WIDTH2 = apptWidthRef.current;
1481
+ const staffIndex = resources2.findIndex((r) => r.id === event.resourceId);
1482
+ const leftmostColumnIndex = Math.round(scrollX.value / APPOINTMENT_BLOCK_WIDTH2);
1483
+ const screenColumn = staffIndex - leftmostColumnIndex;
1484
+ const selectedAppointmentStartedX = TIME_LABEL_WIDTH + APPOINTMENT_BLOCK_WIDTH2 / 2 + APPOINTMENT_BLOCK_WIDTH2 * screenColumn;
1485
+ panXAbs.value = selectedAppointmentStartedX;
1486
+ startedX.value = selectedAppointmentStartedX;
1487
+ lastHapticScrollY.value = scrollY.value;
1488
+ eventHeight.value = initialHeight;
1489
+ setSelectedEvent(event);
1490
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
1491
+ };
1492
+ }, []);
1493
+ const internalStableOnLongPress = useCallback((e) => {
1494
+ internalOnLongPress.current?.(e);
1495
+ }, []);
1496
+ const onLayout = useCallback((evt) => {
1497
+ setLayout(evt?.nativeEvent?.layout);
1498
+ }, []);
1499
+ const verticalScrollHandler = useAnimatedScrollHandler({
1500
+ onScroll: (event) => {
1501
+ scrollY.value = event?.contentOffset?.y;
1502
+ }
1503
+ });
1504
+ const flashListScrollHandler = useAnimatedScrollHandler({
1505
+ onScroll: (event) => {
1506
+ const offsetX = event?.contentOffset?.x;
1507
+ scrollTo(headerScrollViewRef, offsetX, 0, false);
1508
+ scrollX.value = offsetX;
1509
+ }
1510
+ });
1511
+ const resourceIds = useMemo(() => {
1512
+ const ids = resources?.map((item) => item?.id) || [];
1513
+ if (JSON.stringify(prevResourceIdsRef.current) !== JSON.stringify(ids)) {
1514
+ prevResourceIdsRef.current = ids;
1515
+ }
1516
+ return prevResourceIdsRef.current;
1517
+ }, [resources]);
1518
+ const handleBlockPress = useCallback((resourceId, time) => {
1519
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
1520
+ const resource = resources.find((r) => r.id === resourceId);
1521
+ if (onBlockLongPress)
1522
+ onBlockLongPress(resource, new Date(time));
1523
+ }, [resources, onBlockLongPress]);
1524
+ useEffect(() => {
1525
+ const handleOrientationChange = () => {
1526
+ if (selectedEvent)
1527
+ setSelectedEvent(null);
1528
+ };
1529
+ const subscription = Dimensions.addEventListener("change", handleOrientationChange);
1530
+ return () => {
1531
+ subscription.remove();
1532
+ };
1533
+ }, [setSelectedEvent, selectedEvent]);
1534
+ useEffect(() => {
1535
+ dateRef.current = date;
1536
+ }, [date]);
1537
+ const renderItem = useCallback(({ item }) => {
1538
+ return /* @__PURE__ */ React18__default.createElement(View, { key: item, style: { width: APPOINTMENT_BLOCK_WIDTH } }, /* @__PURE__ */ React18__default.createElement(View, { style: styles7.timelineContainer }, /* @__PURE__ */ React18__default.createElement(
1539
+ EventGridBlocksSkia,
1540
+ {
1541
+ dateRef,
1542
+ hourHeight,
1543
+ APPOINTMENT_BLOCK_WIDTH,
1544
+ handleBlockPress: (date2) => handleBlockPress(item, date2)
1545
+ }
1546
+ ), /* @__PURE__ */ React18__default.createElement(
1547
+ DisabledIntervals_default,
1548
+ {
1549
+ id: item,
1550
+ APPOINTMENT_BLOCK_WIDTH,
1551
+ hourHeight
1552
+ }
1553
+ ), /* @__PURE__ */ React18__default.createElement(
1554
+ DisabledBlocks_default,
1555
+ {
1556
+ id: item,
1557
+ APPOINTMENT_BLOCK_WIDTH,
1558
+ hourHeight,
1559
+ onDisabledBlockPress: stableOnDisabledBlockPress
1560
+ }
1561
+ ), /* @__PURE__ */ React18__default.createElement(
1562
+ EventBlocks_default,
1563
+ {
1564
+ id: item,
1565
+ EVENT_BLOCK_WIDTH: APPOINTMENT_BLOCK_WIDTH,
1566
+ hourHeight,
1567
+ onPress: stableOnPress,
1568
+ onLongPress: internalStableOnLongPress,
1569
+ isEventSelected: isEventSelectedStable,
1570
+ isEventDisabled: isEventDisabledStable,
1571
+ eventRenderer: stableRenderer,
1572
+ mode: overLappingLayoutMode
1573
+ }
1574
+ )));
1575
+ }, [
1576
+ resourceIds,
1577
+ APPOINTMENT_BLOCK_WIDTH,
1578
+ hourHeight,
1579
+ stableRenderer,
1580
+ isEventSelectedStable,
1581
+ isEventDisabledStable,
1582
+ overLappingLayoutMode,
1583
+ stableOnPress,
1584
+ internalStableOnLongPress,
1585
+ stableOnDisabledBlockPress,
1586
+ dateRef
1587
+ ]);
1588
+ return /* @__PURE__ */ React18__default.createElement(React18__default.Fragment, null, /* @__PURE__ */ React18__default.createElement(StoreFeeder, { resources, store: binding }), /* @__PURE__ */ React18__default.createElement(View, { style: { flex: 1 } }, /* @__PURE__ */ React18__default.createElement(View, null, /* @__PURE__ */ React18__default.createElement(
1589
+ Animated2.ScrollView,
1590
+ {
1591
+ style: { backgroundColor: "white" },
1592
+ showsHorizontalScrollIndicator: false,
1593
+ contentContainerStyle: {
1594
+ overflow: "visible",
1595
+ paddingLeft: TIME_LABEL_WIDTH,
1596
+ paddingVertical: 15
1597
+ },
1598
+ horizontal: true,
1599
+ scrollEventThrottle: 16,
1600
+ decelerationRate: "fast",
1601
+ ref: headerScrollViewRef,
1602
+ scrollEnabled: false
1603
+ },
1604
+ /* @__PURE__ */ React18__default.createElement(
1605
+ ResourcesComponent,
1606
+ {
1607
+ resourceIds,
1608
+ APPOINTMENT_BLOCK_WIDTH,
1609
+ onResourcePress
1610
+ }
1611
+ )
1612
+ )), /* @__PURE__ */ React18__default.createElement(GestureDetector, { gesture: panGesture }, /* @__PURE__ */ React18__default.createElement(
1613
+ Animated2.View,
1614
+ {
1615
+ key: numberOfColumns + width + hourHeight,
1616
+ onLayout,
1617
+ style: {
1618
+ flex: 1,
1619
+ overflow: "hidden"
1620
+ }
1621
+ },
1622
+ selectedEvent && /* @__PURE__ */ React18__default.createElement(View, { style: {
1623
+ position: "absolute",
1624
+ top: 0,
1625
+ left: TIME_LABEL_WIDTH,
1626
+ paddingLeft: TIME_LABEL_WIDTH,
1627
+ width: width - TIME_LABEL_WIDTH,
1628
+ height: "100%",
1629
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
1630
+ zIndex: 1
1631
+ } }),
1632
+ /* @__PURE__ */ React18__default.createElement(
1633
+ Animated2.ScrollView,
1634
+ {
1635
+ scrollEnabled: !selectedEvent,
1636
+ onScroll: verticalScrollHandler,
1637
+ ref: verticalScrollViewRef,
1638
+ scrollEventThrottle: 16,
1639
+ snapToInterval: hourHeight,
1640
+ decelerationRate: "fast",
1641
+ snapToAlignment: "start",
1642
+ style: styles7.container,
1643
+ contentContainerStyle: { flexDirection: "row", paddingRight: TIME_LABEL_WIDTH }
1644
+ },
1645
+ /* @__PURE__ */ React18__default.createElement(
1646
+ TimeLabels,
1647
+ {
1648
+ startMinutes,
1649
+ layout,
1650
+ hourHeight,
1651
+ totalTimelineWidth: APPOINTMENT_BLOCK_WIDTH * numberOfColumns,
1652
+ timezone,
1653
+ date,
1654
+ ref: verticalScrollViewRef
1655
+ }
1656
+ ),
1657
+ /* @__PURE__ */ React18__default.createElement(
1658
+ AnimatedFlashList,
1659
+ {
1660
+ extraData: numberOfColumns + width + hourHeight + (overLappingLayoutMode === "stacked" ? 1 : 0),
1661
+ scrollEnabled: !selectedEvent,
1662
+ ref: flashListRef,
1663
+ onScroll: flashListScrollHandler,
1664
+ estimatedItemSize: APPOINTMENT_BLOCK_WIDTH,
1665
+ removeClippedSubviews: true,
1666
+ data: resourceIds,
1667
+ horizontal: true,
1668
+ renderItem,
1669
+ keyExtractor: (item, index) => index + "",
1670
+ snapToInterval: APPOINTMENT_BLOCK_WIDTH,
1671
+ decelerationRate: "fast",
1672
+ snapToAlignment: "start"
1673
+ }
1674
+ )
1675
+ ),
1676
+ selectedEvent && /* @__PURE__ */ React18__default.createElement(
1677
+ DraggableEvent,
1678
+ {
1679
+ selectedEvent,
1680
+ APPOINTMENT_BLOCK_WIDTH,
1681
+ hourHeight,
1682
+ eventStartedTop,
1683
+ eventHeight,
1684
+ panXAbs,
1685
+ panYAbs,
1686
+ slots: props.eventSlots,
1687
+ styleOverrides: props.eventStyleOverrides
1688
+ }
1689
+ )
1690
+ ))));
1691
+ };
1692
+ var Calendar = ({ theme, ...rest }) => {
1693
+ return /* @__PURE__ */ React18__default.createElement(CalendarThemeProvider, { theme }, /* @__PURE__ */ React18__default.createElement(CalendarInner, { ...rest }));
1694
+ };
1695
+ var styles7 = StyleSheet.create({
1696
+ container: {
1697
+ flex: 1,
1698
+ backgroundColor: "#fff"
1699
+ },
1700
+ timelineContainer: {
1701
+ borderColor: "#ddd",
1702
+ borderRightWidth: 1,
1703
+ position: "relative",
1704
+ height: "100%"
1705
+ }
1706
+ });
1707
+ var Calendar_default = Calendar;
1708
+
1709
+ export { Calendar_default as Calendar, CalendarBindingProvider, useCalendarBinding };
1710
+ //# sourceMappingURL=index.mjs.map
1711
+ //# sourceMappingURL=index.mjs.map