react-native-reanimated-carousel 5.0.0-beta.5 → 5.0.0-beta.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.
@@ -15,6 +15,34 @@ import { ScrollViewGesture } from "./ScrollViewGesture";
15
15
 
16
16
  export type TAnimationStyle = (value: number) => ViewStyle;
17
17
 
18
+ export function resolveCarouselLayoutStyle(params: {
19
+ flattenedStyle: Partial<ViewStyle>;
20
+ vertical: boolean;
21
+ measuredSize: number;
22
+ sizeExplicit: boolean;
23
+ legacyWidth?: number;
24
+ legacyHeight?: number;
25
+ }) {
26
+ "worklet";
27
+
28
+ const { flattenedStyle, vertical, measuredSize, sizeExplicit, legacyWidth, legacyHeight } =
29
+ params;
30
+ const { width, height } = flattenedStyle;
31
+ const hasLegacyMainAxisSize = vertical
32
+ ? typeof legacyHeight === "number" && legacyHeight > 0
33
+ : typeof legacyWidth === "number" && legacyWidth > 0;
34
+ const shouldUseMeasuredMainAxisSize = sizeExplicit || hasLegacyMainAxisSize;
35
+ const resolvedMainAxisSize = measuredSize || "100%";
36
+
37
+ return {
38
+ width:
39
+ width ?? (vertical ? "100%" : shouldUseMeasuredMainAxisSize ? resolvedMainAxisSize : "100%"),
40
+ height:
41
+ height ??
42
+ (vertical ? (shouldUseMeasuredMainAxisSize ? resolvedMainAxisSize : "100%") : "100%"),
43
+ };
44
+ }
45
+
18
46
  export const CarouselLayout = React.forwardRef<ICarouselInstance>((_props, ref) => {
19
47
  const { props, common } = useGlobalState();
20
48
 
@@ -46,9 +74,11 @@ export const CarouselLayout = React.forwardRef<ICarouselInstance>((_props, ref)
46
74
  onProgressChange,
47
75
  customAnimation,
48
76
  defaultIndex,
77
+ width: legacyWidth,
78
+ height: legacyHeight,
49
79
  } = props;
50
80
 
51
- const { size, handlerOffset, resolvedSize, sizePhase } = common;
81
+ const { size, handlerOffset, resolvedSize, sizePhase, sizeExplicit } = common;
52
82
  const layoutConfig = useLayoutConfig({ ...props, size });
53
83
 
54
84
  const isSizeReady = useDerivedValue(() => {
@@ -157,18 +187,31 @@ export const CarouselLayout = React.forwardRef<ICarouselInstance>((_props, ref)
157
187
  const flattenedStyle = StyleSheet.flatten(style) || {};
158
188
 
159
189
  const layoutStyle = useAnimatedStyle(() => {
160
- const { width, height } = flattenedStyle;
161
190
  const measuredSize = resolvedSize.value ?? 0;
162
-
163
- const computedWidth = width ?? (vertical ? "100%" : measuredSize || "100%");
164
- const computedHeight = height ?? (vertical ? measuredSize || "100%" : "100%");
191
+ const { width, height } = resolveCarouselLayoutStyle({
192
+ flattenedStyle,
193
+ vertical: !!vertical,
194
+ measuredSize,
195
+ sizeExplicit,
196
+ legacyWidth,
197
+ legacyHeight,
198
+ });
165
199
 
166
200
  return {
167
- width: computedWidth,
168
- height: computedHeight,
201
+ width,
202
+ height,
169
203
  opacity: isSizeReady.value ? 1 : 0,
170
204
  };
171
- }, [flattenedStyle, isSizeReady, vertical, resolvedSize, sizePhase]);
205
+ }, [
206
+ flattenedStyle,
207
+ isSizeReady,
208
+ vertical,
209
+ resolvedSize,
210
+ sizePhase,
211
+ sizeExplicit,
212
+ legacyWidth,
213
+ legacyHeight,
214
+ ]);
172
215
 
173
216
  return (
174
217
  <GestureHandlerRootView testID={testID} style={[styles.layoutContainer, style]}>
@@ -19,6 +19,20 @@ import { sanitizeAnimationStyle } from "../utils/sanitize-animation-style";
19
19
 
20
20
  export type TAnimationStyle = NonNullable<TCarouselProps["customAnimation"]>;
21
21
 
22
+ export function resolveItemMainAxisSize(params: {
23
+ explicitSize?: number;
24
+ effectivePageSize?: number;
25
+ measuredSize: number | null;
26
+ }) {
27
+ "worklet";
28
+
29
+ const { explicitSize, effectivePageSize, measuredSize } = params;
30
+
31
+ return typeof explicitSize === "number"
32
+ ? explicitSize
33
+ : (effectivePageSize ?? measuredSize ?? "100%");
34
+ }
35
+
22
36
  export const ItemLayout: React.FC<{
23
37
  index: number;
24
38
  handlerOffset: SharedValue<number>;
@@ -71,15 +85,25 @@ export const ItemLayout: React.FC<{
71
85
  const widthCandidate = vertical ? width : (explicitItemSize ?? explicitAxisSize);
72
86
  const heightCandidate = vertical ? (explicitItemSize ?? explicitAxisSize) : height;
73
87
 
74
- const computedWidth =
75
- typeof widthCandidate === "number"
88
+ const computedWidth = vertical
89
+ ? typeof widthCandidate === "number"
76
90
  ? widthCandidate
77
- : (measuredSize.value.width ?? (vertical ? "100%" : (effectivePageSize ?? "100%")));
78
-
79
- const computedHeight =
80
- typeof heightCandidate === "number"
91
+ : (measuredSize.value.width ?? "100%")
92
+ : resolveItemMainAxisSize({
93
+ explicitSize: widthCandidate,
94
+ effectivePageSize,
95
+ measuredSize: measuredSize.value.width,
96
+ });
97
+
98
+ const computedHeight = vertical
99
+ ? resolveItemMainAxisSize({
100
+ explicitSize: heightCandidate,
101
+ effectivePageSize,
102
+ measuredSize: measuredSize.value.height,
103
+ })
104
+ : typeof heightCandidate === "number"
81
105
  ? heightCandidate
82
- : (measuredSize.value.height ?? (vertical ? (effectivePageSize ?? "100%") : "100%"));
106
+ : (measuredSize.value.height ?? "100%");
83
107
 
84
108
  return {
85
109
  width: computedWidth,
@@ -67,6 +67,35 @@ describe("useVisibleRanges", () => {
67
67
  `);
68
68
  });
69
69
 
70
+ it.each([
71
+ { total: 1, windowSize: 1, currentIndex: 0 },
72
+ { total: 2, windowSize: 5, currentIndex: 1 },
73
+ { total: 3, windowSize: 3, currentIndex: 2 },
74
+ { total: 4, windowSize: 5, currentIndex: 1 },
75
+ ])(
76
+ "should display every item when windowSize=$windowSize is at least total=$total in loop mode",
77
+ ({ total, windowSize, currentIndex }) => {
78
+ const ranges = renderHook(() => {
79
+ const translation = useSharedValue(-currentIndex * viewSize);
80
+ return useVisibleRanges({
81
+ total,
82
+ translation,
83
+ viewSize,
84
+ windowSize,
85
+ loop: true,
86
+ });
87
+ }).result.current.value;
88
+
89
+ const visibleIndexes = Array.from({ length: total }, (_, index) => index).filter(
90
+ (index) =>
91
+ (index >= ranges.negativeRange[0] && index <= ranges.negativeRange[1]) ||
92
+ (index >= ranges.positiveRange[0] && index <= ranges.positiveRange[1])
93
+ );
94
+
95
+ expect(visibleIndexes).toEqual(Array.from({ length: total }, (_, index) => index));
96
+ }
97
+ );
98
+
70
99
  it("should shows the increased range of the list when the loop is false and swiped the carousel.", async () => {
71
100
  const slide0hook = renderHook(() => {
72
101
  const translation = useSharedValue(-0 * viewSize);
@@ -50,7 +50,12 @@ export function useVisibleRanges(options: {
50
50
 
51
51
  let newRanges: VisibleRanges;
52
52
 
53
- if (!loop) {
53
+ if (loop && windowSize >= total) {
54
+ newRanges = {
55
+ negativeRange: [0, 0],
56
+ positiveRange: [0, total - 1],
57
+ };
58
+ } else if (!loop) {
54
59
  // Clamp currentIndex to valid range [0, total-1] for non-loop mode
55
60
  // When overdragging right, translation.value becomes positive, making currentIndex negative
56
61
  currentIndex = Math.max(0, Math.min(total - 1, currentIndex));