Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/example/app/(drawer)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ const allDayEvents: EventItem[] = [
},
];

const TOTAL_RESOURCES = 50;
const TOTAL_RESOURCES = 10;

const generateEvents = () => {
return new Array(500)
Expand Down
13 changes: 11 additions & 2 deletions packages/react-native-calendar-kit/src/CalendarBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ const CalendarBody: React.FC<CalendarBodyProps> = ({
resourcePerPage,
resourcePagingEnabled,
linkedScrollGroup,
dateResourceItems,
daySnapOffsets,
handleResourceScrollOffsetChange,
} = useCalendar();
const { onTouchStart, onWheel } = linkedScrollGroup.addAndGet(
ScrollType.calendarGrid,
Expand Down Expand Up @@ -195,9 +198,11 @@ const CalendarBody: React.FC<CalendarBodyProps> = ({

const _renderResourceItem = useCallback(
(item: { items: ResourceItem[]; index: number }) => {
return <BodyResourceItem resources={item.items} />;
// In dual-axis mode, get the date for this specific item
const dateUnix = dateResourceItems?.[item.index]?.date;
return <BodyResourceItem resources={item.items} dateUnix={dateUnix} />;
},
[]
[dateResourceItems]
);

const value = useMemo<BodyContextProps>(
Expand Down Expand Up @@ -360,6 +365,7 @@ const CalendarBody: React.FC<CalendarBodyProps> = ({
<ResourceListView
ref={gridListRef}
resources={resources}
items={dateResourceItems}
width={calendarGridWidth}
height={maxTimelineHeight + EXTRA_HEIGHT * 2}
resourcePerPage={resourcePerPage}
Expand All @@ -369,6 +375,9 @@ const CalendarBody: React.FC<CalendarBodyProps> = ({
scrollEnabled={allowHorizontalSwipe}
onTouchStart={onTouchStart}
onWheel={onWheel}
snapToOffsets={daySnapOffsets}
onScrollOffsetChange={handleResourceScrollOffsetChange}
initialOffset={initialOffset}
/>
) : (
<CalendarListView
Expand Down
208 changes: 186 additions & 22 deletions packages/react-native-calendar-kit/src/CalendarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,12 @@ const CalendarContainer: React.ForwardRefRenderFunction<
: (initialUseAllDayEvent ?? true);
const hideWeekDaysCount = hideWeekDays.length;
const daysToShow = 7 - hideWeekDaysCount;
const numberOfDays = isResourceMode
? 1
: initialNumberOfDays > daysToShow
? daysToShow
: initialNumberOfDays;
const numberOfDays =
isResourceMode && !enableResourceScroll
? 1
: initialNumberOfDays > daysToShow
? daysToShow
: initialNumberOfDays;

const isSingleDay = numberOfDays === 1;
const columns = isSingleDay ? 1 : daysToShow;
Expand Down Expand Up @@ -276,7 +277,14 @@ const CalendarContainer: React.ForwardRefRenderFunction<
}
const nearestIndex = nearestDate.index;
if (isSingleDay || scrollByDay) {
const colWidth = isSingleDay ? calendarGridWidth : columnWidth;
let colWidth = isSingleDay ? calendarGridWidth : columnWidth;

// For resource mode with enableResourceScroll, calculate day width
if (isResourceMode && enableResourceScroll && resources) {
const resourceWidth = calendarGridWidth / resourcePerPage;
colWidth = resources.length * resourceWidth;
}

return nearestIndex * colWidth;
}

Expand All @@ -290,12 +298,14 @@ const CalendarContainer: React.ForwardRefRenderFunction<
isSingleDay,
scrollByDay,
visibleDateUnix,
isResourceMode,
enableResourceScroll,
resources,
resourcePerPage,
]);

const offsetY = useSharedValue(0);
const offsetX = useSharedValue(
isResourceMode && enableResourceScroll ? 0 : initialOffset
);
const offsetX = useSharedValue(initialOffset);
const linkedScrollGroup = useLinkedScrollGroup(offsetX);
const scrollVisibleHeightAnim = useSharedValue(0);
const timeIntervalHeight = useSharedValue(initialTimeIntervalHeight);
Expand Down Expand Up @@ -332,14 +342,28 @@ const CalendarContainer: React.ForwardRefRenderFunction<
offset = pageIndex * (columnWidth * columns);
}

if (isResourceMode && enableResourceScroll) {
if (isResourceMode && enableResourceScroll && resources) {
visibleDateUnix.current = nearestUnix;
visibleDateUnixAnim.value = nearestUnix;
visibleDateRef.current?.updateVisibleDate(nearestUnix);
const dateObj = forceUpdateZone(nearestUnix, timeZone);
const newDate = dateTimeToISOString(dateObj);
onDateChanged?.(newDate);
onChange?.(newDate);

// Calculate the scroll offset for the date
const resourceWidth = calendarGridWidth / resourcePerPage;
const dayOffset = visibleDayIndex * resources.length * resourceWidth;

linkedScrollGroup.setActiveId(ScrollType.calendarGrid);
const animatedDate =
props?.animatedDate !== undefined ? props.animatedDate : true;

runOnUI(() => {
offsetX.value = dayOffset;
scrollTo(dayBarListRef, dayOffset, 0, animatedDate);
scrollTo(gridListRef, dayOffset, 0, animatedDate);
})();
} else {
const isScrollable = calendarListRef.current?.isScrollable(
offset,
Expand Down Expand Up @@ -408,14 +432,25 @@ const CalendarContainer: React.ForwardRefRenderFunction<
}

const nextDateUnix = visibleDatesArray[nextVisibleDayIndex];
if (isResourceMode && enableResourceScroll && nextDateUnix) {
if (isResourceMode && enableResourceScroll && nextDateUnix && resources) {
visibleDateUnix.current = nextDateUnix;
visibleDateUnixAnim.value = nextDateUnix;
visibleDateRef.current?.updateVisibleDate(nextDateUnix);
const dateObj = forceUpdateZone(nextDateUnix, timeZone);
const newDate = dateTimeToISOString(dateObj);
onDateChanged?.(newDate);
onChange?.(newDate);

// Calculate the scroll offset for the new day
const resourceWidth = calendarGridWidth / resourcePerPage;
const dayOffset = nextVisibleDayIndex * resources.length * resourceWidth;

linkedScrollGroup.setActiveId(ScrollType.calendarGrid);
runOnUI(() => {
offsetX.value = dayOffset;
scrollTo(dayBarListRef, dayOffset, 0, animated);
scrollTo(gridListRef, dayOffset, 0, animated);
})();
return;
}

Expand Down Expand Up @@ -466,14 +501,25 @@ const CalendarContainer: React.ForwardRefRenderFunction<
numberOfDays
);
const nextDateUnix = visibleDatesArray[nextVisibleDayIndex];
if (isResourceMode && enableResourceScroll && nextDateUnix) {
if (isResourceMode && enableResourceScroll && nextDateUnix && resources) {
visibleDateUnix.current = nextDateUnix;
visibleDateUnixAnim.value = nextDateUnix;
visibleDateRef.current?.updateVisibleDate(nextDateUnix);
const dateObj = forceUpdateZone(nextDateUnix, timeZone);
const newDate = dateTimeToISOString(dateObj);
onDateChanged?.(newDate);
onChange?.(newDate);

// Calculate the scroll offset for the new day
const resourceWidth = calendarGridWidth / resourcePerPage;
const dayOffset = nextVisibleDayIndex * resources.length * resourceWidth;

linkedScrollGroup.setActiveId(ScrollType.calendarGrid);
runOnUI(() => {
offsetX.value = dayOffset;
scrollTo(dayBarListRef, dayOffset, 0, animated);
scrollTo(gridListRef, dayOffset, 0, animated);
})();
return;
}

Expand Down Expand Up @@ -638,14 +684,19 @@ const CalendarContainer: React.ForwardRefRenderFunction<

const goToNextResource = useLatestCallback(
(animated?: boolean, resourceScrollType?: 'resource' | 'page') => {
const resourceWidth = columnWidth / resourcePerPage;
if (!isResourceMode || !enableResourceScroll) {
console.warn('Only available for resource mode (enableResourceScroll)');
return;
}

const resourceWidth = calendarGridWidth / resourcePerPage;
let nextOffset = 0;
let mode = resourcePagingEnabled ? 'page' : 'resource';
if (resourceScrollType) {
mode = resourceScrollType;
}
if (mode === 'page') {
nextOffset = offsetX.value + columnWidth;
nextOffset = offsetX.value + calendarGridWidth;
} else {
nextOffset = offsetX.value + resourceWidth;
}
Expand All @@ -667,19 +718,24 @@ const CalendarContainer: React.ForwardRefRenderFunction<

const goToPrevResource = useLatestCallback(
(animated?: boolean, resourceScrollType?: 'resource' | 'page') => {
const resourceWidth = columnWidth / resourcePerPage;
if (!isResourceMode || !enableResourceScroll) {
console.warn('Only available for resource mode (enableResourceScroll)');
return;
}

const resourceWidth = calendarGridWidth / resourcePerPage;
let nextOffset = 0;
let mode = resourcePagingEnabled ? 'page' : 'resource';
if (resourceScrollType) {
mode = resourceScrollType;
}
if (mode === 'page') {
nextOffset = offsetX.value - columnWidth;
nextOffset = offsetX.value - calendarGridWidth;
} else {
nextOffset = offsetX.value - resourceWidth;
}
if (nextOffset < 0) {
return;
nextOffset = 0;
}

linkedScrollGroup.setActiveId(ScrollType.calendarGrid);
Expand Down Expand Up @@ -730,12 +786,114 @@ const CalendarContainer: React.ForwardRefRenderFunction<
useImperativeHandle(ref, () => calendarMethods, [calendarMethods]);

useEffect(() => {
if (enableResourceScroll && isResourceMode) {
offsetX.value = 0;
} else {
offsetX.value = initialOffset;
offsetX.value = initialOffset;
}, [initialOffset, offsetX]);

const dateResourceItems = useMemo(() => {
if (!enableResourceScroll || !isResourceMode || !resources) {
return undefined;
}
}, [enableResourceScroll, initialOffset, isResourceMode, offsetX]);

const visibleDatesArray = calendarData.visibleDatesArray;
const items = visibleDatesArray.flatMap((date) =>
resources.map((resource) => ({ date, resource }))
);

return items;
}, [enableResourceScroll, isResourceMode, resources, calendarData]);

const daySnapOffsets = useMemo(() => {
if (!enableResourceScroll || !isResourceMode || !resources) {
return undefined;
}

const visibleDatesArray = calendarData.visibleDatesArray;
const resourceWidth = calendarGridWidth / resourcePerPage;
const resourcesCount = resources.length;

const offsets: number[] = [];

visibleDatesArray.forEach((_, dayIndex) => {
const dayStartOffset = dayIndex * resourcesCount * resourceWidth;

if (resourcesCount <= resourcePerPage) {
// If all resources fit in one page, only snap at the start of the day
offsets.push(dayStartOffset);
} else {
// Snap at every resource position that keeps all visible resources within the current day
// Last valid snap position is where the last resource of the day is at the right edge
const maxResourceStartIndex = resourcesCount - resourcePerPage;

for (let i = 0; i <= maxResourceStartIndex; i++) {
offsets.push(dayStartOffset + i * resourceWidth);
}
}
});

return offsets;
}, [
enableResourceScroll,
isResourceMode,
resources,
calendarData,
calendarGridWidth,
resourcePerPage,
]);

const handleResourceScrollOffsetChange = useLatestCallback(
(scrollOffset: number) => {
if (
!enableResourceScroll ||
!isResourceMode ||
!resources ||
!dateResourceItems
) {
return;
}

const resourceWidth = calendarGridWidth / resourcePerPage;
const viewportStart = scrollOffset;
const viewportEnd = scrollOffset + calendarGridWidth;

// Calculate which resource items are visible
// Use Math.floor for start, but subtract 0.5 from end to avoid counting items at exact boundary
const startItemIndex = Math.floor(viewportStart / resourceWidth);
const endItemIndex = Math.floor((viewportEnd - 0.5) / resourceWidth);

// Count visible resources per day
const dayCounts = new Map<number, number>();
for (
let i = startItemIndex;
i <= Math.min(endItemIndex, dateResourceItems.length - 1);
i++
) {
const item = dateResourceItems[i];
if (item) {
const count = dayCounts.get(item.date) || 0;
dayCounts.set(item.date, count + 1);
}
}

// Select the latest date that has at least one visible resource
const visibleDates = Array.from(dayCounts.keys()).sort((a, b) => a - b);
const activeDayUnix =
visibleDates.length > 0
? visibleDates[visibleDates.length - 1]
: visibleDateUnix.current;

if (activeDayUnix && activeDayUnix !== visibleDateUnix.current) {
visibleDateUnix.current = activeDayUnix;
visibleDateUnixAnim.value = activeDayUnix;
visibleDateRef.current?.updateVisibleDate(activeDayUnix);

const dateObj = forceUpdateZone(activeDayUnix, timeZone);
const newDate = dateTimeToISOString(dateObj);

onDateChanged?.(newDate);
onChange?.(newDate);
}
}
);

const snapToInterval =
numberOfDays > 1 && scrollByDay && !isResourceMode
Expand Down Expand Up @@ -798,6 +956,9 @@ const CalendarContainer: React.ForwardRefRenderFunction<
resourcePerPage,
resourcePagingEnabled,
linkedScrollGroup,
dateResourceItems,
daySnapOffsets,
handleResourceScrollOffsetChange,
}),
[
calendarLayout,
Expand Down Expand Up @@ -852,6 +1013,9 @@ const CalendarContainer: React.ForwardRefRenderFunction<
resourcePerPage,
resourcePagingEnabled,
linkedScrollGroup,
dateResourceItems,
daySnapOffsets,
handleResourceScrollOffsetChange,
]
);

Expand Down
Loading