@momo-kits/carousel 0.165.2-beta.2 → 0.167.1-beta.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 (40) hide show
  1. package/animation.ts +62 -0
  2. package/index.tsx +455 -390
  3. package/package.json +5 -6
  4. package/types.ts +48 -123
  5. package/core/LICENSE +0 -21
  6. package/core/README.md +0 -16
  7. package/core/components/Carousel.tsx +0 -28
  8. package/core/components/CarouselLayout.tsx +0 -231
  9. package/core/components/ItemLayout.tsx +0 -215
  10. package/core/components/ItemRenderer.tsx +0 -126
  11. package/core/components/LazyView.tsx +0 -14
  12. package/core/components/ScrollViewGesture.tsx +0 -443
  13. package/core/constants/index.ts +0 -11
  14. package/core/hooks/useAutoPlay.ts +0 -63
  15. package/core/hooks/useCarouselController.tsx +0 -295
  16. package/core/hooks/useCheckMounted.ts +0 -14
  17. package/core/hooks/useCommonVariables.ts +0 -261
  18. package/core/hooks/useInitProps.ts +0 -177
  19. package/core/hooks/useLayoutConfig.ts +0 -35
  20. package/core/hooks/useOffsetX.ts +0 -81
  21. package/core/hooks/useOnProgressChange.ts +0 -33
  22. package/core/hooks/usePanGestureProxy.ts +0 -144
  23. package/core/hooks/usePropsErrorBoundary.ts +0 -41
  24. package/core/hooks/useUpdateGestureConfig.ts +0 -17
  25. package/core/hooks/useVisibleRanges.tsx +0 -141
  26. package/core/layouts/index.tsx +0 -10
  27. package/core/layouts/normal.ts +0 -32
  28. package/core/layouts/parallax.ts +0 -50
  29. package/core/layouts/stack.ts +0 -266
  30. package/core/public-types.ts +0 -209
  31. package/core/store/index.tsx +0 -63
  32. package/core/types.ts +0 -32
  33. package/core/utils/carousel-direction.ts +0 -39
  34. package/core/utils/carousel-math.ts +0 -156
  35. package/core/utils/compute-gesture-translation.ts +0 -29
  36. package/core/utils/compute-offset-if-size-changed.ts +0 -14
  37. package/core/utils/computed-with-auto-fill-data.ts +0 -92
  38. package/core/utils/deal-with-animation.ts +0 -19
  39. package/core/utils/log.ts +0 -12
  40. package/core/utils/sanitize-animation-style.ts +0 -20
@@ -1,177 +0,0 @@
1
- import React from "react";
2
- import { I18nManager } from "react-native";
3
-
4
- import { Easing } from "../constants";
5
- import type { CarouselAnimation, CarouselProps } from "../types";
6
- import { type CarouselDirectionSign, getCarouselDirectionSign } from "../utils/carousel-direction";
7
- import { computedFillDataWithAutoFillData } from "../utils/computed-with-auto-fill-data";
8
-
9
- type CarouselSnapMode = NonNullable<CarouselProps<unknown>["snapMode"]>;
10
-
11
- export type InitializedCarouselProps<Item> = Omit<
12
- CarouselProps<Item>,
13
- | "autoplay"
14
- | "autoplayDirection"
15
- | "autoplayInterval"
16
- | "data"
17
- | "defaultIndex"
18
- | "loop"
19
- | "orientation"
20
- | "overscrollEnabled"
21
- | "scrollEnabled"
22
- | "snapMode"
23
- | "style"
24
- > & {
25
- autoplay: boolean;
26
- autoplayDirection: "forward" | "backward";
27
- autoplayInterval: number;
28
- data: Item[];
29
- defaultIndex: number;
30
- loop: boolean;
31
- orientation: "horizontal" | "vertical";
32
- overscrollEnabled: boolean;
33
- scrollEnabled: boolean;
34
- snapMode: CarouselSnapMode;
35
- style: NonNullable<CarouselProps<Item>["style"]>;
36
- animation: CarouselAnimation;
37
- rawData: Item[];
38
- dataLength: number;
39
- rawDataLength: number;
40
- autoFillData: true;
41
- directionSign: CarouselDirectionSign;
42
- };
43
-
44
- export function useInitProps<Item>(props: CarouselProps<Item>): InitializedCarouselProps<Item> {
45
- validateStaticProps(props);
46
-
47
- const rawData = props.data ?? [];
48
- const requestedDefaultIndex = props.defaultIndex ?? 0;
49
- const loop = props.loop ?? false;
50
- const autoplay = props.autoplay ?? false;
51
- const autoplayInterval = props.autoplayInterval ?? 3000;
52
- const autoplayDirection = props.autoplayDirection ?? "forward";
53
- const orientation = props.orientation ?? "horizontal";
54
- const directionSign = getCarouselDirectionSign({
55
- isRTL: I18nManager.isRTL,
56
- orientation,
57
- });
58
- const scrollEnabled = props.scrollEnabled ?? true;
59
- const snapMode = props.snapMode ?? "page";
60
- const overscrollEnabled = props.overscrollEnabled ?? true;
61
- const style = props.style ?? {};
62
- const animation: CarouselAnimation = props.animation ?? {
63
- type: "timing",
64
- duration: 500,
65
- easing: Easing.easeOutQuart,
66
- };
67
-
68
- const defaultIndexState = React.useRef<{
69
- consumed: boolean;
70
- requested: number;
71
- resolved: number;
72
- warned: boolean;
73
- } | null>(null);
74
-
75
- if (!defaultIndexState.current) {
76
- if (rawData.length > 0) {
77
- assertValidDefaultIndex(requestedDefaultIndex, rawData.length);
78
- }
79
-
80
- defaultIndexState.current = {
81
- consumed: rawData.length > 0,
82
- requested: requestedDefaultIndex,
83
- resolved: rawData.length > 0 ? requestedDefaultIndex : 0,
84
- warned: false,
85
- };
86
- } else if (!defaultIndexState.current.consumed && rawData.length > 0) {
87
- const pendingIndex = defaultIndexState.current.requested;
88
- defaultIndexState.current.consumed = true;
89
-
90
- if (isValidDefaultIndex(pendingIndex, rawData.length)) {
91
- defaultIndexState.current.resolved = pendingIndex;
92
- } else {
93
- defaultIndexState.current.resolved = 0;
94
- if (__DEV__ && !defaultIndexState.current.warned) {
95
- console.warn(
96
- `[react-native-reanimated-carousel] Ignored defaultIndex ${pendingIndex} because it is outside the first non-empty data set.`
97
- );
98
- defaultIndexState.current.warned = true;
99
- }
100
- }
101
- }
102
-
103
- const defaultIndex = defaultIndexState.current.resolved;
104
- const data = React.useMemo(
105
- () =>
106
- computedFillDataWithAutoFillData({
107
- loop,
108
- autoFillData: true,
109
- data: rawData,
110
- dataLength: rawData.length,
111
- }),
112
- [loop, rawData]
113
- );
114
-
115
- return {
116
- ...props,
117
- autoplay,
118
- autoplayDirection,
119
- autoplayInterval,
120
- defaultIndex,
121
- loop,
122
- orientation,
123
- overscrollEnabled,
124
- scrollEnabled,
125
- snapMode,
126
- style,
127
- animation,
128
- data,
129
- rawData,
130
- dataLength: data.length,
131
- rawDataLength: rawData.length,
132
- autoFillData: true,
133
- directionSign,
134
- };
135
- }
136
-
137
- function validateStaticProps<Item>(props: CarouselProps<Item>) {
138
- if (props.layout && props.itemAnimation) {
139
- throw new Error(
140
- "[react-native-reanimated-carousel] `layout` and `itemAnimation` are mutually exclusive."
141
- );
142
- }
143
-
144
- if (props.itemSize !== undefined && (!Number.isFinite(props.itemSize) || props.itemSize <= 0)) {
145
- throw new Error("[react-native-reanimated-carousel] itemSize must be a positive number.");
146
- }
147
-
148
- if (
149
- props.autoplayInterval !== undefined &&
150
- (!Number.isFinite(props.autoplayInterval) || props.autoplayInterval < 0)
151
- ) {
152
- throw new Error(
153
- "[react-native-reanimated-carousel] autoplayInterval must be a non-negative number."
154
- );
155
- }
156
-
157
- if (
158
- props.renderWindowSize !== undefined &&
159
- (!Number.isInteger(props.renderWindowSize) || props.renderWindowSize <= 0)
160
- ) {
161
- throw new Error(
162
- "[react-native-reanimated-carousel] renderWindowSize must be a positive integer."
163
- );
164
- }
165
- }
166
-
167
- function isValidDefaultIndex(defaultIndex: number, dataLength: number) {
168
- return Number.isInteger(defaultIndex) && defaultIndex >= 0 && defaultIndex < dataLength;
169
- }
170
-
171
- function assertValidDefaultIndex(defaultIndex: number, dataLength: number) {
172
- if (!isValidDefaultIndex(defaultIndex, dataLength)) {
173
- throw new Error(
174
- `[react-native-reanimated-carousel] defaultIndex must be an integer between 0 and ${dataLength - 1}.`
175
- );
176
- }
177
- }
@@ -1,35 +0,0 @@
1
- import React from "react";
2
-
3
- import { Layouts } from "../layouts";
4
- import type { CarouselItemAnimation } from "../types";
5
- import type { InitializedCarouselProps } from "./useInitProps";
6
-
7
- type LayoutConfigOptions<Item> = InitializedCarouselProps<Item> & { size: number };
8
-
9
- export function useLayoutConfig<Item>(options: LayoutConfigOptions<Item>): CarouselItemAnimation {
10
- const { dataLength, directionSign, layout, orientation, size } = options;
11
- const isVertical = orientation === "vertical";
12
-
13
- return React.useMemo(() => {
14
- const baseConfig = { size, isVertical, directionSign };
15
- switch (layout?.type) {
16
- case "parallax":
17
- return Layouts.parallax(baseConfig, layout);
18
- case "horizontal-stack":
19
- return Layouts.horizontalStack(
20
- {
21
- ...layout,
22
- visibleCount: layout.visibleCount ?? Math.max(0, dataLength - 1),
23
- },
24
- directionSign
25
- );
26
- case "vertical-stack":
27
- return Layouts.verticalStack({
28
- ...layout,
29
- visibleCount: layout.visibleCount ?? Math.max(0, dataLength - 1),
30
- });
31
- default:
32
- return Layouts.normal(baseConfig);
33
- }
34
- }, [dataLength, directionSign, isVertical, layout, size]);
35
- }
@@ -1,81 +0,0 @@
1
- import type { SharedValue } from "react-native-reanimated";
2
- import { Extrapolation, interpolate, useDerivedValue } from "react-native-reanimated";
3
-
4
- import type { VisibleRangesValue } from "./useVisibleRanges";
5
-
6
- export interface OffsetOptions {
7
- index: number;
8
- size: number;
9
- handlerOffset: SharedValue<number>;
10
- dataLength: number;
11
- type?: "positive" | "negative" | undefined;
12
- viewCount?: number | undefined;
13
- loop?: boolean | undefined;
14
- }
15
-
16
- export const useOffsetX = (opts: OffsetOptions, visibleRanges: VisibleRangesValue) => {
17
- const {
18
- handlerOffset,
19
- index,
20
- size,
21
- loop,
22
- dataLength,
23
- type = "positive",
24
- viewCount: _viewCount,
25
- } = opts;
26
-
27
- const ITEM_LENGTH = dataLength;
28
- const VALID_LENGTH = ITEM_LENGTH - 1;
29
- const TOTAL_WIDTH = size * ITEM_LENGTH;
30
- const HALF_WIDTH = 0.5 * size;
31
-
32
- const viewCount = _viewCount ?? Math.round((ITEM_LENGTH - 1) / 2);
33
- const positiveCount = type === "positive" ? viewCount : VALID_LENGTH - viewCount;
34
-
35
- let startPos = size * index;
36
- if (index > positiveCount) startPos = (index - ITEM_LENGTH) * size;
37
-
38
- const MAX = positiveCount * size;
39
- const MIN = -((VALID_LENGTH - positiveCount) * size);
40
-
41
- const x = useDerivedValue(() => {
42
- if (!size) return 0;
43
-
44
- const { negativeRange, positiveRange } = visibleRanges.value;
45
-
46
- if (
47
- (index >= negativeRange[0] && index <= negativeRange[1]) ||
48
- (index >= positiveRange[0] && index <= positiveRange[1])
49
- ) {
50
- if (loop) {
51
- const inputRange = [
52
- -TOTAL_WIDTH,
53
- MIN - HALF_WIDTH - startPos - Number.MIN_VALUE,
54
- MIN - HALF_WIDTH - startPos,
55
- 0,
56
- MAX + HALF_WIDTH - startPos,
57
- MAX + HALF_WIDTH - startPos + Number.MIN_VALUE,
58
- TOTAL_WIDTH,
59
- ];
60
-
61
- const outputRange = [
62
- startPos,
63
- MAX + HALF_WIDTH - Number.MIN_VALUE,
64
- MIN - HALF_WIDTH,
65
- startPos,
66
- MAX + HALF_WIDTH,
67
- MIN - HALF_WIDTH + Number.MIN_VALUE,
68
- startPos,
69
- ];
70
-
71
- return interpolate(handlerOffset.value, inputRange, outputRange, Extrapolation.CLAMP);
72
- }
73
-
74
- return handlerOffset.value + size * index;
75
- }
76
-
77
- return Number.MAX_SAFE_INTEGER;
78
- }, [loop, dataLength, viewCount, type, size, visibleRanges, handlerOffset]);
79
-
80
- return x;
81
- };
@@ -1,33 +0,0 @@
1
- import type { SharedValue } from "react-native-reanimated";
2
- import { useAnimatedReaction } from "react-native-reanimated";
3
- import { scheduleOnRN } from "react-native-worklets";
4
-
5
- import type { CarouselProgressChangeHandler } from "../types";
6
- import { getLogicalProgress } from "../utils/carousel-math";
7
-
8
- export function useOnProgressChange(options: {
9
- loop: boolean;
10
- offset: SharedValue<number>;
11
- onProgressChange?: CarouselProgressChangeHandler | undefined;
12
- progress?: SharedValue<number> | undefined;
13
- rawDataLength: number;
14
- size: number;
15
- sizeReady: SharedValue<boolean>;
16
- }) {
17
- const { loop, offset, onProgressChange, progress, rawDataLength, size, sizeReady } = options;
18
-
19
- useAnimatedReaction(
20
- () => {
21
- if (!sizeReady.value || size <= 0 || rawDataLength <= 0) return null;
22
-
23
- const logicalProgress = getLogicalProgress(offset.value, size);
24
- return loop ? logicalProgress : Math.max(0, Math.min(rawDataLength - 1, logicalProgress));
25
- },
26
- (nextProgress) => {
27
- if (nextProgress == null) return;
28
- if (progress) progress.value = nextProgress;
29
- if (onProgressChange) scheduleOnRN(onProgressChange, nextProgress);
30
- },
31
- [loop, offset, onProgressChange, progress, rawDataLength, size, sizeReady]
32
- );
33
- }
@@ -1,144 +0,0 @@
1
- import { useMemo } from "react";
2
- import type {
3
- GestureStateChangeEvent,
4
- GestureUpdateEvent,
5
- PanGestureHandlerEventPayload,
6
- } from "react-native-gesture-handler";
7
- import { Gesture } from "react-native-gesture-handler";
8
-
9
- import type { CarouselPanGesture } from "../types";
10
- import type { GestureConfig } from "./useUpdateGestureConfig";
11
- import { useUpdateGestureConfig } from "./useUpdateGestureConfig";
12
-
13
- export const usePanGestureProxy = (customization: {
14
- onConfigurePanGesture?: ((gesture: CarouselPanGesture) => void) | undefined;
15
- onGestureStart: (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => void;
16
- onGestureUpdate: (event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => void;
17
- onGestureEnd: (
18
- event: GestureStateChangeEvent<PanGestureHandlerEventPayload>,
19
- success: boolean
20
- ) => void;
21
- options?: GestureConfig | undefined;
22
- }) => {
23
- const {
24
- onConfigurePanGesture,
25
- onGestureStart,
26
- onGestureUpdate,
27
- onGestureEnd,
28
- options = {},
29
- } = customization;
30
-
31
- const gesture = useMemo(() => {
32
- const gesture = Gesture.Pan().withTestId("rnrc-gesture-handler");
33
-
34
- // Save the original gesture callbacks
35
- const originalGestures = {
36
- onBegin: gesture.onBegin,
37
- onStart: gesture.onStart,
38
- onUpdate: gesture.onUpdate,
39
- onEnd: gesture.onEnd,
40
- onFinalize: gesture.onFinalize,
41
- };
42
-
43
- // Save the user defined gesture callbacks
44
- const userDefinedConflictGestures: {
45
- onBegin: Parameters<(typeof gesture)["onBegin"]>[0] | undefined;
46
- onStart: Parameters<(typeof gesture)["onStart"]>[0] | undefined;
47
- onUpdate: Parameters<(typeof gesture)["onUpdate"]>[0] | undefined;
48
- onEnd: Parameters<(typeof gesture)["onEnd"]>[0] | undefined;
49
- onFinalize: Parameters<(typeof gesture)["onFinalize"]>[0] | undefined;
50
- } = {
51
- onBegin: undefined,
52
- onStart: undefined,
53
- onUpdate: undefined,
54
- onEnd: undefined,
55
- onFinalize: undefined,
56
- };
57
-
58
- const fakeOnBegin: typeof gesture.onBegin = (cb) => {
59
- // Using fakeOnBegin to save the user defined callback
60
- userDefinedConflictGestures.onBegin = cb;
61
- return gesture;
62
- };
63
-
64
- const fakeOnStart: typeof gesture.onStart = (cb) => {
65
- // Using fakeOnStart to save the user defined callback
66
- userDefinedConflictGestures.onStart = cb;
67
- return gesture;
68
- };
69
-
70
- const fakeOnUpdate: typeof gesture.onUpdate = (cb) => {
71
- // Using fakeOnUpdate to save the user defined callback
72
- userDefinedConflictGestures.onUpdate = cb;
73
- return gesture;
74
- };
75
-
76
- const fakeOnEnd: typeof gesture.onEnd = (cb) => {
77
- // Using fakeOnEnd to save the user defined callback
78
- userDefinedConflictGestures.onEnd = cb;
79
- return gesture;
80
- };
81
-
82
- const fakeOnFinalize: typeof gesture.onFinalize = (cb) => {
83
- // Using fakeOnFinalize to save the user defined callback
84
- userDefinedConflictGestures.onFinalize = cb;
85
- return gesture;
86
- };
87
-
88
- // Setup the fake callbacks
89
- gesture.onBegin = fakeOnBegin;
90
- gesture.onStart = fakeOnStart;
91
- gesture.onUpdate = fakeOnUpdate;
92
- gesture.onEnd = fakeOnEnd;
93
- gesture.onFinalize = fakeOnFinalize;
94
-
95
- if (onConfigurePanGesture)
96
- // Get the gesture with the user defined configuration
97
- onConfigurePanGesture(gesture);
98
-
99
- // Restore the original callbacks
100
- gesture.onBegin = originalGestures.onBegin;
101
- gesture.onStart = originalGestures.onStart;
102
- gesture.onUpdate = originalGestures.onUpdate;
103
- gesture.onEnd = originalGestures.onEnd;
104
- gesture.onFinalize = originalGestures.onFinalize;
105
-
106
- // Setup the original callbacks with the user defined callbacks
107
- gesture
108
- .onBegin((e) => {
109
- "worklet";
110
-
111
- if (userDefinedConflictGestures.onBegin) userDefinedConflictGestures.onBegin(e);
112
- })
113
- .onStart((e) => {
114
- "worklet";
115
- onGestureStart(e);
116
-
117
- if (userDefinedConflictGestures.onStart) userDefinedConflictGestures.onStart(e);
118
- })
119
- .onUpdate((e) => {
120
- "worklet";
121
- onGestureUpdate(e);
122
-
123
- if (userDefinedConflictGestures.onUpdate) userDefinedConflictGestures.onUpdate(e);
124
- })
125
- .onEnd((e, success) => {
126
- "worklet";
127
- onGestureEnd(e, success);
128
-
129
- if (userDefinedConflictGestures.onEnd) userDefinedConflictGestures.onEnd(e, success);
130
- })
131
- .onFinalize((e, success) => {
132
- "worklet";
133
-
134
- if (userDefinedConflictGestures.onFinalize)
135
- userDefinedConflictGestures.onFinalize(e, success);
136
- });
137
-
138
- return gesture;
139
- }, [onGestureStart, onGestureUpdate, onGestureEnd, onConfigurePanGesture]);
140
-
141
- useUpdateGestureConfig(gesture, options);
142
-
143
- return gesture;
144
- };
@@ -1,41 +0,0 @@
1
- import React from "react";
2
- import { type StyleProp, StyleSheet, type ViewStyle } from "react-native";
3
-
4
- import type { InitializedCarouselProps } from "./useInitProps";
5
-
6
- export function usePropsErrorBoundary(props: InitializedCarouselProps<unknown>) {
7
- const warned = React.useRef({
8
- contentStyle: false,
9
- missingMainAxisSize: false,
10
- });
11
-
12
- React.useEffect(() => {
13
- if (!__DEV__) return;
14
-
15
- const { contentContainerStyle, orientation, style } = props;
16
- const flattenedContentStyle =
17
- StyleSheet.flatten(contentContainerStyle as StyleProp<ViewStyle>) || {};
18
- if (
19
- (flattenedContentStyle.opacity !== undefined ||
20
- flattenedContentStyle.transform !== undefined) &&
21
- !warned.current.contentStyle
22
- ) {
23
- console.warn(
24
- "[react-native-reanimated-carousel] `contentContainerStyle` cannot set `opacity` or `transform`; those fields are owned by Carousel and were ignored."
25
- );
26
- warned.current.contentStyle = true;
27
- }
28
-
29
- const flattenedStyle = StyleSheet.flatten(style) || {};
30
- const mainAxisSize = orientation === "vertical" ? flattenedStyle.height : flattenedStyle.width;
31
- const hasFlexibleSize =
32
- (typeof flattenedStyle.flex === "number" && flattenedStyle.flex > 0) ||
33
- mainAxisSize !== undefined;
34
- if (!hasFlexibleSize && !warned.current.missingMainAxisSize) {
35
- console.warn(
36
- `[react-native-reanimated-carousel] ${orientation} Carousel has no main-axis size in \`style\`; automatic layout measurement will be used.`
37
- );
38
- warned.current.missingMainAxisSize = true;
39
- }
40
- }, [props]);
41
- }
@@ -1,17 +0,0 @@
1
- import { useEffect } from "react";
2
-
3
- export interface GestureConfig {
4
- enabled?: boolean | undefined;
5
- }
6
-
7
- type ConfigurablePanGesture = {
8
- enabled(enabled: boolean): unknown;
9
- };
10
-
11
- export const useUpdateGestureConfig = (gesture: ConfigurablePanGesture, config: GestureConfig) => {
12
- const { enabled } = config;
13
-
14
- useEffect(() => {
15
- if (typeof enabled !== "undefined") gesture.enabled(enabled);
16
- }, [enabled, gesture]);
17
- };
@@ -1,141 +0,0 @@
1
- import { useRef } from "react";
2
- import type { SharedValue } from "react-native-reanimated";
3
- import { useDerivedValue } from "react-native-reanimated";
4
-
5
- type Range = [number, number];
6
-
7
- export interface VisibleRanges {
8
- negativeRange: Range;
9
- positiveRange: Range;
10
- }
11
-
12
- export type VisibleRangesValue = SharedValue<VisibleRanges>;
13
-
14
- function normalizeWindowSize(total: number, windowSize?: number) {
15
- "worklet";
16
-
17
- return typeof windowSize === "number" && Number.isFinite(windowSize) && windowSize > 0
18
- ? windowSize
19
- : total;
20
- }
21
-
22
- function normalizeLoopIndex(currentIndex: number, total: number) {
23
- "worklet";
24
-
25
- return currentIndex < 0 ? (currentIndex % total) + total : currentIndex;
26
- }
27
-
28
- export function computeVisibleRanges(params: {
29
- total: number;
30
- windowSize?: number | undefined;
31
- currentIndex: number;
32
- loop?: boolean | undefined;
33
- }): VisibleRanges {
34
- "worklet";
35
-
36
- const { total = 0, loop } = params;
37
- const windowSize = normalizeWindowSize(total, params.windowSize);
38
-
39
- if (total <= 0) {
40
- return {
41
- negativeRange: [0, 0],
42
- positiveRange: [0, -1],
43
- };
44
- }
45
-
46
- if (loop && windowSize >= total) {
47
- return {
48
- negativeRange: [0, 0],
49
- positiveRange: [0, total - 1],
50
- };
51
- }
52
-
53
- const positiveCount = Math.round(windowSize / 2);
54
- const negativeCount = windowSize - positiveCount;
55
-
56
- let currentIndex = params.currentIndex;
57
- if (!Number.isFinite(currentIndex)) currentIndex = 0;
58
-
59
- if (!loop) {
60
- currentIndex = Math.max(0, Math.min(total - 1, currentIndex));
61
- return {
62
- negativeRange: [Math.max(0, currentIndex - (windowSize - 1)), currentIndex],
63
- positiveRange: [currentIndex, Math.min(total - 1, currentIndex + (windowSize - 1))],
64
- };
65
- }
66
-
67
- currentIndex = normalizeLoopIndex(currentIndex, total);
68
-
69
- const negativeRange: Range = [
70
- (currentIndex - negativeCount + total) % total,
71
- (currentIndex - 1 + total) % total,
72
- ];
73
-
74
- const positiveRange: Range = [
75
- (currentIndex + total) % total,
76
- (currentIndex + positiveCount + total) % total,
77
- ];
78
-
79
- if (negativeRange[0] < total && negativeRange[0] > negativeRange[1]) {
80
- negativeRange[1] = total - 1;
81
- positiveRange[0] = 0;
82
- }
83
- if (positiveRange[0] > positiveRange[1]) {
84
- negativeRange[1] = total - 1;
85
- positiveRange[0] = 0;
86
- }
87
-
88
- return { negativeRange, positiveRange };
89
- }
90
-
91
- export function useVisibleRanges(options: {
92
- total: number;
93
- viewSize: number;
94
- windowSize?: number | undefined;
95
- translation: SharedValue<number>;
96
- loop?: boolean | undefined;
97
- }): VisibleRangesValue {
98
- const { total = 0, viewSize, translation, windowSize: _windowSize, loop } = options;
99
-
100
- const windowSize = normalizeWindowSize(total, _windowSize);
101
- const cachedRanges = useRef<VisibleRanges | null>(null);
102
-
103
- const ranges = useDerivedValue(() => {
104
- if (total <= 0) {
105
- return computeVisibleRanges({ total, currentIndex: 0, windowSize, loop });
106
- }
107
-
108
- // Prevent division by zero when viewSize is not yet measured
109
- if (!Number.isFinite(viewSize) || viewSize <= 0) {
110
- return {
111
- negativeRange: [0, 0] as Range,
112
- positiveRange: [0, Math.min(total - 1, windowSize - 1)] as Range,
113
- };
114
- }
115
-
116
- let currentIndex = Math.round(-translation.value / viewSize);
117
- if (!Number.isFinite(currentIndex)) currentIndex = 0;
118
-
119
- const newRanges = computeVisibleRanges({ total, windowSize, currentIndex, loop });
120
-
121
- if (
122
- cachedRanges.current &&
123
- isArraysEqual(cachedRanges.current.negativeRange, newRanges.negativeRange) &&
124
- isArraysEqual(cachedRanges.current.positiveRange, newRanges.positiveRange)
125
- ) {
126
- return cachedRanges.current;
127
- }
128
-
129
- cachedRanges.current = newRanges;
130
- return newRanges;
131
- }, [loop, total, windowSize, translation, viewSize]);
132
-
133
- return ranges;
134
- }
135
-
136
- function isArraysEqual(a: number[], b: number[]): boolean {
137
- "worklet";
138
- if (a.length !== b.length) return false;
139
-
140
- return a.every((value, index) => value === b[index]);
141
- }
@@ -1,10 +0,0 @@
1
- import { normalLayout } from "./normal";
2
- import { parallaxLayout } from "./parallax";
3
- import { horizontalStackLayout, verticalStackLayout } from "./stack";
4
-
5
- export const Layouts = {
6
- normal: normalLayout,
7
- parallax: parallaxLayout,
8
- horizontalStack: horizontalStackLayout,
9
- verticalStack: verticalStackLayout,
10
- };