@momo-kits/calendar 0.121.0-rc.3 → 0.121.0-rc.4

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/MonthList.tsx CHANGED
@@ -1,282 +1,219 @@
1
- import React, {Component} from 'react';
1
+ import {scaleSize, Spacing} from '@momo-kits/foundation';
2
+ import moment, {Moment} from 'moment';
3
+ import React, {
4
+ forwardRef,
5
+ useImperativeHandle,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
2
13
  import {FlatList, Platform} from 'react-native';
3
- import moment from 'moment';
4
- import Moment from 'moment';
5
- import Month from './Month';
6
- import LunarDateConverter from './LunarDateConverter';
7
- import {Holidays, MonthListProps, MonthListState} from './types';
8
14
  import {ContainerContext} from './index';
9
- import {scaleSize, Spacing} from '@momo-kits/foundation';
15
+ import LunarDateConverter from './LunarDateConverter';
16
+ import Month from './Month';
17
+ import {MonthListProps, MonthListRef} from './types';
10
18
 
11
19
  const MAX_RENDER_PER_BATCH = Platform.OS === 'android' ? 1 : 12;
12
20
 
13
21
  // @ts-ignore
14
22
  const converter = new LunarDateConverter();
15
- export default class MonthList extends Component<
16
- MonthListProps,
17
- MonthListState
18
- > {
19
- currentDate;
20
- data;
21
- currentScrollIndex;
22
- list;
23
- heightStyle;
24
- holidays?: Holidays[] = [];
25
- currentKey: any;
26
-
27
- constructor(props: MonthListProps) {
28
- super(props);
29
- this.currentDate = moment();
30
- this.data = this.getMonthList();
31
- this.currentScrollIndex = this.getIndexOfMonth(
32
- moment(props.selectedDate),
33
- this.data,
34
- );
35
- this.list = React.createRef<FlatList>();
36
- this.heightStyle = {};
37
- this.holidays = props.holidays;
38
- }
39
-
40
- renderMonth = (data: {
41
- item: {dateList: any; date: moment.Moment};
42
- index: number;
43
- }) => {
44
- const {isDoubleDateMode, disabledDays, priceList, isShowLunar} = this.props;
45
- const {item, index} = data;
46
-
47
- const keyMonth = moment(item.date)?.format('YYYY-MM');
48
- const priceListDate = priceList?.[keyMonth];
49
-
50
- return (
51
- <Month
52
- {...this.props}
53
- key={index}
54
- month={item.date || {}}
55
- dateList={item.dateList || []}
56
- priceListDate={priceListDate}
57
- isShowLunar={isShowLunar}
58
- isDoubleDateMode={isDoubleDateMode}
59
- disabledDays={disabledDays}
60
- />
61
- );
62
- };
63
-
64
- checkRange = (
65
- date?: moment.Moment | null,
66
- start?: moment.Moment | null,
67
- end?: moment.Moment | null,
68
- ) => {
69
- if (!date || !start) return false;
70
- if (!end) {
71
- return date.year() === start.year() && date.month() === start.month();
72
- }
73
- if (
74
- date.year() < start.year() ||
75
- (date.year() === start.year() && date.month() < start.month())
76
- ) {
77
- return false;
78
- }
79
- return !(
80
- date.year() > end.year() ||
81
- (date.year() === end.year() && date.month() > end.month())
82
- );
83
- };
84
-
85
- shouldUpdate = (
86
- month: {date: any; dateList?: any[]},
87
- props?: MonthListProps,
88
- ) => {
89
- if (!props) return false;
90
- const {startDate, endDate} = props;
91
- const {startDate: curStartDate, endDate: curEndDate} = this.props;
92
- const {date} = month;
93
- const next = this.checkRange(date, startDate, endDate);
94
- const prev = this.checkRange(date, curStartDate, curEndDate);
95
- return prev || next;
96
- };
97
-
98
- convertLunarToSolar(date: moment.Moment) {
99
- return date
100
- ? converter.SolarToLunar({
101
- solarDay: date.date(),
102
- solarMonth: date.month() + 1,
103
- solarYear: date.year(),
104
- })
105
- : {};
106
- }
107
-
108
- findHoliday = (date: Moment.Moment) => {
109
- const {holidays} = this.props;
110
- if (date && holidays && holidays.length > 0) {
111
- const day = date.date();
112
- const month = date.month() + 1;
113
- return holidays.find(item => item.day === day && item.month === month);
114
- }
115
- return null;
116
- };
117
-
118
- checkHoliday = (date: moment.Moment) => {
119
- const holiday = this.findHoliday(date);
120
- return {
121
- isSolarHoliday: !!(holiday && !holiday.lunar),
122
- isLunarHoliday: !!(holiday && holiday.lunar),
123
- };
124
- };
125
23
 
126
- getDayList = (date: moment.Moment) => {
127
- let dayList;
128
- const month = date.month();
129
- let weekday = date.isoWeekday();
130
- if (weekday === 1) {
131
- dayList = [];
132
- } else {
133
- dayList = new Array(weekday - 1).fill({
134
- empty: moment(date).subtract(1, 'h'),
135
- lunarDate: this.convertLunarToSolar(date),
136
- ...this.checkHoliday(date),
137
- });
138
- }
139
- while (date.month() === month) {
140
- const cloned = moment(date);
141
- dayList.push({
142
- date: cloned,
143
- lunarDate: this.convertLunarToSolar(cloned),
144
- ...this.checkHoliday(date),
145
- });
146
- date.add(1, 'days');
147
- }
148
- date.subtract(1, 'days');
149
- weekday = date.isoWeekday();
150
- if (weekday === 1) {
151
- return dayList.concat(
152
- new Array(6).fill({
153
- empty: moment(date).hour(1),
154
- lunarDate: this.convertLunarToSolar(date),
155
- }),
156
- );
157
- }
158
- return dayList.concat(
159
- new Array(Math.abs(7 - weekday)).fill({
160
- empty: moment(date).hour(1),
161
- lunarDate: this.convertLunarToSolar(date),
162
- }),
163
- );
164
- };
165
-
166
- getMonthList = (props?: MonthListProps) => {
167
- const minDate = moment((props || this.props).minDate).date(1);
168
- const maxDate = moment((props || this.props).maxDate);
169
- const monthList: {date: moment.Moment; dateList: any[]}[] = [];
170
- if (!maxDate || !minDate) return monthList;
24
+ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
25
+ const {
26
+ selectedDate,
27
+ minDate,
28
+ maxDate,
29
+ startDate,
30
+ endDate,
31
+ holidays = [],
32
+ priceList,
33
+ isShowLunar,
34
+ isDoubleDateMode,
35
+ disabledDays,
36
+ onScrollCalendar,
37
+ ...restProps
38
+ } = props;
39
+
40
+ const size = useContext(ContainerContext);
41
+ const listRef = useRef<FlatList>(null);
42
+ const currentKey = useRef<string | null>(null);
43
+ const [heightStyle, setHeightStyle] = useState({});
44
+
45
+ const data = useMemo(() => {
46
+ const months: {date: Moment; dateList: any[]}[] = [];
47
+ const mMin = moment(minDate).date(1);
48
+ const mMax = moment(maxDate);
171
49
  while (
172
- maxDate > minDate ||
173
- (maxDate.year() === minDate.year() && maxDate.month() === minDate.month())
50
+ mMax > mMin ||
51
+ (mMax.year() === mMin.year() && mMax.month() === mMin.month())
174
52
  ) {
175
- const d = moment(minDate);
176
- const month = {
177
- date: d,
178
- dateList: this.getDayList(d),
179
- shouldUpdate: false,
180
- };
181
- month.shouldUpdate = this.shouldUpdate(month, props);
182
- monthList.push(month);
183
- minDate.add(1, 'month');
53
+ const d = moment(mMin);
54
+ const dayList: any[] = [];
55
+ const monthIdx = d.month();
56
+ let cursor = d.clone();
57
+ let weekday = cursor.isoWeekday();
58
+ if (weekday !== 1) {
59
+ dayList.push(
60
+ ...new Array(weekday - 1).fill(0).map(() => ({ isEmpty: true }))
61
+ );
62
+ }
63
+ while (cursor.month() === monthIdx) {
64
+ const cloned = cursor.clone();
65
+ const hol = holidays.find(
66
+ h => h.day === cloned.date() && h.month === cloned.month() + 1
67
+ );
68
+ dayList.push({
69
+ isEmpty: false,
70
+ date: cloned,
71
+ lunarDate: converter.SolarToLunar({
72
+ solarDay: cloned.date(),
73
+ solarMonth: cloned.month() + 1,
74
+ solarYear: cloned.year(),
75
+ }),
76
+ isSolarHoliday: !!(hol && !hol.lunar),
77
+ isLunarHoliday: !!(hol && hol.lunar),
78
+ });
79
+ cursor.add(1, 'days');
80
+ }
81
+ cursor.subtract(1, 'days');
82
+ weekday = cursor.isoWeekday();
83
+ if (weekday !== 7) {
84
+ dayList.push(
85
+ ...new Array(7 - weekday).fill(0).map(() => ({ isEmpty: true }))
86
+ );
87
+ }
88
+ months.push({date: d, dateList: dayList});
89
+ mMin.add(1, 'month');
184
90
  }
91
+ return months;
92
+ }, [minDate, maxDate, holidays]);
93
+
94
+ const getIndexOfMonth = useCallback(
95
+ (date: Moment) => data.findIndex(item => item.date.isSame(date, 'month')),
96
+ [data]
97
+ );
98
+
99
+ const currentScrollIndex = useMemo(
100
+ () => getIndexOfMonth(moment(selectedDate)),
101
+ [selectedDate, getIndexOfMonth]
102
+ );
103
+
104
+ const scrollToMonth = useCallback(
105
+ (month: Moment) => {
106
+ const idx = getIndexOfMonth(month);
107
+ if (listRef.current && idx !== -1) {
108
+ listRef.current.scrollToIndex({index: idx, animated: true});
109
+ }
110
+ },
111
+ [getIndexOfMonth]
112
+ );
185
113
 
186
- return monthList;
187
- };
114
+ useImperativeHandle(ref, () => ({scrollToMonth}));
188
115
 
189
- getIndexOfMonth = (month: moment.Moment, data: any[]) => {
190
- return data.findIndex(item => item.date.isSame(month, 'month'));
191
- };
116
+ useEffect(() => {
117
+ const timer = setTimeout(() => {
118
+ scrollToMonth(selectedDate);
119
+ }, 500);
120
+ return () => clearTimeout(timer);
121
+ }, []);
192
122
 
193
- getCurrentVisibleMonth = (info: {changed: any}) => {
194
- const {changed} = info;
195
- const {onScrollCalendar} = this.props;
196
- if (changed && changed.length > 0) {
123
+ const onViewableItemsChanged = useRef(({changed}: {changed: any[]}) => {
124
+ if (changed.length > 0) {
197
125
  const {item, key, index} = changed[0];
198
- if (onScrollCalendar && item && this.currentKey !== key) {
199
- this.currentKey = key;
126
+ if (onScrollCalendar && item && currentKey.current !== key) {
127
+ currentKey.current = key;
200
128
  try {
201
- this.heightStyle =
202
- this.data && this.data[index] && this.data[index].dateList
203
- ? {
204
- height:
205
- (scaleSize(48) * this.data[index].dateList.length) / 7 +
206
- Spacing.L * 2 +
207
- Spacing.XS,
208
- }
209
- : {};
210
- } catch (e) {
211
- this.heightStyle = {};
212
- }
213
- if (onScrollCalendar) {
214
- onScrollCalendar({
215
- date: item.date,
216
- key,
217
- currentIndex: index,
129
+ setHeightStyle({
130
+ height:
131
+ (scaleSize(48) * item.dateList.length) / 7 +
132
+ Spacing.L * 2 +
133
+ Spacing.XS,
218
134
  });
135
+ } catch {
136
+ setHeightStyle({});
219
137
  }
138
+ onScrollCalendar({date: item.date, key, currentIndex: index});
220
139
  }
221
140
  }
222
- };
223
-
224
- keyExtractor = (item: {date: Moment.Moment}) =>
225
- `${item.date.month() + 1}/${item.date.year()}`;
226
-
227
- scrollToMonth = (month: moment.Moment) => {
228
- const index = this.getIndexOfMonth(month, this.data);
229
- if (this.list.current && index !== -1) {
230
- this.list.current.scrollToIndex({
231
- index,
232
- animated: true,
233
- });
234
- }
235
- };
236
-
237
- getItemLayout = (data: any, index: number, width: number) => ({
238
- length: width,
239
- offset: width * index,
240
- index,
241
- });
242
-
243
- componentDidMount() {
244
- setTimeout(() => {
245
- this.scrollToMonth(this.currentDate);
246
- }, 500);
247
- }
248
-
249
- render() {
250
- const {priceList} = this.props;
251
- return (
252
- <ContainerContext.Consumer>
253
- {size => (
254
- <FlatList
255
- extraData={priceList}
256
- style={this.heightStyle}
257
- pagingEnabled
258
- horizontal
259
- ref={this.list}
260
- data={this.data}
261
- renderItem={this.renderMonth}
262
- showsHorizontalScrollIndicator={false}
263
- keyExtractor={this.keyExtractor}
264
- onViewableItemsChanged={this.getCurrentVisibleMonth}
265
- onScrollToIndexFailed={() => {}}
266
- scrollEnabled
267
- initialNumToRender={3}
268
- maxToRenderPerBatch={MAX_RENDER_PER_BATCH}
269
- windowSize={1}
270
- initialScrollIndex={this.currentScrollIndex}
271
- getItemLayout={(data, index) =>
272
- this.getItemLayout(data, index, size.width)
273
- }
274
- viewabilityConfig={{
275
- itemVisiblePercentThreshold: 50,
276
- }}
277
- />
278
- )}
279
- </ContainerContext.Consumer>
280
- );
281
- }
282
- }
141
+ }).current;
142
+
143
+ const viewabilityConfig = {itemVisiblePercentThreshold: 50};
144
+
145
+ const keyExtractor = useCallback(
146
+ (item: {date: Moment}) => `${item.date.month() + 1}/${item.date.year()}`,
147
+ []
148
+ );
149
+
150
+ const getItemLayout = useCallback(
151
+ (_: any, index: number) => ({
152
+ length: size.width,
153
+ offset: size.width * index,
154
+ index,
155
+ }),
156
+ [size.width]
157
+ );
158
+
159
+ const renderItem = useCallback(
160
+ ({item, index}: {item: {date: Moment; dateList: any[]}; index: number}) => {
161
+ const keyMonth = moment(item.date).format('YYYY-MM');
162
+ const priceListDate = priceList?.[keyMonth];
163
+ return (
164
+ <Month
165
+ {...restProps}
166
+ key={index}
167
+ month={item.date}
168
+ dateList={item.dateList}
169
+ priceListDate={priceListDate}
170
+ holidays={holidays}
171
+ minDate={minDate}
172
+ maxDate={maxDate}
173
+ startDate={startDate}
174
+ endDate={endDate}
175
+ isShowLunar={isShowLunar}
176
+ isDoubleDateMode={isDoubleDateMode}
177
+ disabledDays={disabledDays}
178
+ />
179
+ );
180
+ },
181
+ [
182
+ restProps,
183
+ priceList,
184
+ holidays,
185
+ minDate,
186
+ maxDate,
187
+ startDate,
188
+ endDate,
189
+ isShowLunar,
190
+ isDoubleDateMode,
191
+ disabledDays,
192
+ ]
193
+ );
194
+
195
+ return (
196
+ <FlatList
197
+ extraData={priceList}
198
+ style={heightStyle}
199
+ pagingEnabled
200
+ horizontal
201
+ ref={listRef}
202
+ data={data}
203
+ renderItem={renderItem}
204
+ showsHorizontalScrollIndicator={false}
205
+ keyExtractor={keyExtractor}
206
+ onScrollToIndexFailed={() => {}}
207
+ scrollEnabled
208
+ initialNumToRender={3}
209
+ maxToRenderPerBatch={MAX_RENDER_PER_BATCH}
210
+ windowSize={1}
211
+ onViewableItemsChanged={onViewableItemsChanged}
212
+ initialScrollIndex={currentScrollIndex}
213
+ getItemLayout={getItemLayout}
214
+ viewabilityConfig={viewabilityConfig}
215
+ />
216
+ );
217
+ });
218
+
219
+ export default MonthList;
package/TabHeader.tsx CHANGED
@@ -17,7 +17,7 @@ export default class TabHeader extends React.Component<
17
17
  > {
18
18
  static contextType = ApplicationContext;
19
19
  label;
20
- defaultDate: Moment.Moment;
20
+ defaultDate: Moment.Moment | null;
21
21
 
22
22
  constructor(props: TabHeaderProps) {
23
23
  super(props);
@@ -25,7 +25,7 @@ export default class TabHeader extends React.Component<
25
25
  active: props.activeTab,
26
26
  };
27
27
  this.label = props.label;
28
- this.defaultDate = props.date ? Moment(props.date) : Moment();
28
+ this.defaultDate = props.date ? Moment(props.date) : null;
29
29
  }
30
30
 
31
31
  onChangeTab = () => {
@@ -35,7 +35,7 @@ export default class TabHeader extends React.Component<
35
35
 
36
36
  updateView = (date?: Moment.Moment | Date | null, activeTab?: boolean) => {
37
37
  this.setState({
38
- date: date ? Moment(date) : Moment(),
38
+ date: date ? Moment(date) : undefined,
39
39
  active: activeTab,
40
40
  });
41
41
  };
package/Util.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import moment from 'moment';
2
2
  import LunarDateConverter from './LunarDateConverter';
3
3
  import holiday from './holidayData';
4
+ import {Holidays} from './types';
4
5
 
5
6
  const I18N_MAP = {
6
7
  en: {
@@ -59,8 +60,10 @@ const formatYYYYMMDD = (dd: number, mm: number, yyyy: any) =>
59
60
  const formatDDMM = (dd: number, mm: number) =>
60
61
  `${dd < 10 ? `0${dd}` : dd}/${mm < 10 ? `0${mm}` : mm}`;
61
62
 
62
- const groupHolidaysByDate = (holidays: any[]) => {
63
- const groupedHolidays: {[key: string]: string[]} = {};
63
+ const groupHolidaysByDate = (
64
+ holidays: Holidays[]
65
+ ): {[key: string]: Holidays} => {
66
+ const groupedHolidays: {[key: string]: Holidays} = {};
64
67
  if (holidays && holidays.length > 0) {
65
68
  holidays.forEach(item => {
66
69
  const {day, month, lunar, label} = item;
@@ -90,17 +93,18 @@ const groupHolidaysByDate = (holidays: any[]) => {
90
93
  return groupedHolidays;
91
94
  };
92
95
 
93
- const sortByDate = (arr: any[]) => {
96
+ /**
97
+ * Sort and group holidays by date, return sorted array of unique Holidays.
98
+ */
99
+ const sortByDate = (arr: Holidays[]): Holidays[] => {
94
100
  if (arr && arr.length > 1) {
95
- arr.sort((a, b) => {
96
- if (a.month > b.month || (a.month === b.month && a.day > b.day)) {
97
- return 1;
98
- }
99
- return -1;
100
- });
101
+ arr.sort((a, b) =>
102
+ a.month !== b.month ? a.month - b.month : a.day - b.day
103
+ );
101
104
  }
102
-
103
- return groupHolidaysByDate(arr);
105
+ // group by date string and return unique values in sorted order
106
+ const grouped = groupHolidaysByDate(arr);
107
+ return Object.values(grouped);
104
108
  };
105
109
 
106
110
  const Utils = {
@@ -183,9 +187,11 @@ const Utils = {
183
187
  return lastDayOfMonth.getDate();
184
188
  },
185
189
 
186
- getHolidaysInMonth(headerInfo: moment.Moment) {
190
+ /**
191
+ * Get sorted list of holidays within the given month.
192
+ */
193
+ getHolidaysInMonth(headerInfo: moment.Moment): Holidays[] {
187
194
  if (headerInfo) {
188
- const today = moment();
189
195
  // @ts-ignore
190
196
  const converter = new LunarDateConverter();
191
197
  const startDate = moment(headerInfo).startOf('month');
@@ -209,10 +215,10 @@ const Utils = {
209
215
  month: date.month - 1,
210
216
  date: date.day,
211
217
  });
212
- if (dateAsMoment.isSameOrAfter(today, 'date')) {
218
+ if (dateAsMoment.isBetween(startDate, endDate, 'day', '[]')) {
213
219
  holidays.push(date);
214
220
  }
215
- },
221
+ }
216
222
  );
217
223
  }
218
224
 
@@ -229,22 +235,13 @@ const Utils = {
229
235
  lunarYear: currentYear[1],
230
236
  });
231
237
  const solar1AsMoment = moment(
232
- formatYYYYMMDD(
233
- solar1.solarDay,
234
- solar1.solarMonth,
235
- solar1.solarYear,
236
- ),
238
+ formatYYYYMMDD(solar1.solarDay, solar1.solarMonth, solar1.solarYear)
237
239
  );
238
240
  const solar2AsMoment = moment(
239
- formatYYYYMMDD(
240
- solar2.solarDay,
241
- solar2.solarMonth,
242
- solar2.solarYear,
243
- ),
241
+ formatYYYYMMDD(solar2.solarDay, solar2.solarMonth, solar2.solarYear)
244
242
  );
245
243
  if (
246
- solar1AsMoment.isBetween(startDate, endDate) &&
247
- solar1AsMoment.isSameOrAfter(today, 'date')
244
+ solar1AsMoment.isBetween(startDate, endDate, 'day', '[]')
248
245
  ) {
249
246
  holidays.push({
250
247
  ...item,
@@ -252,8 +249,7 @@ const Utils = {
252
249
  month: solar1.solarMonth,
253
250
  });
254
251
  } else if (
255
- solar2AsMoment.isBetween(startDate, endDate) &&
256
- solar2AsMoment.isSameOrAfter(today, 'date')
252
+ solar2AsMoment.isBetween(startDate, endDate, 'day', '[]')
257
253
  ) {
258
254
  holidays.push({
259
255
  ...item,
@@ -268,11 +264,10 @@ const Utils = {
268
264
  lunarYear: currentYear,
269
265
  });
270
266
  const solarAsMoment = moment(
271
- formatYYYYMMDD(solar.solarDay, solar.solarMonth, solar.solarYear),
267
+ formatYYYYMMDD(solar.solarDay, solar.solarMonth, solar.solarYear)
272
268
  );
273
269
  if (
274
- solarAsMoment.isBetween(startDate, endDate) &&
275
- solarAsMoment.isSameOrAfter(today, 'date')
270
+ solarAsMoment.isBetween(startDate, endDate, 'day', '[]')
276
271
  ) {
277
272
  holidays.push({
278
273
  ...item,