@momo-kits/carousel 0.165.2-beta.3 → 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.
- package/animation.ts +62 -0
- package/index.tsx +455 -390
- package/package.json +5 -6
- package/types.ts +48 -123
- package/core/LICENSE +0 -21
- package/core/README.md +0 -16
- package/core/components/Carousel.tsx +0 -28
- package/core/components/CarouselLayout.tsx +0 -231
- package/core/components/ItemLayout.tsx +0 -215
- package/core/components/ItemRenderer.tsx +0 -126
- package/core/components/LazyView.tsx +0 -14
- package/core/components/ScrollViewGesture.tsx +0 -443
- package/core/constants/index.ts +0 -11
- package/core/hooks/useAutoPlay.ts +0 -63
- package/core/hooks/useCarouselController.tsx +0 -295
- package/core/hooks/useCheckMounted.ts +0 -14
- package/core/hooks/useCommonVariables.ts +0 -261
- package/core/hooks/useInitProps.ts +0 -177
- package/core/hooks/useLayoutConfig.ts +0 -35
- package/core/hooks/useOffsetX.ts +0 -81
- package/core/hooks/useOnProgressChange.ts +0 -33
- package/core/hooks/usePanGestureProxy.ts +0 -144
- package/core/hooks/usePropsErrorBoundary.ts +0 -41
- package/core/hooks/useUpdateGestureConfig.ts +0 -17
- package/core/hooks/useVisibleRanges.tsx +0 -141
- package/core/layouts/index.tsx +0 -10
- package/core/layouts/normal.ts +0 -32
- package/core/layouts/parallax.ts +0 -50
- package/core/layouts/stack.ts +0 -266
- package/core/public-types.ts +0 -209
- package/core/store/index.tsx +0 -63
- package/core/types.ts +0 -32
- package/core/utils/carousel-direction.ts +0 -39
- package/core/utils/carousel-math.ts +0 -156
- package/core/utils/compute-gesture-translation.ts +0 -29
- package/core/utils/compute-offset-if-size-changed.ts +0 -14
- package/core/utils/computed-with-auto-fill-data.ts +0 -92
- package/core/utils/deal-with-animation.ts +0 -19
- package/core/utils/log.ts +0 -12
- package/core/utils/sanitize-animation-style.ts +0 -20
|
@@ -1,215 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,126 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
};
|