@momo-kits/carousel 0.164.1-beta.1 → 0.164.1-test.10

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 (40) hide show
  1. package/core/LICENSE +21 -0
  2. package/core/README.md +16 -0
  3. package/core/components/Carousel.tsx +28 -0
  4. package/core/components/CarouselLayout.tsx +231 -0
  5. package/core/components/ItemLayout.tsx +215 -0
  6. package/core/components/ItemRenderer.tsx +126 -0
  7. package/core/components/LazyView.tsx +14 -0
  8. package/core/components/ScrollViewGesture.tsx +443 -0
  9. package/core/constants/index.ts +11 -0
  10. package/core/hooks/useAutoPlay.ts +63 -0
  11. package/core/hooks/useCarouselController.tsx +295 -0
  12. package/core/hooks/useCheckMounted.ts +14 -0
  13. package/core/hooks/useCommonVariables.ts +261 -0
  14. package/core/hooks/useInitProps.ts +177 -0
  15. package/core/hooks/useLayoutConfig.ts +35 -0
  16. package/core/hooks/useOffsetX.ts +81 -0
  17. package/core/hooks/useOnProgressChange.ts +33 -0
  18. package/core/hooks/usePanGestureProxy.ts +144 -0
  19. package/core/hooks/usePropsErrorBoundary.ts +41 -0
  20. package/core/hooks/useUpdateGestureConfig.ts +17 -0
  21. package/core/hooks/useVisibleRanges.tsx +141 -0
  22. package/core/layouts/index.tsx +10 -0
  23. package/core/layouts/normal.ts +32 -0
  24. package/core/layouts/parallax.ts +50 -0
  25. package/core/layouts/stack.ts +266 -0
  26. package/core/public-types.ts +209 -0
  27. package/core/store/index.tsx +63 -0
  28. package/core/types.ts +32 -0
  29. package/core/utils/carousel-direction.ts +39 -0
  30. package/core/utils/carousel-math.ts +156 -0
  31. package/core/utils/compute-gesture-translation.ts +29 -0
  32. package/core/utils/compute-offset-if-size-changed.ts +14 -0
  33. package/core/utils/computed-with-auto-fill-data.ts +92 -0
  34. package/core/utils/deal-with-animation.ts +19 -0
  35. package/core/utils/log.ts +12 -0
  36. package/core/utils/sanitize-animation-style.ts +20 -0
  37. package/index.tsx +390 -455
  38. package/package.json +7 -6
  39. package/types.ts +123 -48
  40. package/animation.ts +0 -62
package/core/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Doho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/core/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Vendored carousel engine
2
+
3
+ Source: [react-native-reanimated-carousel](https://github.com/dohooo/react-native-reanimated-carousel) v5.1.1, MIT (see `LICENSE`).
4
+
5
+ Copied from the upstream `src/` directory, minus two things we do not ship:
6
+
7
+ - `index.tsx` — the package root is `../index.tsx` instead.
8
+ - `components/Pagination/` — MoMo screens use `Pagination` from
9
+ `@momo-kits/foundation`.
10
+
11
+ Do not edit these files for MoMo-specific behaviour. `../index.tsx` decides what
12
+ the package exposes.
13
+
14
+ To update: re-copy upstream `src/` over `core/`, drop the two paths above again,
15
+ keep `LICENSE` and this file, then re-check `../index.tsx` and `../types.ts`
16
+ against the upstream public types.
@@ -0,0 +1,28 @@
1
+ import React from "react";
2
+ import { useCommonVariables } from "../hooks/useCommonVariables";
3
+ import { useInitProps } from "../hooks/useInitProps";
4
+ import { usePropsErrorBoundary } from "../hooks/usePropsErrorBoundary";
5
+ import { GlobalStateProvider } from "../store";
6
+ import type { CarouselProps, CarouselRef } from "../types";
7
+ import { CarouselLayout } from "./CarouselLayout";
8
+
9
+ const CarouselImpl = React.forwardRef<CarouselRef, CarouselProps<unknown>>((_props, ref) => {
10
+ const props = useInitProps(_props);
11
+ const { dataLength } = props;
12
+ const commonVariables = useCommonVariables(props);
13
+ usePropsErrorBoundary({ ...props, dataLength });
14
+
15
+ return (
16
+ <GlobalStateProvider value={{ props, common: commonVariables }}>
17
+ <CarouselLayout ref={ref} />
18
+ </GlobalStateProvider>
19
+ );
20
+ });
21
+
22
+ type CarouselComponent = <T>(
23
+ props: CarouselProps<T> & {
24
+ ref?: React.Ref<CarouselRef>;
25
+ }
26
+ ) => React.ReactElement;
27
+
28
+ export const Carousel = CarouselImpl as CarouselComponent;
@@ -0,0 +1,231 @@
1
+ import React from "react";
2
+ import { type StyleProp, StyleSheet, View, type ViewStyle } from "react-native";
3
+ import { useAnimatedReaction, useAnimatedStyle, useDerivedValue } from "react-native-reanimated";
4
+ import { scheduleOnRN } from "react-native-worklets";
5
+ import { useAutoPlay } from "../hooks/useAutoPlay";
6
+ import { useCarouselController } from "../hooks/useCarouselController";
7
+ import { useLayoutConfig } from "../hooks/useLayoutConfig";
8
+ import { useOnProgressChange } from "../hooks/useOnProgressChange";
9
+ import { useGlobalState } from "../store";
10
+ import type { CarouselRef } from "../types";
11
+ import { ItemRenderer } from "./ItemRenderer";
12
+ import { ScrollViewGesture } from "./ScrollViewGesture";
13
+
14
+ export function resolveCarouselLayoutStyle(params: {
15
+ flattenedStyle: Partial<ViewStyle>;
16
+ isVertical: boolean;
17
+ measuredSize: number;
18
+ sizeExplicit: boolean;
19
+ }) {
20
+ "worklet";
21
+
22
+ const { flattenedStyle, isVertical, measuredSize, sizeExplicit } = params;
23
+ const { width, height } = flattenedStyle;
24
+ const resolvedMainAxisSize = measuredSize || "100%";
25
+
26
+ return {
27
+ width: width ?? (isVertical ? "100%" : sizeExplicit ? resolvedMainAxisSize : "100%"),
28
+ height: height ?? (isVertical ? (sizeExplicit ? resolvedMainAxisSize : "100%") : "100%"),
29
+ };
30
+ }
31
+
32
+ export const CarouselLayout = React.forwardRef<CarouselRef>((_props, ref) => {
33
+ const { props, common } = useGlobalState();
34
+
35
+ const {
36
+ testID,
37
+ loop,
38
+ autoFillData,
39
+ // Fill data with autoFillData
40
+ data,
41
+ rawData,
42
+ // Length of fill data
43
+ dataLength,
44
+ // Length of raw data
45
+ rawDataLength,
46
+ layout,
47
+ style,
48
+ contentContainerStyle,
49
+ orientation,
50
+ autoplay,
51
+ renderWindowSize,
52
+ autoplayDirection,
53
+ autoplayInterval,
54
+ animation,
55
+ renderItem,
56
+ onSnapToItem,
57
+ onScrollStart,
58
+ onProgressChange,
59
+ progress,
60
+ itemAnimation,
61
+ defaultIndex,
62
+ keyExtractor,
63
+ } = props;
64
+ const isVertical = orientation === "vertical";
65
+
66
+ const { size, handlerOffset, resolvedSize, sizePhase, sizeExplicit } = common;
67
+ const layoutConfig = useLayoutConfig({ ...props, size });
68
+
69
+ const isSizeReady = useDerivedValue(() => {
70
+ const currentSize = resolvedSize.value ?? 0;
71
+ return sizePhase.value === "ready" && currentSize > 0;
72
+ }, [resolvedSize, sizePhase]);
73
+
74
+ const offsetX = useDerivedValue(() => {
75
+ const currentSize = resolvedSize.value ?? 0;
76
+ if (currentSize <= 0) return 0;
77
+
78
+ const totalSize = currentSize * dataLength;
79
+ const value = handlerOffset.value;
80
+ if (!loop || totalSize === 0) {
81
+ return value;
82
+ }
83
+
84
+ const x = value % totalSize;
85
+ return Number.isNaN(x) ? 0 : x;
86
+ }, [loop, dataLength, handlerOffset, resolvedSize]);
87
+
88
+ useOnProgressChange({
89
+ loop,
90
+ offset: handlerOffset,
91
+ progress,
92
+ size,
93
+ sizeReady: isSizeReady,
94
+ rawDataLength,
95
+ onProgressChange,
96
+ });
97
+
98
+ const _onScrollEnd = React.useCallback(
99
+ (realIndex: number) => {
100
+ if (onSnapToItem) onSnapToItem(realIndex);
101
+ },
102
+ [onSnapToItem]
103
+ );
104
+
105
+ const carouselController = useCarouselController({
106
+ ref,
107
+ loop,
108
+ size,
109
+ dataLength,
110
+ rawDataLength,
111
+ handlerOffset,
112
+ animation,
113
+ defaultIndex,
114
+ onMovementEnd: _onScrollEnd,
115
+ onScrollStart,
116
+ });
117
+
118
+ const {
119
+ start: startAutoPlay,
120
+ pause: pauseAutoPlay,
121
+ trigger: triggerAutoPlay,
122
+ } = useAutoPlay({
123
+ autoplay,
124
+ autoplayDirection,
125
+ autoplayInterval,
126
+ carouselController,
127
+ });
128
+
129
+ useAnimatedReaction(
130
+ () => ({ ready: isSizeReady.value }),
131
+ (state, previous) => {
132
+ if (!autoplay) return;
133
+ if (state.ready === previous?.ready) return;
134
+
135
+ if (state.ready) scheduleOnRN(triggerAutoPlay);
136
+ else scheduleOnRN(pauseAutoPlay);
137
+ },
138
+ [autoplay]
139
+ );
140
+
141
+ const scrollViewGestureOnScrollStart = React.useCallback(() => {
142
+ carouselController.startMovement();
143
+ pauseAutoPlay();
144
+ onScrollStart?.();
145
+ }, [carouselController, onScrollStart, pauseAutoPlay]);
146
+
147
+ const scrollViewGestureOnScrollEnd = React.useCallback(() => {
148
+ const settledIndex = carouselController.settle();
149
+ startAutoPlay();
150
+ _onScrollEnd(settledIndex);
151
+ }, [_onScrollEnd, carouselController, startAutoPlay]);
152
+
153
+ const scrollViewGestureOnTouchBegin = React.useCallback(pauseAutoPlay, [pauseAutoPlay]);
154
+
155
+ const scrollViewGestureOnTouchEnd = React.useCallback(startAutoPlay, [startAutoPlay]);
156
+
157
+ const { opacity, transform, ...restContentContainerStyle } =
158
+ StyleSheet.flatten(contentContainerStyle as StyleProp<ViewStyle>) || {};
159
+ const flattenedStyle = StyleSheet.flatten(style) || {};
160
+
161
+ const layoutStyle = useAnimatedStyle(() => {
162
+ const measuredSize = resolvedSize.value ?? 0;
163
+ const { width, height } = resolveCarouselLayoutStyle({
164
+ flattenedStyle,
165
+ isVertical,
166
+ measuredSize,
167
+ sizeExplicit,
168
+ });
169
+
170
+ return {
171
+ width,
172
+ height,
173
+ opacity: isSizeReady.value ? 1 : 0,
174
+ };
175
+ }, [flattenedStyle, isSizeReady, isVertical, resolvedSize, sizePhase, sizeExplicit]);
176
+
177
+ return (
178
+ <View testID={testID} style={[styles.layoutContainer, style]} onLayout={props.onLayout}>
179
+ <ScrollViewGesture
180
+ size={size}
181
+ key={layout?.type}
182
+ translation={handlerOffset}
183
+ style={[
184
+ styles.contentContainer,
185
+ layoutStyle,
186
+ restContentContainerStyle,
187
+ isVertical ? styles.itemsVertical : styles.itemsHorizontal,
188
+ ]}
189
+ testID="carousel-content-container"
190
+ onScrollStart={scrollViewGestureOnScrollStart}
191
+ onScrollEnd={scrollViewGestureOnScrollEnd}
192
+ onTouchBegin={scrollViewGestureOnTouchBegin}
193
+ onTouchEnd={scrollViewGestureOnTouchEnd}
194
+ >
195
+ <ItemRenderer
196
+ data={data}
197
+ rawData={rawData}
198
+ dataLength={dataLength}
199
+ rawDataLength={rawDataLength}
200
+ loop={loop}
201
+ size={size}
202
+ renderWindowSize={renderWindowSize}
203
+ defaultIndex={defaultIndex}
204
+ autoFillData={autoFillData}
205
+ offsetX={offsetX}
206
+ handlerOffset={handlerOffset}
207
+ layoutConfig={layoutConfig}
208
+ renderItem={renderItem}
209
+ itemAnimation={itemAnimation}
210
+ keyExtractor={keyExtractor}
211
+ />
212
+ </ScrollViewGesture>
213
+ </View>
214
+ );
215
+ });
216
+
217
+ const styles = StyleSheet.create({
218
+ layoutContainer: {
219
+ display: "flex",
220
+ overflow: "hidden",
221
+ },
222
+ contentContainer: {
223
+ overflow: "hidden",
224
+ },
225
+ itemsHorizontal: {
226
+ flexDirection: "row",
227
+ },
228
+ itemsVertical: {
229
+ flexDirection: "column",
230
+ },
231
+ });
@@ -0,0 +1,215 @@
1
+ import React from "react";
2
+ import type { LayoutChangeEvent, ViewStyle } from "react-native";
3
+ import { StyleSheet } from "react-native";
4
+ import type { SharedValue } from "react-native-reanimated";
5
+ import Animated, {
6
+ useAnimatedReaction,
7
+ useAnimatedStyle,
8
+ useDerivedValue,
9
+ useSharedValue,
10
+ } from "react-native-reanimated";
11
+ import { scheduleOnRN, scheduleOnUI } from "react-native-worklets";
12
+
13
+ import type { OffsetOptions } from "../hooks/useOffsetX";
14
+ import { useOffsetX } from "../hooks/useOffsetX";
15
+ import type { VisibleRangesValue } from "../hooks/useVisibleRanges";
16
+ import { useGlobalState } from "../store";
17
+ import type { CarouselItemAnimation } from "../types";
18
+ import { getHorizontalStackOffsetType } from "../utils/carousel-direction";
19
+ import { positiveModulo } from "../utils/carousel-math";
20
+ import { sanitizeAnimationStyle } from "../utils/sanitize-animation-style";
21
+
22
+ export type ItemAnimationStyle = CarouselItemAnimation;
23
+
24
+ export function resolveItemMainAxisSize(params: {
25
+ explicitSize?: number | undefined;
26
+ effectivePageSize?: number | undefined;
27
+ measuredSize: number | null;
28
+ }) {
29
+ "worklet";
30
+
31
+ const { explicitSize, effectivePageSize, measuredSize } = params;
32
+
33
+ return typeof explicitSize === "number"
34
+ ? explicitSize
35
+ : (effectivePageSize ?? measuredSize ?? "100%");
36
+ }
37
+
38
+ export const ItemLayout: React.FC<{
39
+ index: number;
40
+ rawIndex: number;
41
+ handlerOffset: SharedValue<number>;
42
+ visibleRanges: VisibleRangesValue;
43
+ animationStyle: ItemAnimationStyle;
44
+ children: (ctx: {
45
+ relativeProgress: SharedValue<number>;
46
+ }) => React.ReactElement;
47
+ }> = (props) => {
48
+ const { handlerOffset, index, rawIndex, children, visibleRanges, animationStyle } = props;
49
+
50
+ const {
51
+ props: { loop, dataLength, directionSign, orientation, layout, style, itemSize },
52
+ common,
53
+ layout: { updateItemDimensions },
54
+ } = useGlobalState();
55
+
56
+ const measuredSize = useSharedValue<{ width: number | null; height: number | null }>({
57
+ width: null,
58
+ height: null,
59
+ });
60
+
61
+ const fallbackSize = common.size;
62
+ const isVertical = orientation === "vertical";
63
+ const { width: styleWidth, height: styleHeight } = StyleSheet.flatten(style) || {};
64
+ const styleWidthNumber = typeof styleWidth === "number" ? styleWidth : undefined;
65
+ const styleHeightNumber = typeof styleHeight === "number" ? styleHeight : undefined;
66
+
67
+ const size = (itemSize ?? fallbackSize) || 0;
68
+ const effectivePageSize = size > 0 ? size : undefined;
69
+
70
+ const dimensionsStyle = useAnimatedStyle<ViewStyle>(() => {
71
+ const widthCandidate = isVertical ? styleWidthNumber : itemSize;
72
+ const heightCandidate = isVertical ? itemSize : styleHeightNumber;
73
+
74
+ const computedWidth = isVertical
75
+ ? typeof widthCandidate === "number"
76
+ ? widthCandidate
77
+ : (measuredSize.value.width ?? "100%")
78
+ : resolveItemMainAxisSize({
79
+ explicitSize: widthCandidate,
80
+ effectivePageSize,
81
+ measuredSize: measuredSize.value.width,
82
+ });
83
+
84
+ const computedHeight = isVertical
85
+ ? resolveItemMainAxisSize({
86
+ explicitSize: heightCandidate,
87
+ effectivePageSize,
88
+ measuredSize: measuredSize.value.height,
89
+ })
90
+ : typeof heightCandidate === "number"
91
+ ? heightCandidate
92
+ : (measuredSize.value.height ?? "100%");
93
+
94
+ return {
95
+ width: computedWidth,
96
+ height: computedHeight,
97
+ };
98
+ }, [effectivePageSize, isVertical, itemSize, styleHeightNumber, styleWidthNumber]);
99
+
100
+ let offsetXConfig: OffsetOptions = {
101
+ handlerOffset,
102
+ index,
103
+ size,
104
+ dataLength,
105
+ loop,
106
+ };
107
+
108
+ if (layout?.type === "horizontal-stack") {
109
+ offsetXConfig = {
110
+ handlerOffset,
111
+ index,
112
+ size,
113
+ dataLength,
114
+ loop,
115
+ type: getHorizontalStackOffsetType(layout.exitDirection ?? "left", directionSign),
116
+ viewCount: layout.visibleCount,
117
+ };
118
+ }
119
+
120
+ const x = useOffsetX(offsetXConfig, visibleRanges);
121
+ const isCurrentForAccessibility = React.useCallback(() => {
122
+ "worklet";
123
+
124
+ return (
125
+ size > 0 &&
126
+ dataLength > 0 &&
127
+ positiveModulo(Math.round(-handlerOffset.value / size), dataLength) === index
128
+ );
129
+ }, [dataLength, handlerOffset, index, size]);
130
+ const [isAccessibilityCurrent, setIsAccessibilityCurrent] =
131
+ React.useState(isCurrentForAccessibility);
132
+
133
+ useAnimatedReaction(
134
+ isCurrentForAccessibility,
135
+ (isCurrent, wasCurrent) => {
136
+ if (isCurrent !== wasCurrent) {
137
+ scheduleOnRN(setIsAccessibilityCurrent, isCurrent);
138
+ }
139
+ },
140
+ [isCurrentForAccessibility]
141
+ );
142
+
143
+ const relativeProgress = useDerivedValue(() => {
144
+ if (!size) return 0;
145
+ return x.value / size;
146
+ }, [x, size]);
147
+ const animatedStyle = useAnimatedStyle<ViewStyle>(() => {
148
+ const safeSize = size || 1;
149
+ return sanitizeAnimationStyle(animationStyle(x.value / safeSize, rawIndex));
150
+ }, [animationStyle, rawIndex, x, size]);
151
+
152
+ // TODO: For dynamic dimension in the future
153
+ // function handleLayout(e: LayoutChangeEvent) {
154
+ // const { width, height } = e.nativeEvent.layout;
155
+ // updateItemDimensions(index, { width, height });
156
+ // }
157
+
158
+ const child = children({ relativeProgress });
159
+
160
+ type LayoutableProps = {
161
+ collapsable?: boolean;
162
+ onLayout?: (event: LayoutChangeEvent) => void;
163
+ };
164
+
165
+ const enhancedChild = React.isValidElement<LayoutableProps>(child)
166
+ ? React.cloneElement(child, {
167
+ collapsable: false,
168
+ onLayout: (event: LayoutChangeEvent) => {
169
+ const { width: layoutWidth, height: layoutHeight } = event.nativeEvent.layout;
170
+ if (layoutWidth > 0 && layoutHeight > 0) {
171
+ scheduleOnUI(() => {
172
+ const { width: prevWidth, height: prevHeight } = measuredSize.value;
173
+ if (prevWidth === layoutWidth && prevHeight === layoutHeight) return;
174
+
175
+ measuredSize.value = {
176
+ width: layoutWidth,
177
+ height: layoutHeight,
178
+ };
179
+ updateItemDimensions(index, {
180
+ width: layoutWidth,
181
+ height: layoutHeight,
182
+ });
183
+ });
184
+ }
185
+
186
+ child.props?.onLayout?.(event);
187
+ },
188
+ })
189
+ : child;
190
+
191
+ return (
192
+ <Animated.View
193
+ accessibilityElementsHidden={!isAccessibilityCurrent}
194
+ accessible={isAccessibilityCurrent ? undefined : false}
195
+ aria-hidden={!isAccessibilityCurrent}
196
+ importantForAccessibility={isAccessibilityCurrent ? "auto" : "no-hide-descendants"}
197
+ style={[
198
+ {
199
+ position: "absolute",
200
+ pointerEvents: "box-none",
201
+ },
202
+ dimensionsStyle,
203
+ animatedStyle,
204
+ ]}
205
+ /**
206
+ * We use this testID to know when the carousel item is ready to be tested in test.
207
+ * e.g.
208
+ * The testID of first item will be changed to __CAROUSEL_ITEM_0_READY__ from __CAROUSEL_ITEM_0_NOT_READY__ when the item is ready.
209
+ * */
210
+ testID={`__CAROUSEL_ITEM_${index}__`}
211
+ >
212
+ {enhancedChild}
213
+ </Animated.View>
214
+ );
215
+ };
@@ -0,0 +1,126 @@
1
+ import React from "react";
2
+ import type { FC } from "react";
3
+ import type { SharedValue } from "react-native-reanimated";
4
+ import { useAnimatedReaction } from "react-native-reanimated";
5
+ import { scheduleOnRN } from "react-native-worklets";
6
+
7
+ import type { ItemAnimationStyle } from "./ItemLayout";
8
+ import { ItemLayout } from "./ItemLayout";
9
+
10
+ import type { VisibleRanges } from "../hooks/useVisibleRanges";
11
+ import { computeVisibleRanges, useVisibleRanges } from "../hooks/useVisibleRanges";
12
+ import type { CarouselItemAnimation, CarouselRenderItem } from "../types";
13
+ import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";
14
+
15
+ interface Props {
16
+ data: unknown[];
17
+ rawData: unknown[];
18
+ dataLength: number;
19
+ rawDataLength: number;
20
+ loop: boolean;
21
+ size: number;
22
+ renderWindowSize?: number | undefined;
23
+ defaultIndex: number;
24
+ autoFillData: boolean;
25
+ offsetX: SharedValue<number>;
26
+ handlerOffset: SharedValue<number>;
27
+ layoutConfig: ItemAnimationStyle;
28
+ renderItem: CarouselRenderItem<unknown>;
29
+ itemAnimation?: CarouselItemAnimation | undefined;
30
+ keyExtractor?: ((item: unknown, index: number) => string) | undefined;
31
+ }
32
+
33
+ export const ItemRenderer: FC<Props> = (props) => {
34
+ const {
35
+ data,
36
+ rawData,
37
+ size,
38
+ renderWindowSize,
39
+ defaultIndex,
40
+ handlerOffset,
41
+ offsetX,
42
+ dataLength,
43
+ rawDataLength,
44
+ loop,
45
+ autoFillData,
46
+ layoutConfig,
47
+ renderItem,
48
+ itemAnimation,
49
+ keyExtractor,
50
+ } = props;
51
+
52
+ const visibleRanges = useVisibleRanges({
53
+ total: dataLength,
54
+ viewSize: size,
55
+ translation: handlerOffset,
56
+ windowSize: renderWindowSize,
57
+ loop,
58
+ });
59
+
60
+ // Initialize with a sensible default to avoid blank render on first frame
61
+ const initialRanges: VisibleRanges = React.useMemo(
62
+ () =>
63
+ computeVisibleRanges({
64
+ total: dataLength,
65
+ windowSize: renderWindowSize,
66
+ currentIndex: defaultIndex,
67
+ loop,
68
+ }),
69
+ [dataLength, defaultIndex, loop, renderWindowSize]
70
+ );
71
+
72
+ const [displayedItems, setDisplayedItems] = React.useState<VisibleRanges>(initialRanges);
73
+
74
+ useAnimatedReaction(
75
+ () => visibleRanges.value,
76
+ (ranges) => scheduleOnRN(setDisplayedItems, ranges),
77
+ [visibleRanges]
78
+ );
79
+
80
+ return (
81
+ <>
82
+ {data.map((item, index) => {
83
+ const realIndex = computedRealIndexWithAutoFillData({
84
+ index,
85
+ dataLength: rawDataLength,
86
+ loop,
87
+ autoFillData,
88
+ });
89
+
90
+ const { negativeRange, positiveRange } = displayedItems;
91
+
92
+ const shouldRender =
93
+ (index >= negativeRange[0] && index <= negativeRange[1]) ||
94
+ (index >= positiveRange[0] && index <= positiveRange[1]);
95
+
96
+ if (!shouldRender) return null;
97
+
98
+ const rawItem = rawData[realIndex] ?? item;
99
+ const key = keyExtractor
100
+ ? `${keyExtractor(rawItem, realIndex)}::rnrc-copy-${Math.floor(
101
+ index / Math.max(rawDataLength, 1)
102
+ )}`
103
+ : String(index);
104
+
105
+ return (
106
+ <ItemLayout
107
+ key={key}
108
+ index={index}
109
+ rawIndex={realIndex}
110
+ handlerOffset={offsetX}
111
+ visibleRanges={visibleRanges}
112
+ animationStyle={itemAnimation || layoutConfig}
113
+ >
114
+ {({ relativeProgress }) =>
115
+ renderItem({
116
+ item: rawItem,
117
+ index: realIndex,
118
+ relativeProgress,
119
+ })
120
+ }
121
+ </ItemLayout>
122
+ );
123
+ })}
124
+ </>
125
+ );
126
+ };
@@ -0,0 +1,14 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import React from "react";
3
+
4
+ interface Props {
5
+ shouldUpdate: boolean;
6
+ }
7
+
8
+ export const LazyView: React.FC<PropsWithChildren<Props>> = (props) => {
9
+ const { shouldUpdate, children } = props;
10
+
11
+ if (!shouldUpdate) return <></>;
12
+
13
+ return <>{children}</>;
14
+ };