react-window 1.8.2 → 1.8.6

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.
@@ -5,10 +5,10 @@ import createListComponent from './createListComponent';
5
5
  import type { Props, ScrollToAlign } from './createListComponent';
6
6
 
7
7
  const FixedSizeList = createListComponent({
8
- getItemOffset: ({ itemSize, size }: Props<any>, index: number): number =>
8
+ getItemOffset: ({ itemSize }: Props<any>, index: number): number =>
9
9
  index * ((itemSize: any): number),
10
10
 
11
- getItemSize: ({ itemSize, size }: Props<any>, index: number): number =>
11
+ getItemSize: ({ itemSize }: Props<any>, index: number): number =>
12
12
  ((itemSize: any): number),
13
13
 
14
14
  getEstimatedTotalSize: ({ itemCount, itemSize }: Props<any>) =>
@@ -23,12 +23,13 @@ const FixedSizeList = createListComponent({
23
23
  // TODO Deprecate direction "horizontal"
24
24
  const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
25
25
  const size = (((isHorizontal ? width : height): any): number);
26
- const maxOffset = Math.max(
26
+ const lastItemOffset = Math.max(
27
27
  0,
28
- Math.min(
29
- itemCount * ((itemSize: any): number) - size,
30
- index * ((itemSize: any): number)
31
- )
28
+ itemCount * ((itemSize: any): number) - size
29
+ );
30
+ const maxOffset = Math.min(
31
+ lastItemOffset,
32
+ index * ((itemSize: any): number)
32
33
  );
33
34
  const minOffset = Math.max(
34
35
  0,
@@ -51,13 +52,25 @@ const FixedSizeList = createListComponent({
51
52
  return maxOffset;
52
53
  case 'end':
53
54
  return minOffset;
54
- case 'center':
55
- return Math.round(minOffset + (maxOffset - minOffset) / 2);
55
+ case 'center': {
56
+ // "Centered" offset is usually the average of the min and max.
57
+ // But near the edges of the list, this doesn't hold true.
58
+ const middleOffset = Math.round(
59
+ minOffset + (maxOffset - minOffset) / 2
60
+ );
61
+ if (middleOffset < Math.ceil(size / 2)) {
62
+ return 0; // near the beginning
63
+ } else if (middleOffset > lastItemOffset + Math.floor(size / 2)) {
64
+ return lastItemOffset; // near the end
65
+ } else {
66
+ return middleOffset;
67
+ }
68
+ }
56
69
  case 'auto':
57
70
  default:
58
71
  if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
59
72
  return scrollOffset;
60
- } else if (scrollOffset - minOffset < maxOffset - scrollOffset) {
73
+ } else if (scrollOffset < minOffset) {
61
74
  return minOffset;
62
75
  } else {
63
76
  return maxOffset;
@@ -83,14 +96,14 @@ const FixedSizeList = createListComponent({
83
96
  const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
84
97
  const offset = startIndex * ((itemSize: any): number);
85
98
  const size = (((isHorizontal ? width : height): any): number);
99
+ const numVisibleItems = Math.ceil(
100
+ (size + scrollOffset - offset) / ((itemSize: any): number)
101
+ );
86
102
  return Math.max(
87
103
  0,
88
104
  Math.min(
89
105
  itemCount - 1,
90
- startIndex +
91
- Math.floor(
92
- (size + (scrollOffset - offset)) / ((itemSize: any): number)
93
- )
106
+ startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive
94
107
  )
95
108
  );
96
109
  },
@@ -274,7 +274,11 @@ const getOffsetForIndexAndAlignment = (
274
274
  default:
275
275
  if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
276
276
  return scrollOffset;
277
- } else if (scrollOffset - minOffset < maxOffset - scrollOffset) {
277
+ } else if (minOffset > maxOffset) {
278
+ // Because we only take into account the scrollbar size when calculating minOffset
279
+ // this value can be larger than maxOffset when at the end of the list
280
+ return minOffset;
281
+ } else if (scrollOffset < minOffset) {
278
282
  return minOffset;
279
283
  } else {
280
284
  return maxOffset;
@@ -227,7 +227,7 @@ const VariableSizeList = createListComponent({
227
227
  default:
228
228
  if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
229
229
  return scrollOffset;
230
- } else if (scrollOffset - minOffset < maxOffset - scrollOffset) {
230
+ } else if (scrollOffset < minOffset) {
231
231
  return minOffset;
232
232
  } else {
233
233
  return maxOffset;
@@ -3,7 +3,7 @@
3
3
  import memoizeOne from 'memoize-one';
4
4
  import { createElement, PureComponent } from 'react';
5
5
  import { cancelTimeout, requestTimeout } from './timer';
6
- import { getScrollbarSize, isRTLOffsetNegative } from './domHelpers';
6
+ import { getScrollbarSize, getRTLOffsetType } from './domHelpers';
7
7
 
8
8
  import type { TimeoutID } from './timer';
9
9
 
@@ -46,6 +46,22 @@ type OnScrollCallback = ({
46
46
  type ScrollEvent = SyntheticEvent<HTMLDivElement>;
47
47
  type ItemStyleCache = { [key: string]: Object };
48
48
 
49
+ type OuterProps = {|
50
+ children: React$Node,
51
+ className: string | void,
52
+ onScroll: ScrollEvent => void,
53
+ style: {
54
+ [string]: mixed,
55
+ },
56
+ |};
57
+
58
+ type InnerProps = {|
59
+ children: React$Node,
60
+ style: {
61
+ [string]: mixed,
62
+ },
63
+ |};
64
+
49
65
  export type Props<T> = {|
50
66
  children: RenderComponent<T>,
51
67
  className?: string,
@@ -56,7 +72,7 @@ export type Props<T> = {|
56
72
  initialScrollLeft?: number,
57
73
  initialScrollTop?: number,
58
74
  innerRef?: any,
59
- innerElementType?: React$ElementType,
75
+ innerElementType?: string | React$AbstractComponent<InnerProps, any>,
60
76
  innerTagName?: string, // deprecated
61
77
  itemData: T,
62
78
  itemKey?: (params: {|
@@ -67,7 +83,7 @@ export type Props<T> = {|
67
83
  onItemsRendered?: OnItemsRenderedCallback,
68
84
  onScroll?: OnScrollCallback,
69
85
  outerRef?: any,
70
- outerElementType?: React$ElementType,
86
+ outerElementType?: string | React$AbstractComponent<OuterProps, any>,
71
87
  outerTagName?: string, // deprecated
72
88
  overscanColumnCount?: number,
73
89
  overscanColumnsCount?: number, // deprecated
@@ -348,12 +364,17 @@ export default function createGridComponent({
348
364
  // So we need to determine which browser behavior we're dealing with, and mimic it.
349
365
  const outerRef = ((this._outerRef: any): HTMLElement);
350
366
  if (direction === 'rtl') {
351
- const isNegative = isRTLOffsetNegative();
352
- if (isNegative) {
353
- outerRef.scrollLeft = -scrollLeft;
354
- } else {
355
- const { clientWidth, scrollWidth } = outerRef;
356
- outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft;
367
+ switch (getRTLOffsetType()) {
368
+ case 'negative':
369
+ outerRef.scrollLeft = -scrollLeft;
370
+ break;
371
+ case 'positive-ascending':
372
+ outerRef.scrollLeft = scrollLeft;
373
+ break;
374
+ default:
375
+ const { clientWidth, scrollWidth } = outerRef;
376
+ outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft;
377
+ break;
357
378
  }
358
379
  } else {
359
380
  outerRef.scrollLeft = Math.max(0, scrollLeft);
@@ -589,13 +610,16 @@ export default function createGridComponent({
589
610
  if (itemStyleCache.hasOwnProperty(key)) {
590
611
  style = itemStyleCache[key];
591
612
  } else {
613
+ const offset = getColumnOffset(
614
+ this.props,
615
+ columnIndex,
616
+ this._instanceProps
617
+ );
618
+ const isRtl = direction === 'rtl';
592
619
  itemStyleCache[key] = style = {
593
620
  position: 'absolute',
594
- [direction === 'rtl' ? 'right' : 'left']: getColumnOffset(
595
- this.props,
596
- columnIndex,
597
- this._instanceProps
598
- ),
621
+ left: isRtl ? undefined : offset,
622
+ right: isRtl ? offset : undefined,
599
623
  top: getRowOffset(this.props, rowIndex, this._instanceProps),
600
624
  height: getRowHeight(this.props, rowIndex, this._instanceProps),
601
625
  width: getColumnWidth(this.props, columnIndex, this._instanceProps),
@@ -726,18 +750,19 @@ export default function createGridComponent({
726
750
 
727
751
  const { direction } = this.props;
728
752
 
753
+ // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
754
+ // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
755
+ // It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
756
+ // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
729
757
  let calculatedScrollLeft = scrollLeft;
730
758
  if (direction === 'rtl') {
731
- const isNegative = isRTLOffsetNegative();
732
-
733
- // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
734
- // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
735
- // It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
736
- // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
737
- if (isNegative) {
738
- calculatedScrollLeft = -scrollLeft;
739
- } else {
740
- calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
759
+ switch (getRTLOffsetType()) {
760
+ case 'negative':
761
+ calculatedScrollLeft = -scrollLeft;
762
+ break;
763
+ case 'positive-descending':
764
+ calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft;
765
+ break;
741
766
  }
742
767
  }
743
768
 
@@ -3,7 +3,7 @@
3
3
  import memoizeOne from 'memoize-one';
4
4
  import { createElement, PureComponent } from 'react';
5
5
  import { cancelTimeout, requestTimeout } from './timer';
6
- import { isRTLOffsetNegative } from './domHelpers';
6
+ import { getRTLOffsetType } from './domHelpers';
7
7
 
8
8
  import type { TimeoutID } from './timer';
9
9
 
@@ -39,6 +39,22 @@ type onScrollCallback = ({
39
39
  type ScrollEvent = SyntheticEvent<HTMLDivElement>;
40
40
  type ItemStyleCache = { [index: number]: Object };
41
41
 
42
+ type OuterProps = {|
43
+ children: React$Node,
44
+ className: string | void,
45
+ onScroll: ScrollEvent => void,
46
+ style: {
47
+ [string]: mixed,
48
+ },
49
+ |};
50
+
51
+ type InnerProps = {|
52
+ children: React$Node,
53
+ style: {
54
+ [string]: mixed,
55
+ },
56
+ |};
57
+
42
58
  export type Props<T> = {|
43
59
  children: RenderComponent<T>,
44
60
  className?: string,
@@ -46,7 +62,7 @@ export type Props<T> = {|
46
62
  height: number | string,
47
63
  initialScrollOffset?: number,
48
64
  innerRef?: any,
49
- innerElementType?: React$ElementType,
65
+ innerElementType?: string | React$AbstractComponent<InnerProps, any>,
50
66
  innerTagName?: string, // deprecated
51
67
  itemCount: number,
52
68
  itemData: T,
@@ -56,7 +72,7 @@ export type Props<T> = {|
56
72
  onItemsRendered?: onItemsRenderedCallback,
57
73
  onScroll?: onScrollCallback,
58
74
  outerRef?: any,
59
- outerElementType?: React$ElementType,
75
+ outerElementType?: string | React$AbstractComponent<OuterProps, any>,
60
76
  outerTagName?: string, // deprecated
61
77
  overscanCount: number,
62
78
  style?: Object,
@@ -235,18 +251,24 @@ export default function createListComponent({
235
251
 
236
252
  if (scrollUpdateWasRequested && this._outerRef != null) {
237
253
  const outerRef = ((this._outerRef: any): HTMLElement);
254
+
238
255
  // TODO Deprecate direction "horizontal"
239
256
  if (direction === 'horizontal' || layout === 'horizontal') {
240
257
  if (direction === 'rtl') {
241
258
  // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
242
259
  // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
243
260
  // So we need to determine which browser behavior we're dealing with, and mimic it.
244
- const isNegative = isRTLOffsetNegative();
245
- if (isNegative) {
246
- outerRef.scrollLeft = -scrollOffset;
247
- } else {
248
- const { clientWidth, scrollWidth } = outerRef;
249
- outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset;
261
+ switch (getRTLOffsetType()) {
262
+ case 'negative':
263
+ outerRef.scrollLeft = -scrollOffset;
264
+ break;
265
+ case 'positive-ascending':
266
+ outerRef.scrollLeft = scrollOffset;
267
+ break;
268
+ default:
269
+ const { clientWidth, scrollWidth } = outerRef;
270
+ outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset;
271
+ break;
250
272
  }
251
273
  } else {
252
274
  outerRef.scrollLeft = scrollOffset;
@@ -444,9 +466,12 @@ export default function createListComponent({
444
466
  const isHorizontal =
445
467
  direction === 'horizontal' || layout === 'horizontal';
446
468
 
469
+ const isRtl = direction === 'rtl';
470
+ const offsetHorizontal = isHorizontal ? offset : 0;
447
471
  itemStyleCache[index] = style = {
448
472
  position: 'absolute',
449
- [direction === 'rtl' ? 'right' : 'left']: isHorizontal ? offset : 0,
473
+ left: isRtl ? undefined : offsetHorizontal,
474
+ right: isRtl ? offsetHorizontal : undefined,
450
475
  top: !isHorizontal ? offset : 0,
451
476
  height: !isHorizontal ? size : '100%',
452
477
  width: isHorizontal ? size : '100%',
@@ -512,16 +537,17 @@ export default function createListComponent({
512
537
 
513
538
  let scrollOffset = scrollLeft;
514
539
  if (direction === 'rtl') {
515
- const isNegative = isRTLOffsetNegative();
516
-
517
540
  // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
518
541
  // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
519
542
  // It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
520
543
  // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
521
- if (isNegative) {
522
- scrollOffset = -scrollLeft;
523
- } else {
524
- scrollOffset = scrollWidth - clientWidth - scrollLeft;
544
+ switch (getRTLOffsetType()) {
545
+ case 'negative':
546
+ scrollOffset = -scrollLeft;
547
+ break;
548
+ case 'positive-descending':
549
+ scrollOffset = scrollWidth - clientWidth - scrollLeft;
550
+ break;
525
551
  }
526
552
  }
527
553
 
package/src/domHelpers.js CHANGED
@@ -21,7 +21,12 @@ export function getScrollbarSize(recalculate?: boolean = false): number {
21
21
  return size;
22
22
  }
23
23
 
24
- let cachedRTLResult: boolean | null = null;
24
+ export type RTLOffsetType =
25
+ | 'negative'
26
+ | 'positive-descending'
27
+ | 'positive-ascending';
28
+
29
+ let cachedRTLResult: RTLOffsetType | null = null;
25
30
 
26
31
  // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
27
32
  // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
@@ -29,7 +34,7 @@ let cachedRTLResult: boolean | null = null;
29
34
  // The safest way to check this is to intentionally set a negative offset,
30
35
  // and then verify that the subsequent "scroll" event matches the negative offset.
31
36
  // If it does not match, then we can assume a non-standard RTL scroll implementation.
32
- export function isRTLOffsetNegative(recalculate?: boolean = false): boolean {
37
+ export function getRTLOffsetType(recalculate?: boolean = false): RTLOffsetType {
33
38
  if (cachedRTLResult === null || recalculate) {
34
39
  const outerDiv = document.createElement('div');
35
40
  const outerStyle = outerDiv.style;
@@ -47,8 +52,16 @@ export function isRTLOffsetNegative(recalculate?: boolean = false): boolean {
47
52
 
48
53
  ((document.body: any): HTMLBodyElement).appendChild(outerDiv);
49
54
 
50
- outerDiv.scrollLeft = -10;
51
- cachedRTLResult = outerDiv.scrollLeft === -10;
55
+ if (outerDiv.scrollLeft > 0) {
56
+ cachedRTLResult = 'positive-descending';
57
+ } else {
58
+ outerDiv.scrollLeft = 1;
59
+ if (outerDiv.scrollLeft === 0) {
60
+ cachedRTLResult = 'negative';
61
+ } else {
62
+ cachedRTLResult = 'positive-ascending';
63
+ }
64
+ }
52
65
 
53
66
  ((document.body: any): HTMLBodyElement).removeChild(outerDiv);
54
67