@shopify/flash-list 1.0.4 → 1.2.1

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +3 -2
  3. package/RNFlashList.podspec +2 -2
  4. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutShadow.kt +5 -0
  5. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt +78 -7
  6. package/dist/FlashList.d.ts +4 -2
  7. package/dist/FlashList.d.ts.map +1 -1
  8. package/dist/FlashList.js +54 -28
  9. package/dist/FlashList.js.map +1 -1
  10. package/dist/FlashListProps.d.ts +15 -1
  11. package/dist/FlashListProps.d.ts.map +1 -1
  12. package/dist/FlashListProps.js +6 -0
  13. package/dist/FlashListProps.js.map +1 -1
  14. package/dist/__tests__/FlashList.test.js +84 -2
  15. package/dist/__tests__/FlashList.test.js.map +1 -1
  16. package/dist/__tests__/PlatformHelper.web.test.js +9 -1
  17. package/dist/__tests__/PlatformHelper.web.test.js.map +1 -1
  18. package/dist/__tests__/helpers/mountFlashList.d.ts +1 -2
  19. package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -1
  20. package/dist/__tests__/helpers/mountFlashList.js.map +1 -1
  21. package/dist/benchmark/AutoScrollHelper.d.ts.map +1 -1
  22. package/dist/benchmark/AutoScrollHelper.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.android.d.ts +4 -0
  28. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.android.d.ts.map +1 -0
  29. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.android.js +6 -0
  30. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.android.js.map +1 -0
  31. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts +2 -1
  32. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -1
  33. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.ios.d.ts +4 -0
  34. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.ios.d.ts.map +1 -0
  35. package/dist/native/auto-layout/{AutoLayoutViewNativeComponent.web.js → AutoLayoutViewNativeComponent.ios.js} +2 -2
  36. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.ios.js.map +1 -0
  37. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js +1 -1
  38. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js.map +1 -1
  39. package/dist/native/cell-container/CellContainer.android.d.ts +6 -0
  40. package/dist/native/cell-container/CellContainer.android.d.ts.map +1 -0
  41. package/dist/native/cell-container/CellContainer.android.js +9 -0
  42. package/dist/native/cell-container/CellContainer.android.js.map +1 -0
  43. package/dist/native/cell-container/CellContainer.d.ts +6 -4
  44. package/dist/native/cell-container/CellContainer.d.ts.map +1 -1
  45. package/dist/native/cell-container/CellContainer.ios.d.ts +6 -0
  46. package/dist/native/cell-container/CellContainer.ios.d.ts.map +1 -0
  47. package/dist/native/cell-container/CellContainer.ios.js +9 -0
  48. package/dist/native/cell-container/CellContainer.ios.js.map +1 -0
  49. package/dist/native/cell-container/CellContainer.js +6 -4
  50. package/dist/native/cell-container/CellContainer.js.map +1 -1
  51. package/dist/native/config/PlatformHelper.android.d.ts +16 -0
  52. package/dist/native/config/PlatformHelper.android.d.ts.map +1 -0
  53. package/dist/native/config/PlatformHelper.android.js +20 -0
  54. package/dist/native/config/PlatformHelper.android.js.map +1 -0
  55. package/dist/{utils → native/config}/PlatformHelper.d.ts +3 -1
  56. package/dist/native/config/PlatformHelper.d.ts.map +1 -0
  57. package/dist/{utils/PlatformHelper.web.d.ts → native/config/PlatformHelper.ios.d.ts} +4 -2
  58. package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -0
  59. package/dist/{utils/PlatformHelper.js → native/config/PlatformHelper.ios.js} +6 -2
  60. package/dist/native/config/PlatformHelper.ios.js.map +1 -0
  61. package/dist/native/config/PlatformHelper.js +21 -0
  62. package/dist/native/config/PlatformHelper.js.map +1 -0
  63. package/dist/native/config/PlatformHelper.web.d.ts +17 -0
  64. package/dist/native/config/PlatformHelper.web.d.ts.map +1 -0
  65. package/dist/{utils → native/config}/PlatformHelper.web.js +7 -2
  66. package/dist/native/config/PlatformHelper.web.js.map +1 -0
  67. package/dist/tsconfig.tsbuildinfo +1 -1
  68. package/dist/utils/AverageWindow.d.ts.map +1 -1
  69. package/dist/utils/AverageWindow.js.map +1 -1
  70. package/dist/viewability/ViewabilityHelper.d.ts.map +1 -1
  71. package/dist/viewability/ViewabilityHelper.js.map +1 -1
  72. package/ios/Sources/AutoLayoutView.swift +61 -8
  73. package/package.json +8 -2
  74. package/src/FlashList.tsx +82 -23
  75. package/src/FlashListProps.ts +24 -1
  76. package/src/__tests__/FlashList.test.tsx +92 -3
  77. package/src/__tests__/PlatformHelper.web.test.ts +17 -1
  78. package/src/__tests__/helpers/mountFlashList.tsx +2 -2
  79. package/src/benchmark/AutoScrollHelper.ts +1 -1
  80. package/src/index.ts +2 -0
  81. package/src/native/auto-layout/{AutoLayoutViewNativeComponent.web.ts → AutoLayoutViewNativeComponent.android.ts} +2 -3
  82. package/src/native/auto-layout/AutoLayoutViewNativeComponent.ios.ts +7 -0
  83. package/src/native/auto-layout/AutoLayoutViewNativeComponent.ts +2 -2
  84. package/src/native/cell-container/{CellContainer.ts → CellContainer.android.ts} +0 -0
  85. package/src/native/cell-container/CellContainer.ios.ts +7 -0
  86. package/src/native/cell-container/CellContainer.tsx +14 -0
  87. package/src/{utils/PlatformHelper.ts → native/config/PlatformHelper.android.ts} +12 -2
  88. package/src/native/config/PlatformHelper.ios.ts +26 -0
  89. package/src/native/config/PlatformHelper.ts +27 -0
  90. package/src/{utils → native/config}/PlatformHelper.web.ts +15 -3
  91. package/src/utils/AverageWindow.ts +1 -1
  92. package/src/viewability/ViewabilityHelper.ts +1 -1
  93. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts +0 -5
  94. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts.map +0 -1
  95. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js.map +0 -1
  96. package/dist/utils/PlatformHelper.d.ts.map +0 -1
  97. package/dist/utils/PlatformHelper.js.map +0 -1
  98. package/dist/utils/PlatformHelper.web.d.ts.map +0 -1
  99. package/dist/utils/PlatformHelper.web.js.map +0 -1
package/src/FlashList.tsx CHANGED
@@ -25,12 +25,18 @@ import CustomError from "./errors/CustomError";
25
25
  import ExceptionList from "./errors/ExceptionList";
26
26
  import WarningList from "./errors/Warnings";
27
27
  import ViewabilityManager from "./viewability/ViewabilityManager";
28
- import { FlashListProps, ContentStyle } from "./FlashListProps";
28
+ import {
29
+ FlashListProps,
30
+ ContentStyle,
31
+ RenderTarget,
32
+ RenderTargetOptions,
33
+ } from "./FlashListProps";
29
34
  import {
30
35
  getCellContainerPlatformStyles,
36
+ getFooterContainer,
31
37
  getItemAnimator,
32
38
  PlatformConfig,
33
- } from "./utils/PlatformHelper";
39
+ } from "./native/config/PlatformHelper";
34
40
 
35
41
  interface StickyProps extends StickyContainerProps {
36
42
  children: any;
@@ -59,6 +65,7 @@ class FlashList<T> extends React.PureComponent<
59
65
  private stickyContentContainerRef?: PureComponentWrapper;
60
66
  private listFixedDimensionSize = 0;
61
67
  private transformStyle = { transform: [{ scaleY: -1 }] };
68
+ private transformStyleHorizontal = { transform: [{ scaleX: -1 }] };
62
69
  private distanceFromWindow = 0;
63
70
  private contentStyle: ContentStyle = {};
64
71
  private loadStartTime = 0;
@@ -73,7 +80,6 @@ class FlashList<T> extends React.PureComponent<
73
80
  applyToInitialOffset: true,
74
81
  };
75
82
 
76
- private emptyObject = {};
77
83
  private postLoadTimeoutId?: ReturnType<typeof setTimeout>;
78
84
  private sizeWarningTimeoutId?: ReturnType<typeof setTimeout>;
79
85
 
@@ -192,7 +198,7 @@ class FlashList<T> extends React.PureComponent<
192
198
  // Using only grid layout provider as it can also act as a listview, sizeProvider is a function to support future overrides
193
199
  private static getLayoutProvider<T>(
194
200
  numColumns: number,
195
- props: FlashListProps<T>
201
+ flashListProps: FlashListProps<T>
196
202
  ) {
197
203
  return new GridLayoutProviderWithProps<T>(
198
204
  // max span or, total columns
@@ -228,7 +234,7 @@ class FlashList<T> extends React.PureComponent<
228
234
  );
229
235
  return mutableLayout?.size;
230
236
  },
231
- props
237
+ flashListProps
232
238
  );
233
239
  }
234
240
 
@@ -276,6 +282,7 @@ class FlashList<T> extends React.PureComponent<
276
282
  initialScrollIndex,
277
283
  style,
278
284
  contentContainerStyle,
285
+ renderScrollComponent,
279
286
  ...restProps
280
287
  } = this.props;
281
288
 
@@ -291,12 +298,12 @@ class FlashList<T> extends React.PureComponent<
291
298
 
292
299
  return (
293
300
  <StickyHeaderContainer
294
- overrideRowRenderer={this.stickyRowRenderer}
301
+ overrideRowRenderer={this.stickyOverrideRowRenderer}
295
302
  applyWindowCorrection={this.applyWindowCorrection}
296
303
  stickyHeaderIndices={stickyHeaderIndices}
297
304
  style={
298
305
  this.props.horizontal
299
- ? this.emptyObject
306
+ ? { ...this.getTransform() }
300
307
  : { flex: 1, ...this.getTransform() }
301
308
  }
302
309
  >
@@ -350,6 +357,9 @@ class FlashList<T> extends React.PureComponent<
350
357
  windowCorrectionConfig={this.getUpdatedWindowCorrectionConfig()}
351
358
  itemAnimator={this.itemAnimator}
352
359
  suppressBoundedSizeException
360
+ externalScrollView={
361
+ renderScrollComponent as RecyclerListViewProps["externalScrollView"]
362
+ }
353
363
  />
354
364
  </StickyHeaderContainer>
355
365
  );
@@ -486,7 +496,10 @@ class FlashList<T> extends React.PureComponent<
486
496
  };
487
497
 
488
498
  private getTransform() {
489
- return (this.props.inverted && this.transformStyle) || undefined;
499
+ const transformStyle = this.props.horizontal
500
+ ? this.transformStyleHorizontal
501
+ : this.transformStyle;
502
+ return (this.props.inverted && transformStyle) || undefined;
490
503
  }
491
504
 
492
505
  private getContentContainerInfo() {
@@ -530,11 +543,18 @@ class FlashList<T> extends React.PureComponent<
530
543
  }
531
544
 
532
545
  private separator = (index: number) => {
533
- const leadingItem = this.props.data?.[index];
534
- const trailingItem = this.props.data?.[index + 1];
535
- if (trailingItem === undefined) {
546
+ // Make sure we have data and don't read out of bounds
547
+ if (
548
+ this.props.data === null ||
549
+ this.props.data === undefined ||
550
+ index + 1 >= this.props.data.length
551
+ ) {
536
552
  return null;
537
553
  }
554
+
555
+ const leadingItem = this.props.data[index];
556
+ const trailingItem = this.props.data[index + 1];
557
+
538
558
  const props = {
539
559
  leadingItem,
540
560
  trailingItem,
@@ -565,13 +585,18 @@ class FlashList<T> extends React.PureComponent<
565
585
  };
566
586
 
567
587
  private footer = () => {
588
+ /** The web version of CellContainer uses a div directly which doesn't compose styles the way a View does.
589
+ * We will skip using CellContainer on web to avoid this issue. `getFooterContainer` on web will
590
+ * return a View. */
591
+ const FooterContainer = getFooterContainer() ?? CellContainer;
568
592
  return (
569
593
  <>
570
- <View
594
+ <FooterContainer
595
+ index={-1}
571
596
  style={[this.props.ListFooterComponentStyle, this.getTransform()]}
572
597
  >
573
598
  {this.getValidComponent(this.props.ListFooterComponent)}
574
- </View>
599
+ </FooterContainer>
575
600
  <View
576
601
  style={{
577
602
  paddingBottom: this.contentStyle.paddingBottom,
@@ -589,7 +614,8 @@ class FlashList<T> extends React.PureComponent<
589
614
  this.state.dataProvider.getSize() > 0 ? (
590
615
  <View style={{ opacity: 0 }} pointerEvents="none">
591
616
  {this.rowRendererWithIndex(
592
- Math.min(this.state.dataProvider.getSize() - 1, 1)
617
+ Math.min(this.state.dataProvider.getSize() - 1, 1),
618
+ RenderTargetOptions.Measurement
593
619
  )}
594
620
  </View>
595
621
  ) : null;
@@ -615,13 +641,18 @@ class FlashList<T> extends React.PureComponent<
615
641
  this.stickyContentContainerRef?.setEnabled(this.isStickyEnabled);
616
642
  };
617
643
 
618
- private rowRendererWithIndex = (index: number) => {
644
+ private rowRendererSticky = (index: number) => {
645
+ return this.rowRendererWithIndex(index, RenderTargetOptions.StickyHeader);
646
+ };
647
+
648
+ private rowRendererWithIndex = (index: number, target: RenderTarget) => {
619
649
  // known issue: expected to pass separators which isn't available in RLV
620
650
  return this.props.renderItem?.({
621
- item: this.props.data?.[index],
651
+ item: this.props.data![index],
622
652
  index,
653
+ target,
623
654
  extraData: this.state.extraData?.value,
624
- } as any) as JSX.Element;
655
+ }) as JSX.Element;
625
656
  };
626
657
 
627
658
  /**
@@ -644,7 +675,7 @@ class FlashList<T> extends React.PureComponent<
644
675
  : "row",
645
676
  }}
646
677
  >
647
- {this.rowRendererWithIndex(index)}
678
+ {this.rowRendererWithIndex(index, RenderTargetOptions.Cell)}
648
679
  </View>
649
680
  {this.separator(index)}
650
681
  </>
@@ -659,13 +690,18 @@ class FlashList<T> extends React.PureComponent<
659
690
  this.stickyContentContainerRef = ref;
660
691
  };
661
692
 
662
- private stickyRowRenderer = (_: any, __: any, index: number, ___: any) => {
693
+ private stickyOverrideRowRenderer = (
694
+ _: any,
695
+ __: any,
696
+ index: number,
697
+ ___: any
698
+ ) => {
663
699
  return (
664
700
  <PureComponentWrapper
665
701
  ref={this.stickyContentRef}
666
702
  enabled={this.isStickyEnabled}
667
703
  arg={index}
668
- renderer={this.rowRendererWithIndex}
704
+ renderer={this.rowRendererSticky}
669
705
  />
670
706
  );
671
707
  };
@@ -748,16 +784,39 @@ class FlashList<T> extends React.PureComponent<
748
784
  viewOffset?: number | undefined;
749
785
  viewPosition?: number | undefined;
750
786
  }) {
751
- // known issue: no support for view offset/position
752
- this.rlvRef?.scrollToIndex(params.index, Boolean(params.animated));
787
+ const layout = this.rlvRef?.getLayout(params.index);
788
+ const listSize = this.rlvRef?.getRenderedSize();
789
+
790
+ if (layout && listSize) {
791
+ const itemOffset = this.props.horizontal ? layout.x : layout.y;
792
+ const fixedDimension = this.props.horizontal
793
+ ? listSize.width
794
+ : listSize.height;
795
+ const itemSize = this.props.horizontal ? layout.width : layout.height;
796
+ const scrollOffset =
797
+ Math.max(
798
+ 0,
799
+ itemOffset - (params.viewPosition ?? 0) * (fixedDimension - itemSize)
800
+ ) - (params.viewOffset ?? 0);
801
+ this.rlvRef?.scrollToOffset(
802
+ scrollOffset,
803
+ scrollOffset,
804
+ Boolean(params.animated),
805
+ true
806
+ );
807
+ }
753
808
  }
754
809
 
755
810
  public scrollToItem(params: {
756
811
  animated?: boolean | null | undefined;
757
812
  item: any;
758
813
  viewPosition?: number | undefined;
814
+ viewOffset?: number | undefined;
759
815
  }) {
760
- this.rlvRef?.scrollToItem(params.item, Boolean(params.animated));
816
+ const index = this.props.data?.indexOf(params.item) ?? -1;
817
+ if (index >= 0) {
818
+ this.scrollToIndex({ ...params, index });
819
+ }
761
820
  }
762
821
 
763
822
  public scrollToOffset(params: {
@@ -1,3 +1,4 @@
1
+ import type React from "react";
1
2
  import {
2
3
  StyleProp,
3
4
  ScrollViewProps,
@@ -12,10 +13,25 @@ import ViewToken from "./viewability/ViewToken";
12
13
 
13
14
  export interface ListRenderItemInfo<TItem> {
14
15
  item: TItem;
15
-
16
16
  index: number;
17
+ /**
18
+ * FlashList may render your items for multiple reasons.
19
+ * Cell - This is for your list item
20
+ * Measurement - Might be invoked for size measurement and won't be visible. You can ignore this in analytics.
21
+ * StickyHeader - This is for your sticky header. Use this to change your item's appearance while it's being used as a sticky header.
22
+ */
23
+ target: RenderTarget;
24
+ extraData?: any;
17
25
  }
18
26
 
27
+ export type RenderTarget = "Cell" | "StickyHeader" | "Measurement";
28
+
29
+ export const RenderTargetOptions: Record<string, RenderTarget> = {
30
+ Cell: "Cell",
31
+ StickyHeader: "StickyHeader",
32
+ Measurement: "Measurement",
33
+ };
34
+
19
35
  export type ListRenderItem<TItem> = (
20
36
  info: ListRenderItemInfo<TItem>
21
37
  ) => React.ReactElement | null;
@@ -122,6 +138,13 @@ export interface FlashListProps<TItem> extends ScrollViewProps {
122
138
  */
123
139
  ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
124
140
 
141
+ /**
142
+ * Rendered as the main scrollview.
143
+ */
144
+ renderScrollComponent?:
145
+ | React.ComponentType<ScrollViewProps>
146
+ | React.FC<ScrollViewProps>;
147
+
125
148
  /**
126
149
  * You can use `contentContainerStyle` to apply padding that will be applied to the whole content itself.
127
150
  * For example, you can apply this padding, so that all of your items have leading and trailing space.
@@ -1,14 +1,20 @@
1
1
  import React, { useEffect } from "react";
2
- import { ScrollView, Text } from "react-native";
2
+ import { Animated, ScrollView, Text, View } from "react-native";
3
3
  import "@quilted/react-testing/matchers";
4
4
  import { ProgressiveListView } from "recyclerlistview";
5
5
 
6
6
  import Warnings from "../errors/Warnings";
7
7
  import AutoLayoutView from "../native/auto-layout/AutoLayoutView";
8
8
  import CellContainer from "../native/cell-container/CellContainer";
9
+ import { ListRenderItemInfo, RenderTargetOptions } from "../FlashListProps";
9
10
 
10
11
  import { mountFlashList } from "./helpers/mountFlashList";
11
12
 
13
+ jest.mock("../native/cell-container/CellContainer", () => {
14
+ return jest.requireActual("../native/cell-container/CellContainer.ios.ts")
15
+ .default;
16
+ });
17
+
12
18
  describe("FlashList", () => {
13
19
  beforeEach(() => {
14
20
  jest.clearAllMocks();
@@ -600,7 +606,9 @@ describe("FlashList", () => {
600
606
 
601
607
  // items widths before layout manager change should be 400
602
608
  flashList.findAll(CellContainer).forEach((cell) => {
603
- expect(cell.instance.props.style.width).toBe(400);
609
+ if (cell.props.index !== -1) {
610
+ expect(cell.instance.props.style.width).toBe(400);
611
+ }
604
612
  });
605
613
 
606
614
  // This will cause a layout manager change
@@ -613,7 +621,9 @@ describe("FlashList", () => {
613
621
 
614
622
  // items widths after layout manager change should be 900
615
623
  flashList.findAll(CellContainer).forEach((cell) => {
616
- expect(cell.instance.props.style.width).toBe(900);
624
+ if (cell.props.index !== -1) {
625
+ expect(cell.instance.props.style.width).toBe(900);
626
+ }
617
627
  });
618
628
 
619
629
  flashList.unmount();
@@ -712,6 +722,31 @@ describe("FlashList", () => {
712
722
  ).toBe(250);
713
723
  flashList.unmount();
714
724
  });
725
+ it("forwards correct renderTarget", () => {
726
+ const renderItem = ({ target }: ListRenderItemInfo<string>) => {
727
+ return <Text>{target}</Text>;
728
+ };
729
+ const flashList = mountFlashList({
730
+ data: ["0"],
731
+ stickyHeaderIndices: [0],
732
+ renderItem,
733
+ });
734
+ expect(flashList.find(Animated.View)?.find(Text)?.props.children).toBe(
735
+ RenderTargetOptions.StickyHeader
736
+ );
737
+ expect(flashList.find(View)?.find(Text)?.props.children).toBe(
738
+ RenderTargetOptions.Cell
739
+ );
740
+ const flashListHorizontal = mountFlashList({
741
+ renderItem,
742
+ horizontal: true,
743
+ });
744
+ expect(
745
+ flashListHorizontal
746
+ .findAllWhere((node: any) => node?.props?.style?.opacity === 0)[0]
747
+ .find(Text)?.props.children
748
+ ).toBe("Measurement");
749
+ });
715
750
  it("force updates items only when renderItem change", () => {
716
751
  const renderItem = jest.fn(() => <Text>Test</Text>);
717
752
  const flashList = mountFlashList({
@@ -735,4 +770,58 @@ describe("FlashList", () => {
735
770
  flashList.setProps({ disableAutoLayout: true });
736
771
  expect(flashList.find(AutoLayoutView)?.props.disableAutoLayout).toBe(true);
737
772
  });
773
+ it("computes correct scrollTo offset when view position is specified", () => {
774
+ const flashList = mountFlashList({
775
+ data: new Array(40).fill(1).map((_, index) => {
776
+ return index.toString();
777
+ }),
778
+ });
779
+ const plv = flashList.find(ProgressiveListView)
780
+ ?.instance as ProgressiveListView;
781
+ const scrollToOffset = jest.spyOn(plv, "scrollToOffset");
782
+ flashList.instance.scrollToIndex({ index: 10, viewPosition: 0.5 });
783
+
784
+ // Each item is 200px in height and to position it in the middle of the window (900 x 400), its offset needs to be
785
+ // reduced by 350px. That gives us 1650. Other test cases follow the same logic.
786
+ expect(scrollToOffset).toBeCalledWith(1650, 1650, false, true);
787
+ flashList.instance.scrollToItem({
788
+ item: "10",
789
+ viewPosition: 0.5,
790
+ });
791
+ expect(scrollToOffset).toBeCalledWith(1650, 1650, false, true);
792
+ flashList.setProps({ horizontal: true });
793
+ flashList.instance.scrollToItem({
794
+ item: "10",
795
+ viewPosition: 0.5,
796
+ });
797
+ expect(scrollToOffset).toBeCalledWith(1900, 1900, false, true);
798
+ flashList.unmount();
799
+ });
800
+ it("computes correct scrollTo offset when view offset is specified", () => {
801
+ const flashList = mountFlashList({
802
+ data: new Array(40).fill(1).map((_, index) => {
803
+ return index.toString();
804
+ }),
805
+ });
806
+ const plv = flashList.find(ProgressiveListView)
807
+ ?.instance as ProgressiveListView;
808
+ const scrollToOffset = jest.spyOn(plv, "scrollToOffset");
809
+
810
+ // Each item is 200px in height and to position it in the middle of the window (900 x 400), it's offset needs to be
811
+ // reduced by 350px + 100px offset. That gives us 1550. Other test cases follow the same logic.
812
+ flashList.instance.scrollToIndex({
813
+ index: 10,
814
+ viewPosition: 0.5,
815
+ viewOffset: 100,
816
+ });
817
+ expect(scrollToOffset).toBeCalledWith(1550, 1550, false, true);
818
+ flashList.setProps({ horizontal: true });
819
+ flashList.instance.scrollToItem({
820
+ item: "10",
821
+ viewPosition: 0.5,
822
+ viewOffset: 100,
823
+ });
824
+ expect(scrollToOffset).toBeCalledWith(1800, 1800, false, true);
825
+ flashList.unmount();
826
+ });
738
827
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getCellContainerPlatformStyles,
3
3
  getItemAnimator,
4
- } from "../utils/PlatformHelper.web";
4
+ } from "../native/config/PlatformHelper.web";
5
5
 
6
6
  describe("Platform Helper Web", () => {
7
7
  it("can compute right transform for web", () => {
@@ -13,8 +13,18 @@ describe("Platform Helper Web", () => {
13
13
  x: 30,
14
14
  y: 30,
15
15
  });
16
+ const transformHorizontalInvertedStyle = getCellContainerPlatformStyles(
17
+ true,
18
+ {
19
+ x: 30,
20
+ y: 30,
21
+ isHorizontal: true,
22
+ }
23
+ );
16
24
  const expectedTransform = "translate(20px,70px)";
17
25
  const expectedTransformInverted = "translate(30px,30px) scaleY(-1)";
26
+ const expectedTransformHorizontalInverted =
27
+ "translate(30px,30px) scaleX(-1)";
18
28
 
19
29
  expect(transformStyle?.transform).toBe(expectedTransform);
20
30
  expect(transformStyle?.WebkitTransform).toBe(expectedTransform);
@@ -22,6 +32,12 @@ describe("Platform Helper Web", () => {
22
32
  expect(transformInvertedStyle?.WebkitTransform).toBe(
23
33
  expectedTransformInverted
24
34
  );
35
+ expect(transformHorizontalInvertedStyle?.transform).toBe(
36
+ expectedTransformHorizontalInverted
37
+ );
38
+ expect(transformHorizontalInvertedStyle?.WebkitTransform).toBe(
39
+ expectedTransformHorizontalInverted
40
+ );
25
41
  });
26
42
  it("can return an animator", () => {
27
43
  expect(getItemAnimator()!["animateWillMount"]).toBeDefined();
@@ -1,10 +1,10 @@
1
1
  import React from "react";
2
- import { ListRenderItem, Text } from "react-native";
2
+ import { Text } from "react-native";
3
3
  import "@quilted/react-testing/matchers";
4
4
  import { mount, Root } from "@quilted/react-testing";
5
5
 
6
6
  import FlashList from "../../FlashList";
7
- import { FlashListProps } from "../../FlashListProps";
7
+ import { FlashListProps, ListRenderItem } from "../../FlashListProps";
8
8
 
9
9
  jest.mock("../../FlashList", () => {
10
10
  const ActualFlashList = jest.requireActual("../../FlashList").default;
@@ -66,5 +66,5 @@ export class Cancellable {
66
66
  return this._isCancelled;
67
67
  }
68
68
 
69
- public _isCancelled: boolean = false;
69
+ public _isCancelled = false;
70
70
  }
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export {
4
4
  ContentStyle,
5
5
  ListRenderItem,
6
6
  ListRenderItemInfo,
7
+ RenderTarget,
8
+ RenderTargetOptions,
7
9
  } from "./FlashListProps";
8
10
  export { default as AnimatedFlashList } from "./AnimatedFlashList";
9
11
  export {
@@ -1,8 +1,7 @@
1
- import React from "react";
2
- import { View } from "react-native";
1
+ import { requireNativeComponent } from "react-native";
3
2
 
4
3
  import { AutoLayoutViewNativeComponentProps } from "./AutoLayoutViewNativeComponentProps";
5
4
 
6
5
  const AutoLayoutViewNativeComponent =
7
- View as any as React.Component<AutoLayoutViewNativeComponentProps>;
6
+ requireNativeComponent<AutoLayoutViewNativeComponentProps>("AutoLayoutView");
8
7
  export default AutoLayoutViewNativeComponent;
@@ -0,0 +1,7 @@
1
+ import { requireNativeComponent } from "react-native";
2
+
3
+ import { AutoLayoutViewNativeComponentProps } from "./AutoLayoutViewNativeComponentProps";
4
+
5
+ const AutoLayoutViewNativeComponent =
6
+ requireNativeComponent<AutoLayoutViewNativeComponentProps>("AutoLayoutView");
7
+ export default AutoLayoutViewNativeComponent;
@@ -1,7 +1,7 @@
1
- import { requireNativeComponent } from "react-native";
1
+ import { HostComponent, View } from "react-native";
2
2
 
3
3
  import { AutoLayoutViewNativeComponentProps } from "./AutoLayoutViewNativeComponentProps";
4
4
 
5
5
  const AutoLayoutViewNativeComponent =
6
- requireNativeComponent<AutoLayoutViewNativeComponentProps>("AutoLayoutView");
6
+ View as any as HostComponent<AutoLayoutViewNativeComponentProps>;
7
7
  export default AutoLayoutViewNativeComponent;
@@ -0,0 +1,7 @@
1
+ import { requireNativeComponent } from "react-native";
2
+
3
+ /**
4
+ * Behaves as a regular `View` with an extra `index` prop that is saved in the native layer.
5
+ */
6
+ const CellContainer = requireNativeComponent("CellContainer");
7
+ export default CellContainer;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { View, ViewProps } from "react-native";
3
+
4
+ export interface CellContainerProps extends ViewProps {
5
+ index: number;
6
+ }
7
+
8
+ const CellContainer = React.forwardRef(
9
+ (props: CellContainerProps, ref: any) => {
10
+ return <View ref={ref} {...props} />;
11
+ }
12
+ );
13
+ CellContainer.displayName = "CellContainer";
14
+ export default CellContainer;
@@ -5,7 +5,7 @@ const PlatformConfig = {
5
5
  };
6
6
  const getCellContainerPlatformStyles = (
7
7
  inverted: boolean,
8
- parentProps: { x: number; y: number }
8
+ parentProps: { x: number; y: number; isHorizontal?: boolean }
9
9
  ): { transform: string; WebkitTransform: string } | undefined => {
10
10
  return undefined;
11
11
  };
@@ -13,4 +13,14 @@ const getCellContainerPlatformStyles = (
13
13
  const getItemAnimator = (): BaseItemAnimator | undefined => {
14
14
  return undefined;
15
15
  };
16
- export { PlatformConfig, getCellContainerPlatformStyles, getItemAnimator };
16
+
17
+ const getFooterContainer = (): React.ComponentClass | undefined => {
18
+ return undefined;
19
+ };
20
+
21
+ export {
22
+ PlatformConfig,
23
+ getCellContainerPlatformStyles,
24
+ getItemAnimator,
25
+ getFooterContainer,
26
+ };
@@ -0,0 +1,26 @@
1
+ import { BaseItemAnimator } from "recyclerlistview";
2
+
3
+ const PlatformConfig = {
4
+ defaultDrawDistance: 250,
5
+ };
6
+ const getCellContainerPlatformStyles = (
7
+ inverted: boolean,
8
+ parentProps: { x: number; y: number; isHorizontal?: boolean }
9
+ ): { transform: string; WebkitTransform: string } | undefined => {
10
+ return undefined;
11
+ };
12
+
13
+ const getItemAnimator = (): BaseItemAnimator | undefined => {
14
+ return undefined;
15
+ };
16
+
17
+ const getFooterContainer = (): React.ComponentClass | undefined => {
18
+ return undefined;
19
+ };
20
+
21
+ export {
22
+ PlatformConfig,
23
+ getCellContainerPlatformStyles,
24
+ getItemAnimator,
25
+ getFooterContainer,
26
+ };
@@ -0,0 +1,27 @@
1
+ import { BaseItemAnimator } from "recyclerlistview";
2
+ import { DefaultJSItemAnimator } from "recyclerlistview/dist/reactnative/platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator";
3
+
4
+ const PlatformConfig = {
5
+ defaultDrawDistance: 250,
6
+ };
7
+ const getCellContainerPlatformStyles = (
8
+ inverted: boolean,
9
+ parentProps: { x: number; y: number; isHorizontal?: boolean }
10
+ ): { transform: string; WebkitTransform: string } | undefined => {
11
+ return undefined;
12
+ };
13
+
14
+ const getItemAnimator = (): BaseItemAnimator | undefined => {
15
+ return new DefaultJSItemAnimator();
16
+ };
17
+
18
+ const getFooterContainer = (): React.ComponentClass | undefined => {
19
+ return undefined;
20
+ };
21
+
22
+ export {
23
+ PlatformConfig,
24
+ getCellContainerPlatformStyles,
25
+ getItemAnimator,
26
+ getFooterContainer,
27
+ };
@@ -1,3 +1,5 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
1
3
  import { BaseItemAnimator } from "recyclerlistview";
2
4
  import { DefaultJSItemAnimator } from "recyclerlistview/dist/reactnative/platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator";
3
5
 
@@ -6,10 +8,10 @@ const PlatformConfig = {
6
8
  };
7
9
  const getCellContainerPlatformStyles = (
8
10
  inverted: boolean,
9
- parentProps: { x: number; y: number }
11
+ parentProps: { x: number; y: number; isHorizontal?: boolean }
10
12
  ): { transform: string; WebkitTransform: string } | undefined => {
11
13
  const transformValue = `translate(${parentProps.x}px,${parentProps.y}px)${
12
- inverted ? ` scaleY(-1)` : ``
14
+ inverted ? ` ${parentProps.isHorizontal ? `scaleX` : `scaleY`}(-1)` : ``
13
15
  }`;
14
16
  return { transform: transformValue, WebkitTransform: transformValue };
15
17
  };
@@ -17,4 +19,14 @@ const getCellContainerPlatformStyles = (
17
19
  const getItemAnimator = (): BaseItemAnimator | undefined => {
18
20
  return new DefaultJSItemAnimator();
19
21
  };
20
- export { PlatformConfig, getCellContainerPlatformStyles, getItemAnimator };
22
+
23
+ const getFooterContainer = (): React.ComponentClass | undefined => {
24
+ return View;
25
+ };
26
+
27
+ export {
28
+ PlatformConfig,
29
+ getCellContainerPlatformStyles,
30
+ getItemAnimator,
31
+ getFooterContainer,
32
+ };
@@ -5,7 +5,7 @@ export class AverageWindow {
5
5
  private currentAverage: number;
6
6
  private currentCount: number;
7
7
  private inputValues: (number | undefined)[];
8
- private nextIndex: number = 0;
8
+ private nextIndex = 0;
9
9
  constructor(size: number, startValue?: number) {
10
10
  this.inputValues = new Array<number>(Math.max(1, size));
11
11
  this.currentAverage = startValue ?? 0;