Skip to content

Commit

Permalink
Merge branch 'master' of github.com:wix/react-native-calendars into r…
Browse files Browse the repository at this point in the history
…elease
  • Loading branch information
Inbal-Tish committed Oct 31, 2023
2 parents b1f5441 + cea10c8 commit 95418db
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 90 deletions.
3 changes: 2 additions & 1 deletion example/src/screens/timelineCalendarScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default class TimelineCalendarScreen extends Component {
[`${getDate(4)}`]: {marked: true}
};

onDateChanged = (date: string) => {
onDateChanged = (date: string, source: string) => {
console.log('TimelineCalendarScreen onDateChanged: ', date, source);
this.setState({currentDate: date});
};

Expand Down
33 changes: 22 additions & 11 deletions src/calendar-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import findIndex from 'lodash/findIndex';
import PropTypes from 'prop-types';
import XDate from 'xdate';

import React, {forwardRef, useImperativeHandle, useRef, useEffect, useState, useCallback, useMemo} from 'react';
import {FlatList, View, ViewStyle, FlatListProps} from 'react-native';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {FlatList, FlatListProps, View, ViewStyle} from 'react-native';

import {extractHeaderProps, extractCalendarProps} from '../componentUpdater';
import {xdateToData, parseDate, toMarkingFormat} from '../interface';
import {extractCalendarProps, extractHeaderProps} from '../componentUpdater';
import {parseDate, toMarkingFormat, xdateToData} from '../interface';
import {page, sameDate, sameMonth} from '../dateutils';
import constants from '../commons/constants';
import {useDidUpdate} from '../hooks';
Expand All @@ -15,6 +15,7 @@ import styleConstructor from './style';
import Calendar, {CalendarProps} from '../calendar';
import CalendarListItem from './item';
import CalendarHeader from '../calendar/header/index';
import isEqual from 'lodash/isEqual';

const CALENDAR_WIDTH = constants.screenWidth;
const CALENDAR_HEIGHT = 360;
Expand Down Expand Up @@ -108,13 +109,15 @@ const CalendarList = (props: CalendarListProps & ContextProp, ref: any) => {

const [currentMonth, setCurrentMonth] = useState(parseDate(current));

const shouldUseAndroidRTLFix = useMemo(() => constants.isAndroidRTL && horizontal, [horizontal]);

const style = useRef(styleConstructor(theme));
const list = useRef();
const range = useRef(horizontal ? 1 : 3);
const initialDate = useRef(parseDate(current) || new XDate());
const visibleMonth = useRef(currentMonth);

const items = useMemo(() => {
const items: XDate[] = useMemo(() => {
const months: any[] = [];
for (let i = 0; i <= pastScrollRange + futureScrollRange; i++) {
const rangeDate = initialDate.current?.clone().addMonths(i - pastScrollRange, true);
Expand Down Expand Up @@ -184,13 +187,13 @@ const CalendarList = (props: CalendarListProps & ContextProp, ref: any) => {
const scrollToMonth = useCallback((date: XDate | string) => {
const scrollTo = parseDate(date);
const diffMonths = Math.round(initialDate?.current?.clone().setDate(1).diffMonths(scrollTo?.clone().setDate(1)));
const scrollAmount = calendarSize * pastScrollRange + diffMonths * calendarSize;
const scrollAmount = calendarSize * (shouldUseAndroidRTLFix ? pastScrollRange - diffMonths : pastScrollRange + diffMonths);

if (scrollAmount !== 0) {
// @ts-expect-error
list?.current?.scrollToOffset({offset: scrollAmount, animated: animateScroll});
}
}, [calendarSize]);
}, [calendarSize, shouldUseAndroidRTLFix, pastScrollRange, animateScroll]);

const addMonth = useCallback((count: number) => {
const day = currentMonth?.clone().addMonths(count, true);
Expand Down Expand Up @@ -274,24 +277,32 @@ const CalendarList = (props: CalendarListProps & ContextProp, ref: any) => {

const onViewableItemsChanged = useCallback(({viewableItems}: any) => {
const newVisibleMonth = parseDate(viewableItems[0]?.item);
if (!sameDate(visibleMonth?.current, newVisibleMonth)) {
visibleMonth.current = newVisibleMonth;
if (shouldUseAndroidRTLFix) {
const centerIndex = items.findIndex((item) => isEqual(parseDate(current), item));
const adjustedOffset = centerIndex - items.findIndex((item) => isEqual(newVisibleMonth, item));
visibleMonth.current = items[centerIndex + adjustedOffset];
setCurrentMonth(visibleMonth.current);
} else {
if (!sameDate(visibleMonth?.current, newVisibleMonth)) {
visibleMonth.current = newVisibleMonth;
setCurrentMonth(visibleMonth.current);
}
}
}, []);
}, [items, shouldUseAndroidRTLFix, current]);

const viewabilityConfigCallbackPairs = useRef([
{
viewabilityConfig: viewabilityConfig.current,
onViewableItemsChanged
},
]);

return (
<View style={style.current.flatListContainer} testID={testID}>
<FlatList
// @ts-expect-error
ref={list}
windowSize={shouldUseAndroidRTLFix ? pastScrollRange + futureScrollRange + 1 : undefined}
style={listStyle}
showsVerticalScrollIndicator={showScrollIndicator}
showsHorizontalScrollIndicator={showScrollIndicator}
Expand Down
4 changes: 3 additions & 1 deletion src/commons/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ const isAndroid = Platform.OS === 'android';
const isIOS = Platform.OS === 'ios';
const screenAspectRatio = screenWidth < screenHeight ? screenHeight / screenWidth : screenWidth / screenHeight;
const isTablet = (Platform as PlatformIOSStatic).isPad || (screenAspectRatio < 1.6 && Math.max(screenWidth, screenHeight) >= 900);
const isAndroidRTL = isAndroid && isRTL;

export default {
screenWidth,
screenHeight,
isRTL,
isAndroid,
isIOS,
isTablet
isTablet,
isAndroidRTL
};
54 changes: 54 additions & 0 deletions src/expandableCalendar/AgendaListsCommon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import isEqual from 'lodash/isEqual';
import React from 'react';
import {DefaultSectionT, SectionListProps, Text, TextProps, ViewStyle} from 'react-native';
import {Theme} from '../types';

export interface AgendaListProps extends SectionListProps<any, DefaultSectionT> {
/** Specify theme properties to override specific styles for calendar parts */
theme?: Theme;
/** day format in section title. Formatting values: http://arshaw.com/xdate/#Formatting */
dayFormat?: string;
/** a function to custom format the section header's title */
dayFormatter?: (arg0: string) => string;
/** whether to use moment.js for date string formatting
* (remember to pass 'dayFormat' with appropriate format, like 'dddd, MMM D') */
useMoment?: boolean;
/** whether to mark today's title with the "Today, ..." string. Default = true */
markToday?: boolean;
/** style passed to the section view */
sectionStyle?: ViewStyle;
/** whether to block the date change in calendar (and calendar context provider) when agenda scrolls */
avoidDateUpdates?: boolean;
/** offset scroll to section */
viewOffset?: number;
/** enable scrolling the agenda list to the next date with content when pressing a day without content */
scrollToNextEvent?: boolean;
/**
* @experimental
* If defined, uses InfiniteList instead of SectionList. This feature is experimental and subject to change.
*/
infiniteListProps?: {
itemHeight?: number;
titleHeight?: number;
visibleIndicesChangedDebounce?: number;
renderFooter?: () => React.ReactElement | null;
};
}

interface AgendaSectionHeaderProps {
title?: string;
onLayout?: TextProps['onLayout'];
style: TextProps['style'];
}

function areTextPropsEqual(prev: AgendaSectionHeaderProps, next: AgendaSectionHeaderProps): boolean {
return isEqual(prev.style, next.style) && prev.title === next.title;
}

export const AgendaSectionHeader = React.memo((props: AgendaSectionHeaderProps) => {
return (
<Text allowFontScaling={false} style={props.style} onLayout={props.onLayout}>
{props.title}
</Text>
);
}, areTextPropsEqual);
10 changes: 5 additions & 5 deletions src/expandableCalendar/WeekCalendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {useDidUpdate} from '../../hooks';

export const NUMBER_OF_PAGES = 6;
const NUM_OF_ITEMS = NUMBER_OF_PAGES * 2 + 1; // NUMBER_OF_PAGES before + NUMBER_OF_PAGES after + current
const APPLY_ANDROID_FIX = constants.isAndroid && constants.isRTL;

export interface WeekCalendarProps extends CalendarListProps {
/** whether to have shadow/elevation for the calendar */
Expand Down Expand Up @@ -70,14 +69,15 @@ const WeekCalendar = (props: WeekCalendarProps) => {
}) :
sameWeek(item, date, firstDay));
if (pageIndex !== currentIndex.current) {
const adjustedIndexFrScroll = constants.isAndroidRTL ? NUM_OF_ITEMS - 1 - pageIndex : pageIndex;
if (pageIndex >= 0) {
visibleWeek.current = items.current[pageIndex];
currentIndex.current = pageIndex;
visibleWeek.current = items.current[adjustedIndexFrScroll];
currentIndex.current = adjustedIndexFrScroll;
} else {
visibleWeek.current = date;
currentIndex.current = NUMBER_OF_PAGES;
}
pageIndex <= 0 ? onEndReached() : list?.current?.scrollToIndex({index: pageIndex, animated: false});
pageIndex <= 0 ? onEndReached() : list?.current?.scrollToIndex({index: adjustedIndexFrScroll, animated: false});
}
}
}, [date, updateSource]);
Expand Down Expand Up @@ -179,7 +179,7 @@ const WeekCalendar = (props: WeekCalendarProps) => {
const currItems = items.current;
const newDate = viewableItems[0]?.item;
if (newDate !== visibleWeek.current) {
if (APPLY_ANDROID_FIX) {
if (constants.isAndroidRTL) {
//in android RTL the item we see is the one in the opposite direction
const newDateOffset = -1 * (NUMBER_OF_PAGES - currItems.indexOf(newDate));
const adjustedNewDate = currItems[NUMBER_OF_PAGES - newDateOffset];
Expand Down
7 changes: 3 additions & 4 deletions src/expandableCalendar/__test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ describe('ExpandableCalendar', () => {
beforeEach(() => {
driver.render();
});
it.each([[Direction.LEFT], [Direction.RIGHT]])(`should call onDateChanged to next week first day when pressing %s arrow`, (direction) => {
const currentDay = today.getUTCDay();
it.each([['last', Direction.LEFT], ['next', Direction.RIGHT]])(`should call onDateChanged to %s week first day when pressing %s arrow`, (direction) => {
const currentDay = today.getDay();
const expectedDate = today.clone().addDays(direction === Direction.LEFT ? - (currentDay + 7) : (7 - currentDay));
driver.pressOnHeaderArrow({left: direction === Direction.LEFT});
expect(onDateChanged).toHaveBeenCalledWith(toMarkingFormat(expectedDate), UpdateSources.PAGE_SCROLL);
Expand All @@ -259,7 +259,7 @@ describe('ExpandableCalendar', () => {

it('should fetch next weeks when in last week of the list', () => {
times(NUMBER_OF_PAGES + 1, () => driver.pressOnHeaderArrow({left: false}));
const currentDay = today.getUTCDay();
const currentDay = today.getDay();
const expectedDate = today.clone().addDays(7 * (NUMBER_OF_PAGES + 1) - currentDay);
const day = driver.getWeekDay(toMarkingFormat(expectedDate));
expect(day).toBeDefined();
Expand All @@ -269,7 +269,6 @@ describe('ExpandableCalendar', () => {
const endOfMonth = new XDate(today.getFullYear(), today.getMonth() + 1, 0, 0, 0 ,0 , 0, true);
const diff = Math.ceil(((endOfMonth.getUTCDate() + 1) - today.getUTCDate()) / 7) + ((today.getUTCDay() > endOfMonth.getUTCDay()) ? 1 : 0);
const expectedDate = today.clone().setDate(today.getDate() + 7 * diff - today.getDay());
console.log(diff, endOfMonth, expectedDate);
times(diff, () => driver.pressOnHeaderArrow({left: false}));
expect(onMonthChange).toHaveBeenCalledWith(xdateToData(expectedDate), UpdateSources.PAGE_SCROLL);
});
Expand Down
57 changes: 1 addition & 56 deletions src/expandableCalendar/agendaList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,36 @@ import map from 'lodash/map';
import isFunction from 'lodash/isFunction';
import isUndefined from 'lodash/isUndefined';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';

import XDate from 'xdate';

import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react';
import {
Text,
SectionList,
SectionListProps,
DefaultSectionT,
SectionListData,
ViewStyle,
NativeSyntheticEvent,
NativeScrollEvent,
LayoutChangeEvent,
ViewToken,
TextProps
} from 'react-native';

import {useDidUpdate} from '../hooks';
import {getMoment} from '../momentResolver';
import {isToday, isGTE, sameDate} from '../dateutils';
import {parseDate} from '../interface';
import {getDefaultLocale} from '../services';
import {Theme} from '../types';
import {UpdateSources, todayString} from './commons';
import constants from '../commons/constants';
import styleConstructor from './style';
import Context from './Context';
import InfiniteAgendaList from './infiniteAgendaList';
import {AgendaListProps, AgendaSectionHeader} from './AgendaListsCommon';

const viewabilityConfig = {
itemVisiblePercentThreshold: 20 // 50 means if 50% of the item is visible
};

export interface AgendaListProps extends SectionListProps<any, DefaultSectionT> {
/** Specify theme properties to override specific styles for calendar parts */
theme?: Theme;
/** day format in section title. Formatting values: http://arshaw.com/xdate/#Formatting */
dayFormat?: string;
/** a function to custom format the section header's title */
dayFormatter?: (arg0: string) => string;
/** whether to use moment.js for date string formatting
* (remember to pass 'dayFormat' with appropriate format, like 'dddd, MMM D') */
useMoment?: boolean;
/** whether to mark today's title with the "Today, ..." string. Default = true */
markToday?: boolean;
/** style passed to the section view */
sectionStyle?: ViewStyle;
/** whether to block the date change in calendar (and calendar context provider) when agenda scrolls */
avoidDateUpdates?: boolean;
/** offset scroll to section */
viewOffset?: number;
/** enable scrolling the agenda list to the next date with content when pressing a day without content */
scrollToNextEvent?: boolean;
/**
* @experimental
* If defined, uses InfiniteList instead of SectionList. This feature is experimental and subject to change.
*/
infiniteListProps?: {
itemHeight?: number;
titleHeight?: number;
visibleIndicesChangedDebounce?: number;
renderFooter?: () => React.ReactElement | null;
};
}

/**
* @description: AgendaList component
* @note: Should be wrapped with 'CalendarProvider'
Expand Down Expand Up @@ -284,24 +247,6 @@ const AgendaList = (props: AgendaListProps) => {
// }
};

interface AgendaSectionHeaderProps {
title?: string;
onLayout?: TextProps['onLayout'];
style: TextProps['style'];
}

function areTextPropsEqual(prev: AgendaSectionHeaderProps, next: AgendaSectionHeaderProps): boolean {
return isEqual(prev.style, next.style) && prev.title === next.title;
}

export const AgendaSectionHeader = React.memo((props: AgendaSectionHeaderProps) => {
return (
<Text allowFontScaling={false} style={props.style} onLayout={props.onLayout}>
{props.title}
</Text>
);
}, areTextPropsEqual);

export default AgendaList;

AgendaList.displayName = 'AgendaList';
Expand Down
2 changes: 1 addition & 1 deletion src/expandableCalendar/infiniteAgendaList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Context from './Context';
import constants from "../commons/constants";
import {parseDate} from "../interface";
import {LayoutProvider} from "recyclerlistview/dist/reactnative/core/dependencies/LayoutProvider";
import {AgendaListProps, AgendaSectionHeader} from "./agendaList";
import {AgendaSectionHeader, AgendaListProps} from "./AgendaListsCommon";

/**
* @description: AgendaList component that use InfiniteList to improve performance
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type {ExpandableCalendarProps} from './expandableCalendar';
export {default as WeekCalendar} from './expandableCalendar/WeekCalendar/new';
export type {WeekCalendarProps} from './expandableCalendar/WeekCalendar';
export {default as AgendaList} from './expandableCalendar/agendaList';
export type {AgendaListProps} from './expandableCalendar/agendaList';
export type {AgendaListProps} from './expandableCalendar/AgendaListsCommon';
export {default as CalendarContext} from './expandableCalendar/Context';
export {default as CalendarProvider} from './expandableCalendar/Context/Provider';
export type {CalendarContextProviderProps} from './expandableCalendar/Context/Provider';
Expand Down
Loading

0 comments on commit 95418db

Please sign in to comment.