rn-marquee-text 2.0.2 → 2.0.3

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.
@@ -1,14 +1,46 @@
1
- import React from 'react';
2
- declare const MarqueeText: ({ text, speed, backgroundColor, textColor, fontSize, paddingVertical, paddingHorizontal, delay, bounceMode, endPauseDuration, }: {
3
- text: string;
1
+ import * as React from 'react';
2
+ import type { ViewStyle } from 'react-native';
3
+ import type { SharedValue } from 'react-native-reanimated';
4
+ type MarqueeDirection = 'horizontal' | 'vertical';
5
+ export type MarqueeProps = React.PropsWithChildren<{
4
6
  speed?: number;
5
- backgroundColor?: string;
6
- textColor?: string;
7
- fontSize?: number;
8
- paddingVertical?: number;
9
- paddingHorizontal?: number;
10
- delay?: number;
11
- bounceMode?: boolean;
12
- endPauseDuration?: number;
13
- }) => React.JSX.Element;
14
- export default MarqueeText;
7
+ spacing?: number;
8
+ style?: ViewStyle;
9
+ reverse?: boolean;
10
+ frameRate?: number;
11
+ direction?: MarqueeDirection;
12
+ position?: SharedValue<number>;
13
+ withGesture?: boolean;
14
+ }>;
15
+ export type MarqueeRef = {
16
+ start: () => void;
17
+ stop: () => void;
18
+ isActive: boolean;
19
+ };
20
+ /**
21
+ * Marquee component that animates its children in a continuous loop.
22
+ *
23
+ * @param speed - Animation speed (pixels per frame)
24
+ * @param spacing - Spacing between cloned elements
25
+ * @param style - Container style
26
+ * @param reverse - Reverse animation direction
27
+ * @param frameRate - Custom frame rate (optional)
28
+ * @param direction - Animation direction ('horizontal' or 'vertical')
29
+ * @param position - Optional shared value to track position
30
+ * @param withGesture - Enable/disable gesture interactions
31
+ * @param ref - Ref to control animation
32
+ * @param children - Child elements to animate
33
+ */
34
+ export declare const Marquee: React.NamedExoticComponent<{
35
+ speed?: number;
36
+ spacing?: number;
37
+ style?: ViewStyle;
38
+ reverse?: boolean;
39
+ frameRate?: number;
40
+ direction?: MarqueeDirection;
41
+ position?: SharedValue<number>;
42
+ withGesture?: boolean;
43
+ } & {
44
+ children?: React.ReactNode | undefined;
45
+ } & React.RefAttributes<MarqueeRef>>;
46
+ export {};
@@ -1,64 +1,276 @@
1
- // MarqueeText.js - Complete rewrite with guaranteed scrolling
2
- import React, { useEffect } from 'react';
3
- import { View } from 'react-native';
4
- import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming, withDelay, Easing, withSequence, } from 'react-native-reanimated';
5
- var MarqueeText = function (_a) {
6
- var text = _a.text, _b = _a.speed, speed = _b === void 0 ? 60 : _b, _c = _a.backgroundColor, backgroundColor = _c === void 0 ? '#000' : _c, _d = _a.textColor, textColor = _d === void 0 ? '#fff' : _d, _e = _a.fontSize, fontSize = _e === void 0 ? 16 : _e, _f = _a.paddingVertical, paddingVertical = _f === void 0 ? 8 : _f, _g = _a.paddingHorizontal, paddingHorizontal = _g === void 0 ? 12 : _g, _h = _a.delay, delay = _h === void 0 ? 1000 : _h, _j = _a.bounceMode, bounceMode = _j === void 0 ? false : _j, _k = _a.endPauseDuration, endPauseDuration = _k === void 0 ? 2000 : _k;
7
- // Force a larger content width to ensure scrolling always happens
8
- // In real usage, we would measure dynamically, but this ensures it works
9
- var contentWidth = text.length * (fontSize * 0.6);
10
- var scrollX = useSharedValue(0);
11
- useEffect(function () {
12
- // Give layout time to complete
13
- var timeoutId = setTimeout(function () {
14
- if (bounceMode) {
15
- // Bounce animation (back and forth with pauses)
16
- scrollX.value = withDelay(delay, withRepeat(withSequence(withTiming(-contentWidth, {
17
- duration: contentWidth * (1000 / speed),
18
- easing: Easing.linear
19
- }), withTiming(-contentWidth, {
20
- duration: endPauseDuration,
21
- easing: Easing.linear
22
- }), withTiming(0, {
23
- duration: contentWidth * (1000 / speed),
24
- easing: Easing.linear
25
- }), withTiming(0, {
26
- duration: endPauseDuration,
27
- easing: Easing.linear
28
- })), -1, // Infinite repeats
29
- false));
30
- }
31
- else {
32
- // Standard loop animation
33
- scrollX.value = withDelay(delay, withRepeat(withTiming(-contentWidth, {
34
- duration: contentWidth * (1000 / speed),
35
- easing: Easing.linear
36
- }), -1, // Infinite repeats
37
- false));
38
- }
39
- }, 500);
40
- return function () { return clearTimeout(timeoutId); };
41
- }, [speed, delay, contentWidth, bounceMode, endPauseDuration]);
42
- var animatedStyle = useAnimatedStyle(function () {
43
- return {
44
- transform: [{ translateX: scrollX.value }],
45
- };
1
+ // // MarqueeText.js - Complete rewrite with guaranteed scrolling
2
+ // import React, { useEffect } from 'react';
3
+ // import { Text, View } from 'react-native';
4
+ // import Animated, {
5
+ // useSharedValue,
6
+ // useAnimatedStyle,
7
+ // withRepeat,
8
+ // withTiming,
9
+ // withDelay,
10
+ // Easing,
11
+ // withSequence,
12
+ // } from 'react-native-reanimated';
13
+ // const MarqueeText = ({
14
+ // text,
15
+ // speed = 60,
16
+ // backgroundColor = '#000',
17
+ // textColor = '#fff',
18
+ // fontSize = 16,
19
+ // paddingVertical = 8,
20
+ // paddingHorizontal = 12,
21
+ // delay = 1000,
22
+ // bounceMode = false,
23
+ // endPauseDuration = 2000,
24
+ // }:{
25
+ // text: string;
26
+ // speed?: number;
27
+ // backgroundColor?: string;
28
+ // textColor?: string;
29
+ // fontSize?: number;
30
+ // paddingVertical?: number;
31
+ // paddingHorizontal?: number;
32
+ // delay?: number;
33
+ // bounceMode?: boolean;
34
+ // endPauseDuration?: number;
35
+ // }) => {
36
+ // // Force a larger content width to ensure scrolling always happens
37
+ // // In real usage, we would measure dynamically, but this ensures it works
38
+ // const contentWidth = text.length * (fontSize * 0.6);
39
+ // const scrollX = useSharedValue(0);
40
+ // useEffect(() => {
41
+ // // Give layout time to complete
42
+ // const timeoutId = setTimeout(() => {
43
+ // if (bounceMode) {
44
+ // // Bounce animation (back and forth with pauses)
45
+ // scrollX.value = withDelay(
46
+ // delay,
47
+ // withRepeat(
48
+ // withSequence(
49
+ // withTiming(-contentWidth, {
50
+ // duration: contentWidth * (1000 / speed),
51
+ // easing: Easing.linear
52
+ // }),
53
+ // withTiming(-contentWidth, {
54
+ // duration: endPauseDuration,
55
+ // easing: Easing.linear
56
+ // }),
57
+ // withTiming(0, {
58
+ // duration: contentWidth * (1000 / speed),
59
+ // easing: Easing.linear
60
+ // }),
61
+ // withTiming(0, {
62
+ // duration: endPauseDuration,
63
+ // easing: Easing.linear
64
+ // })
65
+ // ),
66
+ // -1, // Infinite repeats
67
+ // false
68
+ // )
69
+ // );
70
+ // } else {
71
+ // // Standard loop animation
72
+ // scrollX.value = withDelay(
73
+ // delay,
74
+ // withRepeat(
75
+ // withTiming(-contentWidth, {
76
+ // duration: contentWidth * (1000 / speed),
77
+ // easing: Easing.linear
78
+ // }),
79
+ // -1, // Infinite repeats
80
+ // false
81
+ // )
82
+ // );
83
+ // }
84
+ // }, 500);
85
+ // return () => clearTimeout(timeoutId);
86
+ // }, [speed, delay, contentWidth, bounceMode, endPauseDuration]);
87
+ // const animatedStyle = useAnimatedStyle(() => {
88
+ // return {
89
+ // transform: [{ translateX: scrollX.value }],
90
+ // };
91
+ // });
92
+ // return (
93
+ // <View
94
+ // style={{
95
+ // backgroundColor,
96
+ // paddingVertical,
97
+ // paddingHorizontal,
98
+ // overflow: 'hidden',
99
+ // }}
100
+ // >
101
+ // <Animated.Text
102
+ // style={[
103
+ // {
104
+ // color: textColor,
105
+ // fontSize,
106
+ // },
107
+ // animatedStyle,
108
+ // ]}
109
+ // numberOfLines={1}
110
+ // // ellipsizeMode="tail"
111
+ // >
112
+ // {text}
113
+ // </Animated.Text>
114
+ // </View>
115
+ // );
116
+ // };
117
+ // export default MarqueeText;
118
+ import * as React from 'react';
119
+ import { StyleSheet, View } from 'react-native';
120
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
121
+ import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useFrameCallback, useSharedValue, withDecay, } from 'react-native-reanimated';
122
+ import { useFocusEffect } from '@react-navigation/native';
123
+ var AnimatedChild = React.memo(function (_a) {
124
+ var index = _a.index, children = _a.children, anim = _a.anim, textMeasurement = _a.textMeasurement, spacing = _a.spacing, direction = _a.direction;
125
+ var stylez = useAnimatedStyle(function () {
126
+ var _a;
127
+ var isVertical = direction === 'vertical';
128
+ var dimension = isVertical ? textMeasurement.value.height : textMeasurement.value.width;
129
+ if (dimension <= 0)
130
+ return {};
131
+ var position = (index - 1) * (dimension + spacing);
132
+ var translation = -(anim.value % (dimension + spacing));
133
+ return _a = {
134
+ position: 'absolute'
135
+ },
136
+ _a[isVertical ? 'top' : 'left'] = position,
137
+ _a.transform = [
138
+ {
139
+ translateX: translation,
140
+ },
141
+ ],
142
+ _a;
143
+ }, [index, spacing, textMeasurement, direction]);
144
+ return <Animated.View style={stylez}>{children}</Animated.View>;
145
+ });
146
+ /**
147
+ * Marquee component that animates its children in a continuous loop.
148
+ *
149
+ * @param speed - Animation speed (pixels per frame)
150
+ * @param spacing - Spacing between cloned elements
151
+ * @param style - Container style
152
+ * @param reverse - Reverse animation direction
153
+ * @param frameRate - Custom frame rate (optional)
154
+ * @param direction - Animation direction ('horizontal' or 'vertical')
155
+ * @param position - Optional shared value to track position
156
+ * @param withGesture - Enable/disable gesture interactions
157
+ * @param ref - Ref to control animation
158
+ * @param children - Child elements to animate
159
+ */
160
+ export var Marquee = React.memo(React.forwardRef(function (_a, ref) {
161
+ var _b = _a.speed, speed = _b === void 0 ? 1 : _b, children = _a.children, _c = _a.spacing, spacing = _c === void 0 ? 0 : _c, style = _a.style, _d = _a.reverse, reverse = _d === void 0 ? false : _d, frameRate = _a.frameRate, _e = _a.direction, direction = _e === void 0 ? 'horizontal' : _e, position = _a.position, _f = _a.withGesture, withGesture = _f === void 0 ? true : _f;
162
+ var isVertical = direction === 'vertical';
163
+ var parentMeasurement = useSharedValue({
164
+ width: 0,
165
+ height: 0,
166
+ x: 0,
167
+ y: 0,
168
+ });
169
+ var textMeasurement = useSharedValue({
170
+ width: 0,
171
+ height: 0,
172
+ x: 0,
173
+ y: 0,
174
+ });
175
+ var _g = React.useState(0), cloneTimes = _g[0], setCloneTimes = _g[1];
176
+ var anim = useSharedValue(0);
177
+ var isMounted = React.useRef(true);
178
+ var frameRateMs = frameRate ? 1000 / frameRate : null;
179
+ var frameCallback = useFrameCallback(function (frameInfo) {
180
+ if (frameInfo.timeSincePreviousFrame === null)
181
+ return;
182
+ var frameDelta = frameRateMs
183
+ ? frameInfo.timeSincePreviousFrame / frameRateMs
184
+ : 1;
185
+ anim.value += (reverse ? -1 : 1) * speed * frameDelta;
186
+ }, false);
187
+ useDerivedValue(function () {
188
+ if (position) {
189
+ position.value = anim.value;
190
+ }
46
191
  });
47
- return (<View style={{
48
- backgroundColor: backgroundColor,
49
- paddingVertical: paddingVertical,
50
- paddingHorizontal: paddingHorizontal,
51
- overflow: 'hidden',
192
+ useAnimatedReaction(function () {
193
+ var textDim = isVertical ? textMeasurement.value.height : textMeasurement.value.width;
194
+ var parentDim = isVertical ? parentMeasurement.value.height : parentMeasurement.value.width;
195
+ if (textDim <= 0 || parentDim <= 0)
196
+ return 0;
197
+ return Math.ceil(parentDim / textDim) + 2;
198
+ }, function (times) {
199
+ if (times > 0 && isMounted.current) {
200
+ runOnJS(setCloneTimes)(times);
201
+ }
202
+ }, [direction]);
203
+ // Control functions
204
+ var start = React.useCallback(function () {
205
+ frameCallback.setActive(true);
206
+ }, [frameCallback]);
207
+ var stop = React.useCallback(function () {
208
+ frameCallback.setActive(false);
209
+ }, [frameCallback]);
210
+ React.useImperativeHandle(ref, function () { return ({
211
+ start: start,
212
+ stop: stop,
213
+ isActive: frameCallback.isActive,
214
+ }); });
215
+ var pan = React.useMemo(function () {
216
+ return Gesture.Pan()
217
+ .enabled(withGesture)
218
+ .onBegin(function () { return runOnJS(stop)(); })
219
+ .onChange(function (e) {
220
+ anim.value += -(isVertical ? e.changeY : e.changeX);
221
+ })
222
+ .onFinalize(function (e) {
223
+ anim.value = withDecay({
224
+ velocity: -(isVertical ? e.velocityY : e.velocityX),
225
+ }, function (finished) { return finished && runOnJS(start)(); });
226
+ });
227
+ }, [withGesture, isVertical, anim, start, stop]);
228
+ // Handle focus/unmount
229
+ useFocusEffect(React.useCallback(function () {
230
+ start();
231
+ return function () {
232
+ stop();
233
+ anim.value = 0;
234
+ };
235
+ }, [start, stop, anim]));
236
+ React.useEffect(function () {
237
+ return function () {
238
+ isMounted.current = false;
239
+ };
240
+ }, []);
241
+ return (<Animated.View style={style} onLayout={function (ev) {
242
+ parentMeasurement.value = ev.nativeEvent.layout;
243
+ }} pointerEvents="box-none">
244
+ <GestureDetector gesture={pan}>
245
+ <Animated.View style={isVertical ? styles.column : styles.row} pointerEvents="box-none">
246
+ <Animated.ScrollView horizontal={!isVertical} style={styles.hidden} pointerEvents="none">
247
+ <View onLayout={function (ev) {
248
+ textMeasurement.value = ev.nativeEvent.layout;
52
249
  }}>
53
- <Animated.Text style={[
54
- {
55
- color: textColor,
56
- fontSize: fontSize,
57
- },
58
- animatedStyle,
59
- ]} numberOfLines={1}>
60
- {text}
61
- </Animated.Text>
62
- </View>);
63
- };
64
- export default MarqueeText;
250
+ {children}
251
+ </View>
252
+ </Animated.ScrollView>
253
+
254
+ {cloneTimes > 0 &&
255
+ Array.from({ length: cloneTimes }).map(function (_, index) { return (<AnimatedChild key={"clone-".concat(index)} index={index} anim={anim} textMeasurement={textMeasurement} spacing={spacing} direction={direction}>
256
+ {children}
257
+ </AnimatedChild>); })}
258
+ </Animated.View>
259
+ </GestureDetector>
260
+ </Animated.View>);
261
+ }));
262
+ var styles = StyleSheet.create({
263
+ hidden: {
264
+ opacity: 0,
265
+ zIndex: -9999,
266
+ flex: 0, // Prevent ScrollView from expanding
267
+ },
268
+ row: {
269
+ flexDirection: 'row',
270
+ flex: 1,
271
+ },
272
+ column: {
273
+ flexDirection: 'column',
274
+ flex: 1,
275
+ },
276
+ });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AutoScroll } from './AutoScroll';
2
+ import { Marquee } from './MarqueeText';
2
3
  import { AnimationMode } from './constants';
3
- export { AutoScroll, AnimationMode };
4
+ export { AutoScroll, AnimationMode, Marquee };
4
5
  export default AutoScroll;
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AutoScroll } from './AutoScroll';
2
+ import { Marquee } from './MarqueeText';
2
3
  import { AnimationMode } from './constants';
3
- export { AutoScroll, AnimationMode };
4
+ export { AutoScroll, AnimationMode, Marquee };
4
5
  export default AutoScroll;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-marquee-text",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "A customizable marquee (scrolling) text component for React Native",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -50,6 +50,7 @@
50
50
  "typescript": "^5.0.0"
51
51
  },
52
52
  "dependencies": {
53
+ "@react-navigation/native": "^7.1.9",
53
54
  "react-native-gesture-handler": "^2.25.0",
54
55
  "rn-marquee-text": "^1.0.4"
55
56
  }