@tarojs/components 3.6.0-canary.6 → 3.6.0-canary.7

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.
@@ -1,654 +0,0 @@
1
- /* eslint-disable no-sequences */
2
- /* eslint-disable no-case-declarations */
3
- /* eslint-disable no-void */
4
- /* eslint-disable no-return-assign */
5
- import { createSelectorQuery } from '@tarojs/taro'
6
- import { createElement, PureComponent } from 'react'
7
-
8
- import { getRTLOffsetType } from '../domHelpers'
9
- import { memoizeOne } from '../memoize'
10
- import { cancelTimeout, requestTimeout } from '../timer'
11
-
12
- const IS_SCROLLING_DEBOUNCE_INTERVAL = 200
13
-
14
- const defaultItemKey = (index) => index // In DEV mode, this Set helps us only log a warning once per component instance.
15
- // This avoids spamming the console every time a render happens.
16
-
17
- let INSTANCE_ID = 0
18
-
19
- export function isHorizontalFunc ({ direction, layout }) {
20
- return direction === 'horizontal' || layout === 'horizontal'
21
- }
22
- export function isRtlFunc ({ direction }) {
23
- return direction === 'rtl'
24
- }
25
- export function getRectSize (id, success = () => {}, fail = () => {}) {
26
- const query = createSelectorQuery()
27
- query.select(id).boundingClientRect((res) => {
28
- if (res) {
29
- success(res)
30
- } else {
31
- fail()
32
- }
33
- }).exec()
34
- }
35
-
36
- export default function createListComponent ({
37
- getItemOffset,
38
- getEstimatedTotalSize,
39
- getItemSize,
40
- getOffsetForIndexAndAlignment,
41
- getStartIndexForOffset,
42
- getStopIndexForStartIndex,
43
- initInstanceProps,
44
- shouldResetStyleCacheOnItemSizeChange,
45
- validateProps
46
- }) {
47
- let _class, _temp
48
-
49
- return _temp = _class = class List extends PureComponent {
50
- // Always use explicit constructor for React components.
51
- // It produces less code after transpilation. (#26)
52
- // eslint-disable-next-line no-useless-constructor
53
- constructor (props) {
54
- super(props)
55
- this._instanceProps = initInstanceProps(this.props, this)
56
- this._outerRef = void 0
57
- this._resetIsScrollingTimeoutId = null
58
- this.state = {
59
- id: this.props.id || `virtual-list-${INSTANCE_ID++}`,
60
- instance: this,
61
- isScrolling: false,
62
- scrollDirection: 'forward',
63
- scrollOffset: typeof this.props.initialScrollOffset === 'number' ? this.props.initialScrollOffset : 0,
64
- scrollUpdateWasRequested: false,
65
- sizeList: []
66
- }
67
- if (this.props.unlimitedSize) {
68
- this.state.sizeList = new Array(this.props.itemCount).fill(-1)
69
- }
70
- this.field = {
71
- scrollLeft: 0,
72
- scrollTop: 0,
73
- scrollHeight: 0,
74
- scrollWidth: 0,
75
- clientHeight: 0,
76
- clientWidth: 0
77
- }
78
- this._callOnItemsRendered = void 0
79
- this._callOnItemsRendered = memoizeOne((overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex) => this.props.onItemsRendered({
80
- overscanStartIndex,
81
- overscanStopIndex,
82
- visibleStartIndex,
83
- visibleStopIndex
84
- }))
85
- this._callOnScroll = void 0
86
- this._callOnScroll = memoizeOne((scrollDirection, scrollOffset, scrollUpdateWasRequested, detail) => this.props.onScroll({
87
- scrollDirection,
88
- scrollOffset,
89
- scrollUpdateWasRequested,
90
- detail
91
- }))
92
-
93
- this._getSize = void 0
94
-
95
- this._getSize = (size) => {
96
- if (typeof size === 'number' && size >= 0) {
97
- return size
98
- }
99
- return this.props.itemSize
100
- }
101
-
102
- this._getSizeUploadSync = void 0
103
-
104
- this._getSizeUploadSync = (index, isHorizontal) => {
105
- const ID = `#${this.state.id}-${index}`
106
-
107
- return new Promise((resolve) => {
108
- const success = ({ width, height }) => {
109
- const { sizeList } = this.state
110
- const size = isHorizontal ? width : height
111
- if (size !== sizeList[index]) {
112
- sizeList[index] = this._getSize(size)
113
- this.setState({
114
- sizeList: [...sizeList]
115
- }, () => {
116
- resolve(this._getSize(size))
117
- })
118
- }
119
- }
120
- const fail = () => {
121
- const [startIndex, stopIndex] = this._getRangeToRender()
122
- if (index >= startIndex && index <= stopIndex) {
123
- setTimeout(() => {
124
- getRectSize(ID, success, fail)
125
- }, 100)
126
- }
127
- }
128
- getRectSize(ID, success, fail)
129
- })
130
- }
131
-
132
- this._getSizeUpload = (index, isHorizontal) => {
133
- this._getSizeUploadSync(index, isHorizontal)
134
- const { sizeList } = this.state
135
- return this._getSize(sizeList[index])
136
- }
137
-
138
- this._getCountSize = void 0
139
-
140
- this._getCountSize = (props, count) => {
141
- if (!props.unlimitedSize) {
142
- return props.itemSize * count
143
- }
144
- const { sizeList } = this.state
145
- const sizes = sizeList.slice(0, count)
146
- return sizes.reduce((p, a) => {
147
- return p + this._getSize(a)
148
- }, 0)
149
- }
150
-
151
- this._getSizeCount = void 0
152
-
153
- this._getSizeCount = (props, offset) => {
154
- if (offset === 0) {
155
- return 0
156
- }
157
- if (!props.unlimitedSize) {
158
- return Math.min(props.itemCount - 1, Math.floor(offset / props.itemSize))
159
- }
160
- let offsetSize = 0
161
- const { sizeList } = this.state
162
- const count = sizeList.reduce((p, a) => {
163
- a = this._getSize(a)
164
- if (offsetSize < offset) {
165
- offsetSize += a
166
- return ++p
167
- }
168
- return p
169
- }, 0)
170
- return count - 1
171
- }
172
-
173
- this._getStyleValue = value => {
174
- return typeof value === 'number'
175
- ? value + 'px'
176
- : value == null
177
- ? ''
178
- : value
179
- }
180
-
181
- this._getItemStyle = void 0
182
-
183
- this._getItemStyle = index => {
184
- const {
185
- direction,
186
- itemSize,
187
- layout
188
- } = this.props
189
-
190
- const itemStyleCache = this._getItemStyleCache(shouldResetStyleCacheOnItemSizeChange && itemSize, shouldResetStyleCacheOnItemSizeChange && layout, shouldResetStyleCacheOnItemSizeChange && direction)
191
-
192
- let style
193
-
194
- const offset = getItemOffset(this.props, index, this)
195
- const size = getItemSize(this.props, index, this) // TODO Deprecate direction "horizontal"
196
- const isHorizontal = isHorizontalFunc(this.props)
197
- const isRtl = isRtlFunc(this.props)
198
- if (itemStyleCache.hasOwnProperty(index)) {
199
- style = itemStyleCache[index]
200
- if (isHorizontal) {
201
- style.width = size
202
- if (isRtl) {
203
- style.right = offset
204
- } else {
205
- style.left = offset
206
- }
207
- } else {
208
- style.height = size
209
- style.top = offset
210
- }
211
- } else {
212
- const offsetHorizontal = isHorizontal ? offset : 0
213
- itemStyleCache[index] = style = {
214
- position: 'absolute',
215
- left: !isRtl ? offsetHorizontal : undefined,
216
- right: isRtl ? offsetHorizontal : undefined,
217
- top: !isHorizontal ? offset : 0,
218
- height: !isHorizontal ? size : '100%',
219
- width: isHorizontal ? size : '100%'
220
- }
221
- }
222
-
223
- for (const k in style) {
224
- if (style.hasOwnProperty(k)) {
225
- style[k] = this._getStyleValue(style[k])
226
- }
227
- }
228
-
229
- return style
230
- }
231
-
232
- this._getItemStyleCache = void 0
233
- this._getItemStyleCache = memoizeOne(() => ({}))
234
-
235
- this._onScrollHorizontal = event => {
236
- const {
237
- clientWidth,
238
- scrollTop,
239
- scrollLeft,
240
- scrollHeight,
241
- scrollWidth
242
- } = event.currentTarget
243
- this.field.scrollHeight = scrollHeight
244
- this.field.scrollWidth = getEstimatedTotalSize(this.props, this)
245
- this.field.scrollTop = scrollTop
246
- this.field.scrollLeft = scrollLeft
247
- this.field.clientHeight = scrollHeight
248
- this.field.clientWidth = clientWidth
249
- this.setState(prevState => {
250
- if (prevState.scrollOffset === scrollLeft) {
251
- // Scroll position may have been updated by cDM/cDU,
252
- // In which case we don't need to trigger another render,
253
- // And we don't want to update state.isScrolling.
254
- return null
255
- }
256
-
257
- let scrollOffset = scrollLeft
258
-
259
- if (isRtlFunc(this.props)) {
260
- // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
261
- // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
262
- // It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
263
- // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
264
- switch (getRTLOffsetType()) {
265
- case 'negative':
266
- scrollOffset = -scrollLeft
267
- break
268
-
269
- case 'positive-descending':
270
- scrollOffset = scrollWidth - clientWidth - scrollLeft
271
- break
272
- }
273
- } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
274
-
275
- scrollOffset = Math.max(0, Math.min(scrollOffset, scrollWidth - clientWidth))
276
- this.field.scrollWidth = scrollOffset
277
- return {
278
- isScrolling: true,
279
- scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward',
280
- scrollOffset,
281
- scrollUpdateWasRequested: false
282
- }
283
- }, this._resetIsScrollingDebounced)
284
- }
285
-
286
- this._onScrollVertical = event => {
287
- const {
288
- clientHeight,
289
- scrollHeight,
290
- scrollWidth,
291
- scrollTop,
292
- scrollLeft
293
- } = event.currentTarget
294
- this.setState(prevState => {
295
- const diffOffset = this.field.scrollTop - scrollTop
296
- if (prevState.scrollOffset === scrollTop || this.field.diffOffset === -diffOffset) {
297
- // Scroll position may have been updated by cDM/cDU,
298
- // In which case we don't need to trigger another render,
299
- // And we don't want to update state.isScrolling.
300
- return null
301
- } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
302
- const scrollOffset = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight))
303
- this.field.scrollHeight = getEstimatedTotalSize(this.props, this)
304
- this.field.scrollWidth = scrollWidth
305
- this.field.scrollTop = scrollOffset
306
- this.field.scrollLeft = scrollLeft
307
- this.field.clientHeight = clientHeight
308
- this.field.clientWidth = scrollWidth
309
- this.field.diffOffset = diffOffset
310
- return {
311
- isScrolling: true,
312
- scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
313
- scrollOffset,
314
- scrollUpdateWasRequested: false
315
- }
316
- }, this._resetIsScrollingDebounced)
317
- }
318
-
319
- this._outerRefSetter = ref => {
320
- const {
321
- outerRef
322
- } = this.props
323
- this._outerRef = ref
324
-
325
- if (typeof outerRef === 'function') {
326
- outerRef(ref)
327
- } else if (outerRef != null && typeof outerRef === 'object' && outerRef.hasOwnProperty('current')) {
328
- outerRef.current = ref
329
- }
330
- }
331
-
332
- this._resetIsScrollingDebounced = () => {
333
- if (this._resetIsScrollingTimeoutId !== null) {
334
- cancelTimeout(this._resetIsScrollingTimeoutId)
335
- }
336
-
337
- this._resetIsScrollingTimeoutId = requestTimeout(this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL)
338
- }
339
-
340
- this._resetIsScrolling = () => {
341
- this._resetIsScrollingTimeoutId = null
342
- this.setState({
343
- isScrolling: false
344
- }, () => {
345
- // Clear style cache after state update has been committed.
346
- // This way we don't break pure sCU for items that don't use isScrolling param.
347
- this._getItemStyleCache(-1, null)
348
- })
349
- }
350
- }
351
-
352
- static getDerivedStateFromProps (nextProps, prevState) {
353
- validateProps(nextProps, prevState)
354
- return null
355
- }
356
-
357
- scrollTo (scrollOffset) {
358
- scrollOffset = Math.max(0, scrollOffset)
359
- this.setState(prevState => {
360
- if (prevState.scrollOffset === scrollOffset) {
361
- return null
362
- }
363
-
364
- return {
365
- scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
366
- scrollOffset: scrollOffset,
367
- scrollUpdateWasRequested: true
368
- }
369
- }, this._resetIsScrollingDebounced)
370
- }
371
-
372
- scrollToItem (index, align = 'auto') {
373
- const {
374
- itemCount
375
- } = this.props
376
- const {
377
- scrollOffset
378
- } = this.state
379
- index = Math.max(0, Math.min(index, itemCount - 1))
380
- this.scrollTo(getOffsetForIndexAndAlignment(this.props, this.state.id, index, align, scrollOffset, this))
381
- }
382
-
383
- componentDidMount () {
384
- const { initialScrollOffset } = this.props
385
-
386
- if (typeof initialScrollOffset === 'number' && this._outerRef != null) {
387
- const outerRef = this._outerRef // TODO Deprecate direction "horizontal"
388
-
389
- if (isHorizontalFunc(this.props)) {
390
- outerRef.scrollLeft = initialScrollOffset
391
- } else {
392
- outerRef.scrollTop = initialScrollOffset
393
- }
394
- }
395
-
396
- this._callPropsCallbacks()
397
- }
398
-
399
- componentDidUpdate (prevProps, prevState) {
400
- const {
401
- scrollOffset,
402
- scrollUpdateWasRequested
403
- } = this.state
404
-
405
- if (scrollUpdateWasRequested && this._outerRef != null) {
406
- const outerRef = this._outerRef // TODO Deprecate direction "horizontal"
407
-
408
- if (isHorizontalFunc(this.props)) {
409
- if (isRtlFunc(this.props)) {
410
- // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
411
- // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
412
- // So we need to determine which browser behavior we're dealing with, and mimic it.
413
- switch (getRTLOffsetType()) {
414
- case 'negative':
415
- outerRef.scrollLeft = -scrollOffset
416
- break
417
-
418
- case 'positive-ascending':
419
- outerRef.scrollLeft = scrollOffset
420
- break
421
-
422
- default:
423
- const {
424
- clientWidth,
425
- scrollWidth
426
- } = outerRef
427
- outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset
428
- break
429
- }
430
- } else {
431
- outerRef.scrollLeft = scrollOffset
432
- }
433
- } else {
434
- outerRef.scrollTop = scrollOffset
435
- }
436
- }
437
-
438
- this._callPropsCallbacks(prevProps, prevState)
439
-
440
- setTimeout(() => {
441
- const [startIndex, stopIndex] = this._getRangeToRender()
442
- const isHorizontal = isHorizontalFunc(this.props)
443
- for (let index = startIndex; index <= stopIndex; index++) {
444
- this._getSizeUploadSync(index, isHorizontal)
445
- }
446
- }, 0)
447
- }
448
-
449
- componentWillUnmount () {
450
- if (this._resetIsScrollingTimeoutId !== null) {
451
- cancelTimeout(this._resetIsScrollingTimeoutId)
452
- }
453
- }
454
-
455
- render () {
456
- const {
457
- children,
458
- className,
459
- direction,
460
- height,
461
- innerRef,
462
- innerElementType,
463
- innerTagName,
464
- itemElementType,
465
- itemTagName,
466
- itemCount,
467
- itemData,
468
- itemKey = defaultItemKey,
469
- layout,
470
- outerElementType,
471
- outerTagName,
472
- style,
473
- useIsScrolling,
474
- width,
475
- position,
476
- renderTop,
477
- renderBottom,
478
- ...rest
479
- } = this.props
480
- const {
481
- id,
482
- isScrolling,
483
- scrollOffset,
484
- scrollUpdateWasRequested
485
- } = this.state // TODO Deprecate direction "horizontal"
486
-
487
- const isHorizontal = isHorizontalFunc(this.props)
488
- const onScroll = isHorizontal ? this._onScrollHorizontal : this._onScrollVertical
489
-
490
- const [startIndex, stopIndex] = this._getRangeToRender()
491
-
492
- const items = []
493
-
494
- if (itemCount > 0) {
495
- for (let index = startIndex; index <= stopIndex; index++) {
496
- const key = itemKey(index, itemData)
497
- let style
498
- if (position === 'relative') {
499
- const size = getItemSize(this.props, index, this)
500
- style = {
501
- height: this._getStyleValue(!isHorizontal ? size : '100%'),
502
- width: this._getStyleValue(isHorizontal ? size : '100%')
503
- }
504
- } else {
505
- style = this._getItemStyle(index)
506
- }
507
- items.push(createElement(itemElementType || itemTagName || 'div', {
508
- key, style
509
- }, createElement(children, {
510
- id: `${id}-${index}`,
511
- data: itemData,
512
- index,
513
- isScrolling: useIsScrolling ? isScrolling : undefined
514
- })))
515
- }
516
- }
517
- // Read this value AFTER items have been created,
518
- // So their actual sizes (if variable) are taken into consideration.
519
-
520
- const estimatedTotalSize = getEstimatedTotalSize(this.props, this)
521
- const outerElementProps = {
522
- ...rest,
523
- id,
524
- className,
525
- onScroll,
526
- ref: this._outerRefSetter,
527
- layout,
528
- style: {
529
- position: 'relative',
530
- height: this._getStyleValue(height),
531
- width: this._getStyleValue(width),
532
- overflow: 'auto',
533
- WebkitOverflowScrolling: 'touch',
534
- willChange: 'transform',
535
- direction,
536
- ...style
537
- }
538
- }
539
- if (scrollUpdateWasRequested) {
540
- if (isHorizontal) {
541
- outerElementProps.scrollLeft = scrollOffset
542
- } else {
543
- outerElementProps.scrollTop = scrollOffset
544
- }
545
- }
546
-
547
- if (position === 'relative') {
548
- const pre = getItemOffset(this.props, startIndex, this)
549
- return createElement(outerElementType || outerTagName || 'div', outerElementProps,
550
- renderTop,
551
- createElement(itemElementType || itemTagName || 'div', {
552
- key: `${id}-pre`,
553
- id: `${id}-pre`,
554
- style: {
555
- height: isHorizontal ? '100%' : this._getStyleValue(pre),
556
- width: !isHorizontal ? '100%' : this._getStyleValue(pre)
557
- }
558
- }),
559
- createElement(innerElementType || innerTagName || 'div', {
560
- ref: innerRef,
561
- key: `${id}-inner`,
562
- id: `${id}-inner`,
563
- style: {
564
- pointerEvents: isScrolling ? 'none' : 'auto'
565
- }
566
- }, items),
567
- renderBottom
568
- )
569
- } else {
570
- return createElement(outerElementType || outerTagName || 'div', outerElementProps,
571
- renderTop,
572
- createElement(innerElementType || innerTagName || 'div', {
573
- ref: innerRef,
574
- key: `${id}-inner`,
575
- id: `${id}-inner`,
576
- style: {
577
- height: this._getStyleValue(isHorizontal ? '100%' : estimatedTotalSize),
578
- pointerEvents: isScrolling ? 'none' : 'auto',
579
- width: this._getStyleValue(isHorizontal ? estimatedTotalSize : '100%')
580
- }
581
- }, items),
582
- renderBottom
583
- )
584
- }
585
- }
586
-
587
- _callPropsCallbacks (prevProps, prevState) {
588
- if (typeof this.props.onItemsRendered === 'function') {
589
- if (this.props.itemCount > 0) {
590
- if (!prevProps && prevProps.itemCount !== this.props.itemCount) {
591
- const [overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex] = this._getRangeToRender()
592
-
593
- this._callOnItemsRendered(overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex)
594
- }
595
- }
596
- }
597
-
598
- if (typeof this.props.onScroll === 'function') {
599
- if (!prevState ||
600
- prevState.scrollDirection !== this.state.scrollDirection ||
601
- prevState.scrollOffset !== this.state.scrollOffset ||
602
- prevState.scrollUpdateWasRequested !== this.state.scrollUpdateWasRequested
603
- ) {
604
- this._callOnScroll(
605
- this.state.scrollDirection,
606
- this.state.scrollOffset,
607
- this.state.scrollUpdateWasRequested,
608
- this.field
609
- )
610
- }
611
- }
612
- }
613
- // Lazily create and cache item styles while scrolling,
614
- // So that pure component sCU will prevent re-renders.
615
- // We maintain this cache, and pass a style prop rather than index,
616
- // So that List can clear cached styles and force item re-render if necessary.
617
-
618
- _getRangeToRender () {
619
- const {
620
- itemCount,
621
- overscanCount
622
- } = this.props
623
- const {
624
- isScrolling,
625
- scrollDirection,
626
- scrollOffset
627
- } = this.state
628
-
629
- if (itemCount === 0) {
630
- return [0, 0, 0, 0]
631
- }
632
-
633
- const startIndex = getStartIndexForOffset(this.props, scrollOffset, this)
634
- const stopIndex = getStopIndexForStartIndex(this.props, scrollOffset, startIndex, this) // Overscan by one item in each direction so that tab/focus works.
635
- // If there isn't at least one extra item, tab loops back around.
636
-
637
- const overscanBackward = !isScrolling || scrollDirection === 'backward' ? Math.max(1, overscanCount) : 1
638
- const overscanForward = !isScrolling || scrollDirection === 'forward' ? Math.max(1, overscanCount) : 1
639
- return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), startIndex, stopIndex]
640
- }
641
- }, _class.defaultProps = {
642
- direction: 'ltr',
643
- itemData: undefined,
644
- layout: 'vertical',
645
- overscanCount: 2,
646
- useIsScrolling: false
647
- }, _temp
648
- }
649
-
650
- // NOTE: I considered further wrapping individual items with a pure ListItem component.
651
- // This would avoid ever calling the render function for the same index more than once,
652
- // But it would also add the overhead of a lot of components/fibers.
653
- // I assume people already do this (render function returning a class component),
654
- // So my doing it would just unnecessarily double the wrappers.