1
1
import classNames from 'classnames'
2
2
import dayjs from 'dayjs'
3
- import isoWeek from 'dayjs/plugin/isoWeek'
4
3
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
4
+ import isoWeek from 'dayjs/plugin/isoWeek'
5
5
import React , {
6
6
forwardRef ,
7
- ReactNode ,
8
7
useContext ,
9
8
useEffect ,
10
9
useImperativeHandle ,
11
10
useMemo ,
12
11
useRef ,
13
12
useState ,
14
13
} from 'react'
14
+ import AutoSizer from 'react-virtualized-auto-sizer'
15
+ import { FixedSizeList as List } from 'react-window'
15
16
import { NativeProps , withNativeProps } from '../../utils/native-props'
16
17
import { usePropsValue } from '../../utils/use-props-value'
17
18
import { mergeProps } from '../../utils/with-default-props'
18
19
import { useConfig } from '../config-provider'
19
20
import {
20
- convertPageToDayjs ,
21
- convertValueToRange ,
22
21
DateRange ,
23
22
Page ,
23
+ convertPageToDayjs ,
24
+ convertValueToRange ,
24
25
} from './convert'
25
26
import useSyncScroll from './useSyncScroll'
26
27
@@ -82,6 +83,11 @@ const defaultProps = {
82
83
selectionMode : 'single' ,
83
84
}
84
85
86
+ type RowProps = {
87
+ index : number
88
+ style : React . CSSProperties
89
+ }
90
+
85
91
export const CalendarPickerView = forwardRef <
86
92
CalendarPickerViewRef ,
87
93
CalendarPickerViewProps
@@ -193,12 +199,28 @@ export const CalendarPickerView = forwardRef<
193
199
)
194
200
195
201
function renderBody ( ) {
196
- const cells : ReactNode [ ] = [ ]
197
- let monthIterator = minDay
198
- // 遍历月份
202
+ const totalMonths = Math . ceil ( maxDay . diff ( minDay , 'months' , true ) )
203
+ // default 每个月的高度是 344px
204
+ const monthHeight = 344
205
+ const cells : {
206
+ year : number
207
+ month : number
208
+ daysInMonth : number
209
+ monthIterator : dayjs . Dayjs
210
+ } [ ] = [ ]
211
+ let monthIterator = minDay . clone ( )
212
+
199
213
while ( monthIterator . isSameOrBefore ( maxDay , 'month' ) ) {
200
214
const year = monthIterator . year ( )
201
215
const month = monthIterator . month ( ) + 1
216
+ const daysInMonth = monthIterator . daysInMonth ( )
217
+
218
+ cells . push ( { year, month, daysInMonth, monthIterator } )
219
+ monthIterator = monthIterator . add ( 1 , 'month' )
220
+ }
221
+
222
+ const Row = ( { index, style } : RowProps ) => {
223
+ const { year, month, daysInMonth, monthIterator } = cells [ index ]
202
224
203
225
const renderMap = {
204
226
year,
@@ -212,17 +234,16 @@ export const CalendarPickerView = forwardRef<
212
234
props . weekStartsOn === 'Monday'
213
235
? monthIterator . date ( 1 ) . isoWeekday ( ) - 1
214
236
: monthIterator . date ( 1 ) . isoWeekday ( )
237
+
215
238
const presetEmptyCells =
216
239
presetEmptyCellCount == 7
217
240
? null
218
- : Array ( presetEmptyCellCount )
219
- . fill ( null )
220
- . map ( ( _ , index ) => (
221
- < div key = { index } className = { `${ classPrefix } -cell` } > </ div >
222
- ) )
223
-
224
- cells . push (
225
- < div key = { yearMonth } data-year-month = { yearMonth } >
241
+ : Array . from ( { length : presetEmptyCellCount } ) . map ( ( _ , index ) => (
242
+ < div key = { index } className = { `${ classPrefix } -cell` } > </ div >
243
+ ) )
244
+
245
+ return (
246
+ < div style = { style } key = { yearMonth } data-year-month = { yearMonth } >
226
247
< div className = { `${ classPrefix } -title` } >
227
248
{ locale . Calendar . yearAndMonth ?. replace (
228
249
/ \$ { ( .* ?) } / g,
@@ -235,135 +256,141 @@ export const CalendarPickerView = forwardRef<
235
256
{ /* 空格填充 */ }
236
257
{ presetEmptyCells }
237
258
{ /* 遍历每月 */ }
238
- { Array ( monthIterator . daysInMonth ( ) )
239
- . fill ( null )
240
- . map ( ( _ , index ) => {
241
- const d = monthIterator . date ( index + 1 )
242
- let isSelect = false
243
- let isBegin = false
244
- let isEnd = false
245
- let isSelectRowBegin = false
246
- let isSelectRowEnd = false
247
- if ( dateRange ) {
248
- const [ begin , end ] = dateRange
249
- isBegin = d . isSame ( begin , 'day' )
250
- isEnd = d . isSame ( end , 'day' )
251
- isSelect =
252
- isBegin ||
253
- isEnd ||
254
- ( d . isAfter ( begin , 'day' ) && d . isBefore ( end , 'day' ) )
255
- if ( isSelect ) {
256
- isSelectRowBegin =
257
- ( cells . length % 7 === 0 ||
258
- d . isSame ( d . startOf ( 'month' ) , 'day' ) ) &&
259
- ! isBegin
260
- isSelectRowEnd =
261
- ( cells . length % 7 === 6 ||
262
- d . isSame ( d . endOf ( 'month' ) , 'day' ) ) &&
263
- ! isEnd
264
- }
259
+ { Array . from ( { length : daysInMonth } ) . map ( ( _ , index ) => {
260
+ const d = monthIterator . date ( index + 1 )
261
+ let isSelect = false
262
+ let isBegin = false
263
+ let isEnd = false
264
+ let isSelectRowBegin = false
265
+ let isSelectRowEnd = false
266
+ if ( dateRange ) {
267
+ const [ begin , end ] = dateRange
268
+ isBegin = d . isSame ( begin , 'day' )
269
+ isEnd = d . isSame ( end , 'day' )
270
+ isSelect =
271
+ isBegin ||
272
+ isEnd ||
273
+ ( d . isAfter ( begin , 'day' ) && d . isBefore ( end , 'day' ) )
274
+ if ( isSelect ) {
275
+ isSelectRowBegin =
276
+ ( cells . length % 7 === 0 ||
277
+ d . isSame ( d . startOf ( 'month' ) , 'day' ) ) &&
278
+ ! isBegin
279
+ isSelectRowEnd =
280
+ ( cells . length % 7 === 6 ||
281
+ d . isSame ( d . endOf ( 'month' ) , 'day' ) ) &&
282
+ ! isEnd
265
283
}
266
- const disabled = props . shouldDisableDate
267
- ? props . shouldDisableDate ( d . toDate ( ) )
268
- : ( maxDay && d . isAfter ( maxDay , 'day' ) ) ||
269
- ( minDay && d . isBefore ( minDay , 'day' ) )
270
-
271
- const renderTop = ( ) => {
272
- const top = props . renderTop ?.( d . toDate ( ) )
284
+ }
285
+ const disabled = props . shouldDisableDate
286
+ ? props . shouldDisableDate ( d . toDate ( ) )
287
+ : ( maxDay && d . isAfter ( maxDay , 'day' ) ) ||
288
+ ( minDay && d . isBefore ( minDay , 'day' ) )
273
289
274
- if ( top ) {
275
- return top
276
- }
290
+ const renderTop = ( ) => {
291
+ const top = props . renderTop ?.( d . toDate ( ) )
277
292
278
- if ( props . selectionMode === 'range' ) {
279
- if ( isBegin ) {
280
- return locale . Calendar . start
281
- }
293
+ if ( top ) {
294
+ return top
295
+ }
282
296
283
- if ( isEnd ) {
284
- return locale . Calendar . end
285
- }
297
+ if ( props . selectionMode === 'range' ) {
298
+ if ( isBegin ) {
299
+ return locale . Calendar . start
286
300
}
287
301
288
- if ( d . isSame ( today , 'day' ) && ! isSelect ) {
289
- return locale . Calendar . today
302
+ if ( isEnd ) {
303
+ return locale . Calendar . end
290
304
}
291
305
}
292
- return (
293
- < div
294
- key = { d . valueOf ( ) }
295
- className = { classNames ( `${ classPrefix } -cell` , {
296
- [ `${ classPrefix } -cell-today` ] : d . isSame ( today , 'day' ) ,
297
- [ `${ classPrefix } -cell-selected` ] : isSelect ,
298
- [ `${ classPrefix } -cell-selected-begin` ] : isBegin ,
299
- [ `${ classPrefix } -cell-selected-end` ] : isEnd ,
300
- [ `${ classPrefix } -cell-selected-row-begin` ] :
301
- isSelectRowBegin ,
302
- [ `${ classPrefix } -cell-selected-row-end` ] : isSelectRowEnd ,
303
- [ `${ classPrefix } -cell-disabled` ] : ! ! disabled ,
304
- } ) }
305
- onClick = { ( ) => {
306
- if ( ! props . selectionMode ) return
307
- if ( disabled ) return
308
- const date = d . toDate ( )
309
- function shouldClear ( ) {
310
- if ( ! props . allowClear ) return false
311
- if ( ! dateRange ) return false
312
- const [ begin , end ] = dateRange
313
- return d . isSame ( begin , 'date' ) && d . isSame ( end , 'day' )
306
+
307
+ if ( d . isSame ( today , 'day' ) && ! isSelect ) {
308
+ return locale . Calendar . today
309
+ }
310
+ }
311
+ return (
312
+ < div
313
+ key = { d . valueOf ( ) }
314
+ className = { classNames ( `${ classPrefix } -cell` , {
315
+ [ `${ classPrefix } -cell-today` ] : d . isSame ( today , 'day' ) ,
316
+ [ `${ classPrefix } -cell-selected` ] : isSelect ,
317
+ [ `${ classPrefix } -cell-selected-begin` ] : isBegin ,
318
+ [ `${ classPrefix } -cell-selected-end` ] : isEnd ,
319
+ [ `${ classPrefix } -cell-selected-row-begin` ] :
320
+ isSelectRowBegin ,
321
+ [ `${ classPrefix } -cell-selected-row-end` ] : isSelectRowEnd ,
322
+ [ `${ classPrefix } -cell-disabled` ] : ! ! disabled ,
323
+ } ) }
324
+ onClick = { ( ) => {
325
+ if ( ! props . selectionMode ) return
326
+ if ( disabled ) return
327
+ const date = d . toDate ( )
328
+ function shouldClear ( ) {
329
+ if ( ! props . allowClear ) return false
330
+ if ( ! dateRange ) return false
331
+ const [ begin , end ] = dateRange
332
+ return d . isSame ( begin , 'date' ) && d . isSame ( end , 'day' )
333
+ }
334
+ if ( props . selectionMode === 'single' ) {
335
+ if ( props . allowClear && shouldClear ( ) ) {
336
+ onDateChange ( null )
337
+ return
338
+ }
339
+ onDateChange ( [ date , date ] )
340
+ } else if ( props . selectionMode === 'range' ) {
341
+ if ( ! dateRange ) {
342
+ onDateChange ( [ date , date ] )
343
+ setIntermediate ( true )
344
+ return
345
+ }
346
+ if ( shouldClear ( ) ) {
347
+ onDateChange ( null )
348
+ setIntermediate ( false )
349
+ return
314
350
}
315
- if ( props . selectionMode === 'single' ) {
316
- if ( props . allowClear && shouldClear ( ) ) {
317
- onDateChange ( null )
318
- return
319
- }
351
+ if ( intermediate ) {
352
+ const another = dateRange [ 0 ]
353
+ onDateChange (
354
+ another > date ? [ date , another ] : [ another , date ]
355
+ )
356
+ setIntermediate ( false )
357
+ } else {
320
358
onDateChange ( [ date , date ] )
321
- } else if ( props . selectionMode === 'range' ) {
322
- if ( ! dateRange ) {
323
- onDateChange ( [ date , date ] )
324
- setIntermediate ( true )
325
- return
326
- }
327
- if ( shouldClear ( ) ) {
328
- onDateChange ( null )
329
- setIntermediate ( false )
330
- return
331
- }
332
- if ( intermediate ) {
333
- const another = dateRange [ 0 ]
334
- onDateChange (
335
- another > date ? [ date , another ] : [ another , date ]
336
- )
337
- setIntermediate ( false )
338
- } else {
339
- onDateChange ( [ date , date ] )
340
- setIntermediate ( true )
341
- }
359
+ setIntermediate ( true )
342
360
}
343
- } }
344
- >
345
- < div className = { `${ classPrefix } -cell-top` } >
346
- { renderTop ( ) }
347
- </ div >
348
- < div className = { `${ classPrefix } -cell-date` } >
349
- { props . renderDate
350
- ? props . renderDate ( d . toDate ( ) )
351
- : d . date ( ) }
352
- </ div >
353
- < div className = { `${ classPrefix } -cell-bottom` } >
354
- { props . renderBottom ?.( d . toDate ( ) ) }
355
- </ div >
361
+ }
362
+ } }
363
+ >
364
+ < div className = { `${ classPrefix } -cell-top` } > { renderTop ( ) } </ div >
365
+ < div className = { `${ classPrefix } -cell-date` } >
366
+ { props . renderDate ? props . renderDate ( d . toDate ( ) ) : d . date ( ) }
367
+ </ div >
368
+ < div className = { `${ classPrefix } -cell-bottom` } >
369
+ { props . renderBottom ?.( d . toDate ( ) ) }
356
370
</ div >
357
- )
358
- } ) }
371
+ </ div >
372
+ )
373
+ } ) }
359
374
</ div >
360
375
</ div >
361
376
)
362
-
363
- monthIterator = monthIterator . add ( 1 , 'month' )
364
377
}
365
378
366
- return cells
379
+ return (
380
+ < AutoSizer >
381
+ { ( { height, width } ) => (
382
+ < List
383
+ height = { height }
384
+ itemCount = { totalMonths }
385
+ itemSize = { monthHeight }
386
+ width = { width }
387
+ className = { `${ classPrefix } -no-scrollbar` }
388
+ >
389
+ { Row }
390
+ </ List >
391
+ ) }
392
+ </ AutoSizer >
393
+ )
367
394
}
368
395
const body = (
369
396
< div className = { `${ classPrefix } -body` } ref = { bodyRef } >
0 commit comments